Bug 1795985 [wpt PR 36537] - Reland "Don't take snapshot in CSSScroll/ViewTimeline...
[gecko.git] / image / imgRequestProxy.cpp
blob739c72da681094b096b493c3e648e8e9fbb63128
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 NS_IMETHODIMP imgRequestProxy::GetTriggeringPrincipal(
237 nsIPrincipal** aTriggeringPrincipal) {
238 MOZ_ASSERT(GetOwner());
239 nsCOMPtr<nsIPrincipal> triggeringPrincipal =
240 GetOwner()->GetTriggeringPrincipal();
241 triggeringPrincipal.forget(aTriggeringPrincipal);
242 return NS_OK;
245 void imgRequestProxy::MarkValidating() {
246 MOZ_ASSERT(GetValidator());
247 mValidating = true;
250 void imgRequestProxy::ClearValidating() {
251 MOZ_ASSERT(mValidating);
252 MOZ_ASSERT(!GetValidator());
253 mValidating = false;
255 // If we'd previously requested a synchronous decode, request a decode on the
256 // new image.
257 if (mDecodeRequested) {
258 mDecodeRequested = false;
259 StartDecoding(imgIContainer::FLAG_NONE);
263 already_AddRefed<nsIEventTarget> imgRequestProxy::GetEventTarget() const {
264 nsCOMPtr<nsIEventTarget> target(mEventTarget);
265 return target.forget();
268 bool imgRequestProxy::HasDecodedPixels() {
269 if (IsValidating()) {
270 return false;
273 RefPtr<Image> image = GetImage();
274 if (image) {
275 return image->HasDecodedPixels();
278 return false;
281 nsresult imgRequestProxy::DispatchWithTargetIfAvailable(
282 already_AddRefed<nsIRunnable> aEvent) {
283 LOG_FUNC(gImgLog, "imgRequestProxy::DispatchWithTargetIfAvailable");
285 // This method should only be used when it is *expected* that we are
286 // dispatching an event (e.g. we want to handle an event asynchronously)
287 // rather we need to (e.g. we are in the wrong scheduler group context).
288 // As such, we do not set mHadDispatch for telemetry purposes.
289 if (mEventTarget) {
290 mEventTarget->Dispatch(CreateRenderBlockingRunnable(std::move(aEvent)),
291 NS_DISPATCH_NORMAL);
292 return NS_OK;
295 return NS_DispatchToMainThread(
296 CreateRenderBlockingRunnable(std::move(aEvent)));
299 void imgRequestProxy::AddToOwner(Document* aLoadingDocument) {
300 // An imgRequestProxy can be initialized with neither a listener nor a
301 // document. The caller could follow up later by cloning the canonical
302 // imgRequestProxy with the actual listener. This is possible because
303 // imgLoader::LoadImage does not require a valid listener to be provided.
305 // Without a listener, we don't need to set our scheduler group, because
306 // we have nothing to signal. However if we were told what document this
307 // is for, it is likely that future listeners will belong to the same
308 // scheduler group.
310 // With a listener, we always need to update our scheduler group. A null
311 // scheduler group is valid with or without a document, but that means
312 // we will use the most generic event target possible on dispatch.
313 if (aLoadingDocument) {
314 RefPtr<mozilla::dom::DocGroup> docGroup = aLoadingDocument->GetDocGroup();
315 if (docGroup) {
316 mEventTarget = docGroup->EventTargetFor(mozilla::TaskCategory::Other);
317 MOZ_ASSERT(mEventTarget);
321 if (mListener && !mEventTarget) {
322 mEventTarget = do_GetMainThread();
325 imgRequest* owner = GetOwner();
326 if (!owner) {
327 return;
330 owner->AddProxy(this);
333 void imgRequestProxy::RemoveFromOwner(nsresult aStatus) {
334 imgRequest* owner = GetOwner();
335 if (owner) {
336 if (mValidating) {
337 imgCacheValidator* validator = owner->GetValidator();
338 MOZ_ASSERT(validator);
339 validator->RemoveProxy(this);
340 mValidating = false;
343 owner->RemoveProxy(this, aStatus);
347 void imgRequestProxy::AddToLoadGroup() {
348 NS_ASSERTION(!mIsInLoadGroup, "Whaa, we're already in the loadgroup!");
349 MOZ_ASSERT(!mForceDispatchLoadGroup);
351 /* While in theory there could be a dispatch outstanding to remove this
352 request from the load group, in practice we only add to the load group
353 (when previously not in a load group) at initialization. */
354 if (!mIsInLoadGroup && mLoadGroup) {
355 LOG_FUNC(gImgLog, "imgRequestProxy::AddToLoadGroup");
356 mLoadGroup->AddRequest(this, nullptr);
357 mIsInLoadGroup = true;
361 void imgRequestProxy::RemoveFromLoadGroup() {
362 if (!mIsInLoadGroup || !mLoadGroup) {
363 return;
366 /* Sometimes we may not be able to remove ourselves from the load group in
367 the current context. This is because our listeners are not re-entrant (e.g.
368 we are in the middle of CancelAndForgetObserver or SyncClone). */
369 if (mForceDispatchLoadGroup) {
370 LOG_FUNC(gImgLog, "imgRequestProxy::RemoveFromLoadGroup -- dispatch");
372 /* We take away the load group from the request temporarily; this prevents
373 additional dispatches via RemoveFromLoadGroup occurring, as well as
374 MoveToBackgroundInLoadGroup from removing and readding. This is safe
375 because we know that once we get here, blocking the load group at all is
376 unnecessary. */
377 mIsInLoadGroup = false;
378 nsCOMPtr<nsILoadGroup> loadGroup = std::move(mLoadGroup);
379 RefPtr<imgRequestProxy> self(this);
380 DispatchWithTargetIfAvailable(NS_NewRunnableFunction(
381 "imgRequestProxy::RemoveFromLoadGroup", [self, loadGroup]() -> void {
382 loadGroup->RemoveRequest(self, nullptr, NS_OK);
383 }));
384 return;
387 LOG_FUNC(gImgLog, "imgRequestProxy::RemoveFromLoadGroup");
389 /* calling RemoveFromLoadGroup may cause the document to finish
390 loading, which could result in our death. We need to make sure
391 that we stay alive long enough to fight another battle... at
392 least until we exit this function. */
393 nsCOMPtr<imgIRequest> kungFuDeathGrip(this);
394 mLoadGroup->RemoveRequest(this, nullptr, NS_OK);
395 mLoadGroup = nullptr;
396 mIsInLoadGroup = false;
399 void imgRequestProxy::MoveToBackgroundInLoadGroup() {
400 /* Even if we are still in the load group, we may have taken away the load
401 group reference itself because we are in the process of leaving the group.
402 In that case, there is no need to background the request. */
403 if (!mLoadGroup) {
404 return;
407 /* There is no need to dispatch if we only need to add ourselves to the load
408 group without removal. It is the removal which causes the problematic
409 callbacks (see RemoveFromLoadGroup). */
410 if (mIsInLoadGroup && mForceDispatchLoadGroup) {
411 LOG_FUNC(gImgLog,
412 "imgRequestProxy::MoveToBackgroundInLoadGroup -- dispatch");
414 RefPtr<imgRequestProxy> self(this);
415 DispatchWithTargetIfAvailable(NS_NewRunnableFunction(
416 "imgRequestProxy::MoveToBackgroundInLoadGroup",
417 [self]() -> void { self->MoveToBackgroundInLoadGroup(); }));
418 return;
421 LOG_FUNC(gImgLog, "imgRequestProxy::MoveToBackgroundInLoadGroup");
422 nsCOMPtr<imgIRequest> kungFuDeathGrip(this);
423 if (mIsInLoadGroup) {
424 mLoadGroup->RemoveRequest(this, nullptr, NS_OK);
427 mLoadFlags |= nsIRequest::LOAD_BACKGROUND;
428 mLoadGroup->AddRequest(this, nullptr);
431 /** nsIRequest / imgIRequest methods **/
433 NS_IMETHODIMP
434 imgRequestProxy::GetName(nsACString& aName) {
435 aName.Truncate();
437 if (mURI) {
438 mURI->GetSpec(aName);
441 return NS_OK;
444 NS_IMETHODIMP
445 imgRequestProxy::IsPending(bool* _retval) { return NS_ERROR_NOT_IMPLEMENTED; }
447 NS_IMETHODIMP
448 imgRequestProxy::GetStatus(nsresult* aStatus) {
449 return NS_ERROR_NOT_IMPLEMENTED;
452 NS_IMETHODIMP imgRequestProxy::SetCanceledReason(const nsACString& aReason) {
453 return SetCanceledReasonImpl(aReason);
456 NS_IMETHODIMP imgRequestProxy::GetCanceledReason(nsACString& aReason) {
457 return GetCanceledReasonImpl(aReason);
460 NS_IMETHODIMP imgRequestProxy::CancelWithReason(nsresult aStatus,
461 const nsACString& aReason) {
462 return CancelWithReasonImpl(aStatus, aReason);
465 NS_IMETHODIMP
466 imgRequestProxy::Cancel(nsresult status) {
467 if (mCanceled) {
468 return NS_ERROR_FAILURE;
471 LOG_SCOPE(gImgLog, "imgRequestProxy::Cancel");
473 mCanceled = true;
475 nsCOMPtr<nsIRunnable> ev = new imgCancelRunnable(this, status);
476 return DispatchWithTargetIfAvailable(ev.forget());
479 void imgRequestProxy::DoCancel(nsresult status) {
480 RemoveFromOwner(status);
481 RemoveFromLoadGroup();
482 NullOutListener();
485 NS_IMETHODIMP
486 imgRequestProxy::CancelAndForgetObserver(nsresult aStatus) {
487 // If mCanceled is true but mListener is non-null, that means
488 // someone called Cancel() on us but the imgCancelRunnable is still
489 // pending. We still need to null out mListener before returning
490 // from this function in this case. That means we want to do the
491 // RemoveProxy call right now, because we need to deliver the
492 // onStopRequest.
493 if (mCanceled && !mListener) {
494 return NS_ERROR_FAILURE;
497 LOG_SCOPE(gImgLog, "imgRequestProxy::CancelAndForgetObserver");
499 mCanceled = true;
500 mForceDispatchLoadGroup = true;
501 RemoveFromOwner(aStatus);
502 RemoveFromLoadGroup();
503 mForceDispatchLoadGroup = false;
505 NullOutListener();
507 return NS_OK;
510 NS_IMETHODIMP
511 imgRequestProxy::StartDecoding(uint32_t aFlags) {
512 // Flag this, so we know to request after validation if pending.
513 if (IsValidating()) {
514 mDecodeRequested = true;
515 return NS_OK;
518 RefPtr<Image> image = GetImage();
519 if (image) {
520 return image->StartDecoding(aFlags);
523 if (GetOwner()) {
524 GetOwner()->StartDecoding();
527 return NS_OK;
530 bool imgRequestProxy::StartDecodingWithResult(uint32_t aFlags) {
531 // Flag this, so we know to request after validation if pending.
532 if (IsValidating()) {
533 mDecodeRequested = true;
534 return false;
537 RefPtr<Image> image = GetImage();
538 if (image) {
539 return image->StartDecodingWithResult(aFlags);
542 if (GetOwner()) {
543 GetOwner()->StartDecoding();
546 return false;
549 imgIContainer::DecodeResult imgRequestProxy::RequestDecodeWithResult(
550 uint32_t aFlags) {
551 if (IsValidating()) {
552 mDecodeRequested = true;
553 return imgIContainer::DECODE_REQUESTED;
556 RefPtr<Image> image = GetImage();
557 if (image) {
558 return image->RequestDecodeWithResult(aFlags);
561 if (GetOwner()) {
562 GetOwner()->StartDecoding();
565 return imgIContainer::DECODE_REQUESTED;
568 NS_IMETHODIMP
569 imgRequestProxy::LockImage() {
570 mLockCount++;
571 RefPtr<Image> image =
572 GetOwner() && GetOwner()->ImageAvailable() ? GetImage() : nullptr;
573 if (image) {
574 return image->LockImage();
576 return NS_OK;
579 NS_IMETHODIMP
580 imgRequestProxy::UnlockImage() {
581 MOZ_ASSERT(mLockCount > 0, "calling unlock but no locks!");
583 mLockCount--;
584 RefPtr<Image> image =
585 GetOwner() && GetOwner()->ImageAvailable() ? GetImage() : nullptr;
586 if (image) {
587 return image->UnlockImage();
589 return NS_OK;
592 NS_IMETHODIMP
593 imgRequestProxy::RequestDiscard() {
594 RefPtr<Image> image = GetImage();
595 if (image) {
596 return image->RequestDiscard();
598 return NS_OK;
601 NS_IMETHODIMP
602 imgRequestProxy::IncrementAnimationConsumers() {
603 mAnimationConsumers++;
604 RefPtr<Image> image =
605 GetOwner() && GetOwner()->ImageAvailable() ? GetImage() : nullptr;
606 if (image) {
607 image->IncrementAnimationConsumers();
609 return NS_OK;
612 NS_IMETHODIMP
613 imgRequestProxy::DecrementAnimationConsumers() {
614 // We may get here if some responsible code called Increment,
615 // then called us, but we have meanwhile called ClearAnimationConsumers
616 // because we needed to get rid of them earlier (see
617 // imgRequest::RemoveProxy), and hence have nothing left to
618 // decrement. (In such a case we got rid of the animation consumers
619 // early, but not the observer.)
620 if (mAnimationConsumers > 0) {
621 mAnimationConsumers--;
622 RefPtr<Image> image =
623 GetOwner() && GetOwner()->ImageAvailable() ? GetImage() : nullptr;
624 if (image) {
625 image->DecrementAnimationConsumers();
628 return NS_OK;
631 void imgRequestProxy::ClearAnimationConsumers() {
632 while (mAnimationConsumers > 0) {
633 DecrementAnimationConsumers();
637 NS_IMETHODIMP
638 imgRequestProxy::Suspend() { return NS_ERROR_NOT_IMPLEMENTED; }
640 NS_IMETHODIMP
641 imgRequestProxy::Resume() { return NS_ERROR_NOT_IMPLEMENTED; }
643 NS_IMETHODIMP
644 imgRequestProxy::GetLoadGroup(nsILoadGroup** loadGroup) {
645 NS_IF_ADDREF(*loadGroup = mLoadGroup.get());
646 return NS_OK;
648 NS_IMETHODIMP
649 imgRequestProxy::SetLoadGroup(nsILoadGroup* loadGroup) {
650 if (loadGroup != mLoadGroup) {
651 MOZ_ASSERT_UNREACHABLE("Switching load groups is unsupported!");
652 return NS_ERROR_NOT_IMPLEMENTED;
654 return NS_OK;
657 NS_IMETHODIMP
658 imgRequestProxy::GetLoadFlags(nsLoadFlags* flags) {
659 *flags = mLoadFlags;
660 return NS_OK;
662 NS_IMETHODIMP
663 imgRequestProxy::SetLoadFlags(nsLoadFlags flags) {
664 mLoadFlags = flags;
665 return NS_OK;
668 NS_IMETHODIMP
669 imgRequestProxy::GetTRRMode(nsIRequest::TRRMode* aTRRMode) {
670 return GetTRRModeImpl(aTRRMode);
673 NS_IMETHODIMP
674 imgRequestProxy::SetTRRMode(nsIRequest::TRRMode aTRRMode) {
675 return SetTRRModeImpl(aTRRMode);
678 /** imgIRequest methods **/
680 NS_IMETHODIMP
681 imgRequestProxy::GetImage(imgIContainer** aImage) {
682 NS_ENSURE_TRUE(aImage, NS_ERROR_NULL_POINTER);
683 // It's possible that our owner has an image but hasn't notified us of it -
684 // that'll happen if we get Canceled before the owner instantiates its image
685 // (because Canceling unregisters us as a listener on mOwner). If we're
686 // in that situation, just grab the image off of mOwner.
687 RefPtr<Image> image = GetImage();
688 nsCOMPtr<imgIContainer> imageToReturn;
689 if (image) {
690 imageToReturn = image;
692 if (!imageToReturn && GetOwner()) {
693 imageToReturn = GetOwner()->GetImage();
695 if (!imageToReturn) {
696 return NS_ERROR_FAILURE;
699 imageToReturn.swap(*aImage);
701 return NS_OK;
704 NS_IMETHODIMP
705 imgRequestProxy::GetProviderId(uint32_t* aId) {
706 NS_ENSURE_TRUE(aId, NS_ERROR_NULL_POINTER);
708 nsCOMPtr<imgIContainer> image;
709 nsresult rv = GetImage(getter_AddRefs(image));
710 if (NS_SUCCEEDED(rv)) {
711 *aId = image->GetProviderId();
712 } else {
713 *aId = 0;
716 return NS_OK;
719 NS_IMETHODIMP
720 imgRequestProxy::GetImageStatus(uint32_t* aStatus) {
721 if (IsValidating()) {
722 // We are currently validating the image, and so our status could revert if
723 // we discard the cache. We should also be deferring notifications, such
724 // that the caller will be notified when validation completes. Rather than
725 // risk misleading the caller, return nothing.
726 *aStatus = imgIRequest::STATUS_NONE;
727 } else {
728 RefPtr<ProgressTracker> progressTracker = GetProgressTracker();
729 *aStatus = progressTracker->GetImageStatus();
732 return NS_OK;
735 NS_IMETHODIMP
736 imgRequestProxy::GetImageErrorCode(nsresult* aStatus) {
737 if (!GetOwner()) {
738 return NS_ERROR_FAILURE;
741 *aStatus = GetOwner()->GetImageErrorCode();
743 return NS_OK;
746 NS_IMETHODIMP
747 imgRequestProxy::GetURI(nsIURI** aURI) {
748 MOZ_ASSERT(NS_IsMainThread(), "Must be on main thread to convert URI");
749 nsCOMPtr<nsIURI> uri = mURI;
750 uri.forget(aURI);
751 return NS_OK;
754 nsresult imgRequestProxy::GetFinalURI(nsIURI** aURI) {
755 if (!GetOwner()) {
756 return NS_ERROR_FAILURE;
759 return GetOwner()->GetFinalURI(aURI);
762 NS_IMETHODIMP
763 imgRequestProxy::GetNotificationObserver(imgINotificationObserver** aObserver) {
764 *aObserver = mListener;
765 NS_IF_ADDREF(*aObserver);
766 return NS_OK;
769 NS_IMETHODIMP
770 imgRequestProxy::GetMimeType(char** aMimeType) {
771 if (!GetOwner()) {
772 return NS_ERROR_FAILURE;
775 const char* type = GetOwner()->GetMimeType();
776 if (!type) {
777 return NS_ERROR_FAILURE;
780 *aMimeType = NS_xstrdup(type);
782 return NS_OK;
785 NS_IMETHODIMP
786 imgRequestProxy::GetFileName(nsACString& aFileName) {
787 if (!GetOwner()) {
788 return NS_ERROR_FAILURE;
791 GetOwner()->GetFileName(aFileName);
792 return NS_OK;
795 imgRequestProxy* imgRequestProxy::NewClonedProxy() {
796 return new imgRequestProxy();
799 NS_IMETHODIMP
800 imgRequestProxy::Clone(imgINotificationObserver* aObserver,
801 imgIRequest** aClone) {
802 nsresult result;
803 imgRequestProxy* proxy;
804 result = PerformClone(aObserver, nullptr, /* aSyncNotify */ true, &proxy);
805 *aClone = proxy;
806 return result;
809 nsresult imgRequestProxy::SyncClone(imgINotificationObserver* aObserver,
810 Document* aLoadingDocument,
811 imgRequestProxy** aClone) {
812 return PerformClone(aObserver, aLoadingDocument,
813 /* aSyncNotify */ true, aClone);
816 nsresult imgRequestProxy::Clone(imgINotificationObserver* aObserver,
817 Document* aLoadingDocument,
818 imgRequestProxy** aClone) {
819 return PerformClone(aObserver, aLoadingDocument,
820 /* aSyncNotify */ false, aClone);
823 nsresult imgRequestProxy::PerformClone(imgINotificationObserver* aObserver,
824 Document* aLoadingDocument,
825 bool aSyncNotify,
826 imgRequestProxy** aClone) {
827 MOZ_ASSERT(aClone, "Null out param");
829 LOG_SCOPE(gImgLog, "imgRequestProxy::Clone");
831 *aClone = nullptr;
832 RefPtr<imgRequestProxy> clone = NewClonedProxy();
834 nsCOMPtr<nsILoadGroup> loadGroup;
835 if (aLoadingDocument) {
836 loadGroup = aLoadingDocument->GetDocumentLoadGroup();
839 // It is important to call |SetLoadFlags()| before calling |Init()| because
840 // |Init()| adds the request to the loadgroup.
841 // When a request is added to a loadgroup, its load flags are merged
842 // with the load flags of the loadgroup.
843 // XXXldb That's not true anymore. Stuff from imgLoader adds the
844 // request to the loadgroup.
845 clone->SetLoadFlags(mLoadFlags);
846 nsresult rv = clone->Init(mBehaviour->GetOwner(), loadGroup, aLoadingDocument,
847 mURI, aObserver);
848 if (NS_FAILED(rv)) {
849 return rv;
852 // Assign to *aClone before calling Notify so that if the caller expects to
853 // only be notified for requests it's already holding pointers to it won't be
854 // surprised.
855 NS_ADDREF(*aClone = clone);
857 imgCacheValidator* validator = GetValidator();
858 if (validator) {
859 // Note that if we have a validator, we don't want to issue notifications at
860 // here because we want to defer until that completes. AddProxy will add us
861 // to the load group; we cannot avoid that in this case, because we don't
862 // know when the validation will complete, and if it will cause us to
863 // discard our cached state anyways. We are probably already blocked by the
864 // original LoadImage(WithChannel) request in any event.
865 clone->MarkValidating();
866 validator->AddProxy(clone);
867 } else {
868 // We only want to add the request to the load group of the owning document
869 // if it is still in progress. Some callers cannot handle a supurious load
870 // group removal (e.g. print preview) so we must be careful. On the other
871 // hand, if after cloning, the original request proxy is cancelled /
872 // destroyed, we need to ensure that any clones still block the load group
873 // if it is incomplete.
874 bool addToLoadGroup = mIsInLoadGroup;
875 if (!addToLoadGroup) {
876 RefPtr<ProgressTracker> tracker = clone->GetProgressTracker();
877 addToLoadGroup =
878 tracker && !(tracker->GetProgress() & FLAG_LOAD_COMPLETE);
881 if (addToLoadGroup) {
882 clone->AddToLoadGroup();
885 if (aSyncNotify) {
886 // This is wrong!!! We need to notify asynchronously, but there's code
887 // that assumes that we don't. This will be fixed in bug 580466. Note that
888 // if we have a validator, we won't issue notifications anyways because
889 // they are deferred, so there is no point in requesting.
890 clone->mForceDispatchLoadGroup = true;
891 clone->SyncNotifyListener();
892 clone->mForceDispatchLoadGroup = false;
893 } else {
894 // Without a validator, we can request asynchronous notifications
895 // immediately. If there was a validator, this would override the deferral
896 // and that would be incorrect.
897 clone->NotifyListener();
901 return NS_OK;
904 NS_IMETHODIMP
905 imgRequestProxy::GetImagePrincipal(nsIPrincipal** aPrincipal) {
906 if (!GetOwner()) {
907 return NS_ERROR_FAILURE;
910 nsCOMPtr<nsIPrincipal> principal = GetOwner()->GetPrincipal();
911 principal.forget(aPrincipal);
912 return NS_OK;
915 NS_IMETHODIMP
916 imgRequestProxy::GetHadCrossOriginRedirects(bool* aHadCrossOriginRedirects) {
917 *aHadCrossOriginRedirects = false;
919 nsCOMPtr<nsITimedChannel> timedChannel = TimedChannel();
920 if (timedChannel) {
921 bool allRedirectsSameOrigin = false;
922 *aHadCrossOriginRedirects =
923 NS_SUCCEEDED(
924 timedChannel->GetAllRedirectsSameOrigin(&allRedirectsSameOrigin)) &&
925 !allRedirectsSameOrigin;
928 return NS_OK;
931 NS_IMETHODIMP
932 imgRequestProxy::GetMultipart(bool* aMultipart) {
933 if (!GetOwner()) {
934 return NS_ERROR_FAILURE;
937 *aMultipart = GetOwner()->GetMultipart();
939 return NS_OK;
942 NS_IMETHODIMP
943 imgRequestProxy::GetCORSMode(int32_t* aCorsMode) {
944 if (!GetOwner()) {
945 return NS_ERROR_FAILURE;
948 *aCorsMode = GetOwner()->GetCORSMode();
950 return NS_OK;
953 NS_IMETHODIMP
954 imgRequestProxy::BoostPriority(uint32_t aCategory) {
955 NS_ENSURE_STATE(GetOwner() && !mCanceled);
956 GetOwner()->BoostPriority(aCategory);
957 return NS_OK;
960 /** nsISupportsPriority methods **/
962 NS_IMETHODIMP
963 imgRequestProxy::GetPriority(int32_t* priority) {
964 NS_ENSURE_STATE(GetOwner());
965 *priority = GetOwner()->Priority();
966 return NS_OK;
969 NS_IMETHODIMP
970 imgRequestProxy::SetPriority(int32_t priority) {
971 NS_ENSURE_STATE(GetOwner() && !mCanceled);
972 GetOwner()->AdjustPriority(this, priority - GetOwner()->Priority());
973 return NS_OK;
976 NS_IMETHODIMP
977 imgRequestProxy::AdjustPriority(int32_t priority) {
978 // We don't require |!mCanceled| here. This may be called even if we're
979 // cancelled, because it's invoked as part of the process of removing an image
980 // from the load group.
981 NS_ENSURE_STATE(GetOwner());
982 GetOwner()->AdjustPriority(this, priority);
983 return NS_OK;
986 static const char* NotificationTypeToString(int32_t aType) {
987 switch (aType) {
988 case imgINotificationObserver::SIZE_AVAILABLE:
989 return "SIZE_AVAILABLE";
990 case imgINotificationObserver::FRAME_UPDATE:
991 return "FRAME_UPDATE";
992 case imgINotificationObserver::FRAME_COMPLETE:
993 return "FRAME_COMPLETE";
994 case imgINotificationObserver::LOAD_COMPLETE:
995 return "LOAD_COMPLETE";
996 case imgINotificationObserver::DECODE_COMPLETE:
997 return "DECODE_COMPLETE";
998 case imgINotificationObserver::DISCARD:
999 return "DISCARD";
1000 case imgINotificationObserver::UNLOCKED_DRAW:
1001 return "UNLOCKED_DRAW";
1002 case imgINotificationObserver::IS_ANIMATED:
1003 return "IS_ANIMATED";
1004 case imgINotificationObserver::HAS_TRANSPARENCY:
1005 return "HAS_TRANSPARENCY";
1006 default:
1007 MOZ_ASSERT_UNREACHABLE("Notification list should be exhaustive");
1008 return "(unknown notification)";
1012 void imgRequestProxy::Notify(int32_t aType,
1013 const mozilla::gfx::IntRect* aRect) {
1014 MOZ_ASSERT(aType != imgINotificationObserver::LOAD_COMPLETE,
1015 "Should call OnLoadComplete");
1017 LOG_FUNC_WITH_PARAM(gImgLog, "imgRequestProxy::Notify", "type",
1018 NotificationTypeToString(aType));
1020 if (!mListener || mCanceled) {
1021 return;
1024 // Make sure the listener stays alive while we notify.
1025 nsCOMPtr<imgINotificationObserver> listener(mListener);
1027 listener->Notify(this, aType, aRect);
1030 void imgRequestProxy::OnLoadComplete(bool aLastPart) {
1031 LOG_FUNC_WITH_PARAM(gImgLog, "imgRequestProxy::OnLoadComplete", "uri", mURI);
1033 // There's all sorts of stuff here that could kill us (the OnStopRequest call
1034 // on the listener, the removal from the loadgroup, the release of the
1035 // listener, etc). Don't let them do it.
1036 RefPtr<imgRequestProxy> self(this);
1038 if (mListener && !mCanceled) {
1039 // Hold a ref to the listener while we call it, just in case.
1040 nsCOMPtr<imgINotificationObserver> listener(mListener);
1041 listener->Notify(this, imgINotificationObserver::LOAD_COMPLETE, nullptr);
1044 // If we're expecting more data from a multipart channel, re-add ourself
1045 // to the loadgroup so that the document doesn't lose track of the load.
1046 // If the request is already a background request and there's more data
1047 // coming, we can just leave the request in the loadgroup as-is.
1048 if (aLastPart || (mLoadFlags & nsIRequest::LOAD_BACKGROUND) == 0) {
1049 if (aLastPart) {
1050 RemoveFromLoadGroup();
1052 nsresult errorCode = NS_OK;
1053 // if the load is cross origin without CORS, or the CORS access is
1054 // rejected, always fire load event to avoid leaking site information for
1055 // <link rel=preload>.
1056 // XXXedgar, currently we don't do the same thing for <img>.
1057 imgRequest* request = GetOwner();
1058 if (!request || !(request->IsDeniedCrossSiteCORSRequest() ||
1059 request->IsCrossSiteNoCORSRequest())) {
1060 uint32_t status = imgIRequest::STATUS_NONE;
1061 GetImageStatus(&status);
1062 if (status & imgIRequest::STATUS_ERROR) {
1063 errorCode = NS_ERROR_FAILURE;
1066 NotifyStop(errorCode);
1067 } else {
1068 // More data is coming, so change the request to be a background request
1069 // and put it back in the loadgroup.
1070 MoveToBackgroundInLoadGroup();
1074 if (mListenerIsStrongRef && aLastPart) {
1075 MOZ_ASSERT(mListener, "How did that happen?");
1076 // Drop our strong ref to the listener now that we're done with
1077 // everything. Note that this can cancel us and other fun things
1078 // like that. Don't add anything in this method after this point.
1079 imgINotificationObserver* obs = mListener;
1080 mListenerIsStrongRef = false;
1081 NS_RELEASE(obs);
1085 void imgRequestProxy::NullOutListener() {
1086 // If we have animation consumers, then they don't matter anymore
1087 if (mListener) {
1088 ClearAnimationConsumers();
1091 if (mListenerIsStrongRef) {
1092 // Releasing could do weird reentery stuff, so just play it super-safe
1093 nsCOMPtr<imgINotificationObserver> obs;
1094 obs.swap(mListener);
1095 mListenerIsStrongRef = false;
1096 } else {
1097 mListener = nullptr;
1101 NS_IMETHODIMP
1102 imgRequestProxy::GetStaticRequest(imgIRequest** aReturn) {
1103 RefPtr<imgRequestProxy> proxy =
1104 GetStaticRequest(static_cast<Document*>(nullptr));
1105 if (proxy != this) {
1106 RefPtr<Image> image = GetImage();
1107 if (image && image->HasError()) {
1108 // image/test/unit/test_async_notification_404.js needs this, but ideally
1109 // this special case can be removed from the scripted codepath.
1110 return NS_ERROR_FAILURE;
1113 proxy.forget(aReturn);
1114 return NS_OK;
1117 already_AddRefed<imgRequestProxy> imgRequestProxy::GetStaticRequest(
1118 Document* aLoadingDocument) {
1119 MOZ_DIAGNOSTIC_ASSERT(!aLoadingDocument ||
1120 aLoadingDocument->IsStaticDocument());
1121 RefPtr<Image> image = GetImage();
1123 bool animated;
1124 if (!image || (NS_SUCCEEDED(image->GetAnimated(&animated)) && !animated)) {
1125 // Early exit - we're not animated, so we don't have to do anything.
1126 return do_AddRef(this);
1129 // We are animated. We need to create a frozen version of this image.
1130 RefPtr<Image> frozenImage = ImageOps::Freeze(image);
1132 // Create a static imgRequestProxy with our new extracted frame.
1133 nsCOMPtr<nsIPrincipal> currentPrincipal;
1134 GetImagePrincipal(getter_AddRefs(currentPrincipal));
1135 bool hadCrossOriginRedirects = true;
1136 GetHadCrossOriginRedirects(&hadCrossOriginRedirects);
1137 nsCOMPtr<nsIPrincipal> triggeringPrincipal = GetTriggeringPrincipal();
1138 RefPtr<imgRequestProxy> req = new imgRequestProxyStatic(
1139 frozenImage, currentPrincipal, triggeringPrincipal, hadCrossOriginRedirects);
1140 req->Init(nullptr, nullptr, aLoadingDocument, mURI, nullptr);
1142 return req.forget();
1145 void imgRequestProxy::NotifyListener() {
1146 // It would be nice to notify the observer directly in the status tracker
1147 // instead of through the proxy, but there are several places we do extra
1148 // processing when we receive notifications (like OnStopRequest()), and we
1149 // need to check mCanceled everywhere too.
1151 RefPtr<ProgressTracker> progressTracker = GetProgressTracker();
1152 if (GetOwner()) {
1153 // Send the notifications to our listener asynchronously.
1154 progressTracker->Notify(this);
1155 } else {
1156 // We don't have an imgRequest, so we can only notify the clone of our
1157 // current state, but we still have to do that asynchronously.
1158 MOZ_ASSERT(HasImage(), "if we have no imgRequest, we should have an Image");
1159 progressTracker->NotifyCurrentState(this);
1163 void imgRequestProxy::SyncNotifyListener() {
1164 // It would be nice to notify the observer directly in the status tracker
1165 // instead of through the proxy, but there are several places we do extra
1166 // processing when we receive notifications (like OnStopRequest()), and we
1167 // need to check mCanceled everywhere too.
1169 RefPtr<ProgressTracker> progressTracker = GetProgressTracker();
1170 progressTracker->SyncNotify(this);
1173 void imgRequestProxy::SetHasImage() {
1174 RefPtr<ProgressTracker> progressTracker = GetProgressTracker();
1175 MOZ_ASSERT(progressTracker);
1176 RefPtr<Image> image = progressTracker->GetImage();
1177 MOZ_ASSERT(image);
1179 // Force any private status related to the owner to reflect
1180 // the presence of an image;
1181 mBehaviour->SetOwner(mBehaviour->GetOwner());
1183 // Apply any locks we have
1184 for (uint32_t i = 0; i < mLockCount; ++i) {
1185 image->LockImage();
1188 // Apply any animation consumers we have
1189 for (uint32_t i = 0; i < mAnimationConsumers; i++) {
1190 image->IncrementAnimationConsumers();
1194 already_AddRefed<ProgressTracker> imgRequestProxy::GetProgressTracker() const {
1195 return mBehaviour->GetProgressTracker();
1198 already_AddRefed<mozilla::image::Image> imgRequestProxy::GetImage() const {
1199 return mBehaviour->GetImage();
1202 bool RequestBehaviour::HasImage() const {
1203 if (!mOwnerHasImage) {
1204 return false;
1206 RefPtr<ProgressTracker> progressTracker = GetProgressTracker();
1207 return progressTracker ? progressTracker->HasImage() : false;
1210 bool imgRequestProxy::HasImage() const { return mBehaviour->HasImage(); }
1212 imgRequest* imgRequestProxy::GetOwner() const { return mBehaviour->GetOwner(); }
1214 imgCacheValidator* imgRequestProxy::GetValidator() const {
1215 imgRequest* owner = GetOwner();
1216 if (!owner) {
1217 return nullptr;
1219 return owner->GetValidator();
1222 nsITimedChannel* imgRequestProxy::TimedChannel() {
1223 if (!GetOwner()) {
1224 return nullptr;
1226 return GetOwner()->GetTimedChannel();
1229 ////////////////// imgRequestProxyStatic methods
1231 class StaticBehaviour : public ProxyBehaviour {
1232 public:
1233 explicit StaticBehaviour(mozilla::image::Image* aImage) : mImage(aImage) {}
1235 already_AddRefed<mozilla::image::Image> GetImage() const override {
1236 RefPtr<mozilla::image::Image> image = mImage;
1237 return image.forget();
1240 bool HasImage() const override { return mImage; }
1242 already_AddRefed<ProgressTracker> GetProgressTracker() const override {
1243 return mImage->GetProgressTracker();
1246 imgRequest* GetOwner() const override { return nullptr; }
1248 void SetOwner(imgRequest* aOwner) override {
1249 MOZ_ASSERT(!aOwner,
1250 "We shouldn't be giving static requests a non-null owner.");
1253 private:
1254 // Our image. We have to hold a strong reference here, because that's normally
1255 // the job of the underlying request.
1256 RefPtr<mozilla::image::Image> mImage;
1259 imgRequestProxyStatic::imgRequestProxyStatic(mozilla::image::Image* aImage,
1260 nsIPrincipal* aImagePrincipal,
1261 nsIPrincipal* aTriggeringPrincipal,
1262 bool aHadCrossOriginRedirects)
1263 : mImagePrincipal(aImagePrincipal),
1264 mTriggeringPrincipal(aTriggeringPrincipal),
1265 mHadCrossOriginRedirects(aHadCrossOriginRedirects) {
1266 mBehaviour = mozilla::MakeUnique<StaticBehaviour>(aImage);
1269 NS_IMETHODIMP
1270 imgRequestProxyStatic::GetImagePrincipal(nsIPrincipal** aPrincipal) {
1271 if (!mImagePrincipal) {
1272 return NS_ERROR_FAILURE;
1274 NS_ADDREF(*aPrincipal = mImagePrincipal);
1275 return NS_OK;
1278 NS_IMETHODIMP
1279 imgRequestProxyStatic::GetTriggeringPrincipal(nsIPrincipal** aPrincipal) {
1280 NS_IF_ADDREF(*aPrincipal = mTriggeringPrincipal);
1281 return NS_OK;
1284 NS_IMETHODIMP
1285 imgRequestProxyStatic::GetHadCrossOriginRedirects(
1286 bool* aHadCrossOriginRedirects) {
1287 *aHadCrossOriginRedirects = mHadCrossOriginRedirects;
1288 return NS_OK;
1291 imgRequestProxy* imgRequestProxyStatic::NewClonedProxy() {
1292 nsCOMPtr<nsIPrincipal> currentPrincipal;
1293 GetImagePrincipal(getter_AddRefs(currentPrincipal));
1294 nsCOMPtr<nsIPrincipal> triggeringPrincipal;
1295 GetTriggeringPrincipal(getter_AddRefs(triggeringPrincipal));
1296 bool hadCrossOriginRedirects = true;
1297 GetHadCrossOriginRedirects(&hadCrossOriginRedirects);
1298 RefPtr<mozilla::image::Image> image = GetImage();
1299 return new imgRequestProxyStatic(image, currentPrincipal, triggeringPrincipal,
1300 hadCrossOriginRedirects);