Backed out changeset 36e95068e103 (bug 1848160) for bc failures on browser_preference...
[gecko.git] / image / imgRequestProxy.cpp
blob797ae918a9ec3dfd54eff1b383fc4e35b5c4856a
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
3 * This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "imgRequestProxy.h"
9 #include <utility>
11 #include "Image.h"
12 #include "ImageLogging.h"
13 #include "ImageOps.h"
14 #include "ImageTypes.h"
15 #include "imgINotificationObserver.h"
16 #include "imgLoader.h"
17 #include "mozilla/dom/Document.h"
18 #include "mozilla/Telemetry.h" // for Telemetry
19 #include "mozilla/dom/DocGroup.h" // for DocGroup
20 #include "nsCRTGlue.h"
21 #include "nsError.h"
23 using namespace mozilla;
24 using namespace mozilla::image;
26 // The split of imgRequestProxy and imgRequestProxyStatic means that
27 // certain overridden functions need to be usable in the destructor.
28 // Since virtual functions can't be used in that way, this class
29 // provides a behavioural trait for each class to use instead.
30 class ProxyBehaviour {
31 public:
32 virtual ~ProxyBehaviour() = default;
34 virtual already_AddRefed<mozilla::image::Image> GetImage() const = 0;
35 virtual bool HasImage() const = 0;
36 virtual already_AddRefed<ProgressTracker> GetProgressTracker() const = 0;
37 virtual imgRequest* GetOwner() const = 0;
38 virtual void SetOwner(imgRequest* aOwner) = 0;
41 class RequestBehaviour : public ProxyBehaviour {
42 public:
43 RequestBehaviour() : mOwner(nullptr), mOwnerHasImage(false) {}
45 already_AddRefed<mozilla::image::Image> GetImage() const override;
46 bool HasImage() const override;
47 already_AddRefed<ProgressTracker> GetProgressTracker() const override;
49 imgRequest* GetOwner() const override { return mOwner; }
51 void SetOwner(imgRequest* aOwner) override {
52 mOwner = aOwner;
54 if (mOwner) {
55 RefPtr<ProgressTracker> ownerProgressTracker = GetProgressTracker();
56 mOwnerHasImage = ownerProgressTracker && ownerProgressTracker->HasImage();
57 } else {
58 mOwnerHasImage = false;
62 private:
63 // We maintain the following invariant:
64 // The proxy is registered at most with a single imgRequest as an observer,
65 // and whenever it is, mOwner points to that object. This helps ensure that
66 // imgRequestProxy::~imgRequestProxy unregisters the proxy as an observer
67 // from whatever request it was registered with (if any). This, in turn,
68 // means that imgRequest::mObservers will not have any stale pointers in it.
69 RefPtr<imgRequest> mOwner;
71 bool mOwnerHasImage;
74 already_AddRefed<mozilla::image::Image> RequestBehaviour::GetImage() const {
75 if (!mOwnerHasImage) {
76 return nullptr;
78 RefPtr<ProgressTracker> progressTracker = GetProgressTracker();
79 return progressTracker->GetImage();
82 already_AddRefed<ProgressTracker> RequestBehaviour::GetProgressTracker() const {
83 // NOTE: It's possible that our mOwner has an Image that it didn't notify
84 // us about, if we were Canceled before its Image was constructed.
85 // (Canceling removes us as an observer, so mOwner has no way to notify us).
86 // That's why this method uses mOwner->GetProgressTracker() instead of just
87 // mOwner->mProgressTracker -- we might have a null mImage and yet have an
88 // mOwner with a non-null mImage (and a null mProgressTracker pointer).
89 return mOwner->GetProgressTracker();
92 NS_IMPL_ADDREF(imgRequestProxy)
93 NS_IMPL_RELEASE(imgRequestProxy)
95 NS_INTERFACE_MAP_BEGIN(imgRequestProxy)
96 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, PreloaderBase)
97 NS_INTERFACE_MAP_ENTRY(imgIRequest)
98 NS_INTERFACE_MAP_ENTRY(nsIRequest)
99 NS_INTERFACE_MAP_ENTRY(nsISupportsPriority)
100 NS_INTERFACE_MAP_ENTRY_CONCRETE(imgRequestProxy)
101 NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsITimedChannel, TimedChannel() != nullptr)
102 NS_INTERFACE_MAP_END
104 imgRequestProxy::imgRequestProxy()
105 : mBehaviour(new RequestBehaviour),
106 mURI(nullptr),
107 mListener(nullptr),
108 mLoadFlags(nsIRequest::LOAD_NORMAL),
109 mLockCount(0),
110 mAnimationConsumers(0),
111 mCancelable(true),
112 mCanceled(false),
113 mIsInLoadGroup(false),
114 mForceDispatchLoadGroup(false),
115 mListenerIsStrongRef(false),
116 mDecodeRequested(false),
117 mPendingNotify(false),
118 mValidating(false),
119 mHadListener(false),
120 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 void imgRequestProxy::SetCancelable(bool aCancelable) {
466 MOZ_ASSERT(NS_IsMainThread());
467 mCancelable = aCancelable;
470 NS_IMETHODIMP
471 imgRequestProxy::Cancel(nsresult status) {
472 if (mCanceled) {
473 return NS_ERROR_FAILURE;
476 if (NS_WARN_IF(!mCancelable)) {
477 return NS_ERROR_FAILURE;
480 LOG_SCOPE(gImgLog, "imgRequestProxy::Cancel");
482 mCanceled = true;
484 nsCOMPtr<nsIRunnable> ev = new imgCancelRunnable(this, status);
485 return DispatchWithTargetIfAvailable(ev.forget());
488 void imgRequestProxy::DoCancel(nsresult status) {
489 RemoveFromOwner(status);
490 RemoveFromLoadGroup();
491 NullOutListener();
494 NS_IMETHODIMP
495 imgRequestProxy::CancelAndForgetObserver(nsresult aStatus) {
496 // If mCanceled is true but mListener is non-null, that means
497 // someone called Cancel() on us but the imgCancelRunnable is still
498 // pending. We still need to null out mListener before returning
499 // from this function in this case. That means we want to do the
500 // RemoveProxy call right now, because we need to deliver the
501 // onStopRequest.
502 if (mCanceled && !mListener) {
503 return NS_ERROR_FAILURE;
506 if (NS_WARN_IF(!mCancelable)) {
507 MOZ_ASSERT(mCancelable,
508 "Shouldn't try to cancel non-cancelable requests via "
509 "CancelAndForgetObserver");
510 return NS_ERROR_FAILURE;
513 LOG_SCOPE(gImgLog, "imgRequestProxy::CancelAndForgetObserver");
515 mCanceled = true;
516 mForceDispatchLoadGroup = true;
517 RemoveFromOwner(aStatus);
518 RemoveFromLoadGroup();
519 mForceDispatchLoadGroup = false;
521 NullOutListener();
523 return NS_OK;
526 NS_IMETHODIMP
527 imgRequestProxy::StartDecoding(uint32_t aFlags) {
528 // Flag this, so we know to request after validation if pending.
529 if (IsValidating()) {
530 mDecodeRequested = true;
531 return NS_OK;
534 RefPtr<Image> image = GetImage();
535 if (image) {
536 return image->StartDecoding(aFlags);
539 if (GetOwner()) {
540 GetOwner()->StartDecoding();
543 return NS_OK;
546 bool imgRequestProxy::StartDecodingWithResult(uint32_t aFlags) {
547 // Flag this, so we know to request after validation if pending.
548 if (IsValidating()) {
549 mDecodeRequested = true;
550 return false;
553 RefPtr<Image> image = GetImage();
554 if (image) {
555 return image->StartDecodingWithResult(aFlags);
558 if (GetOwner()) {
559 GetOwner()->StartDecoding();
562 return false;
565 imgIContainer::DecodeResult imgRequestProxy::RequestDecodeWithResult(
566 uint32_t aFlags) {
567 if (IsValidating()) {
568 mDecodeRequested = true;
569 return imgIContainer::DECODE_REQUESTED;
572 RefPtr<Image> image = GetImage();
573 if (image) {
574 return image->RequestDecodeWithResult(aFlags);
577 if (GetOwner()) {
578 GetOwner()->StartDecoding();
581 return imgIContainer::DECODE_REQUESTED;
584 NS_IMETHODIMP
585 imgRequestProxy::LockImage() {
586 mLockCount++;
587 RefPtr<Image> image =
588 GetOwner() && GetOwner()->ImageAvailable() ? GetImage() : nullptr;
589 if (image) {
590 return image->LockImage();
592 return NS_OK;
595 NS_IMETHODIMP
596 imgRequestProxy::UnlockImage() {
597 MOZ_ASSERT(mLockCount > 0, "calling unlock but no locks!");
599 mLockCount--;
600 RefPtr<Image> image =
601 GetOwner() && GetOwner()->ImageAvailable() ? GetImage() : nullptr;
602 if (image) {
603 return image->UnlockImage();
605 return NS_OK;
608 NS_IMETHODIMP
609 imgRequestProxy::RequestDiscard() {
610 RefPtr<Image> image = GetImage();
611 if (image) {
612 return image->RequestDiscard();
614 return NS_OK;
617 NS_IMETHODIMP
618 imgRequestProxy::IncrementAnimationConsumers() {
619 mAnimationConsumers++;
620 RefPtr<Image> image =
621 GetOwner() && GetOwner()->ImageAvailable() ? GetImage() : nullptr;
622 if (image) {
623 image->IncrementAnimationConsumers();
625 return NS_OK;
628 NS_IMETHODIMP
629 imgRequestProxy::DecrementAnimationConsumers() {
630 // We may get here if some responsible code called Increment,
631 // then called us, but we have meanwhile called ClearAnimationConsumers
632 // because we needed to get rid of them earlier (see
633 // imgRequest::RemoveProxy), and hence have nothing left to
634 // decrement. (In such a case we got rid of the animation consumers
635 // early, but not the observer.)
636 if (mAnimationConsumers > 0) {
637 mAnimationConsumers--;
638 RefPtr<Image> image =
639 GetOwner() && GetOwner()->ImageAvailable() ? GetImage() : nullptr;
640 if (image) {
641 image->DecrementAnimationConsumers();
644 return NS_OK;
647 void imgRequestProxy::ClearAnimationConsumers() {
648 while (mAnimationConsumers > 0) {
649 DecrementAnimationConsumers();
653 NS_IMETHODIMP
654 imgRequestProxy::Suspend() { return NS_ERROR_NOT_IMPLEMENTED; }
656 NS_IMETHODIMP
657 imgRequestProxy::Resume() { return NS_ERROR_NOT_IMPLEMENTED; }
659 NS_IMETHODIMP
660 imgRequestProxy::GetLoadGroup(nsILoadGroup** loadGroup) {
661 NS_IF_ADDREF(*loadGroup = mLoadGroup.get());
662 return NS_OK;
664 NS_IMETHODIMP
665 imgRequestProxy::SetLoadGroup(nsILoadGroup* loadGroup) {
666 if (loadGroup != mLoadGroup) {
667 MOZ_ASSERT_UNREACHABLE("Switching load groups is unsupported!");
668 return NS_ERROR_NOT_IMPLEMENTED;
670 return NS_OK;
673 NS_IMETHODIMP
674 imgRequestProxy::GetLoadFlags(nsLoadFlags* flags) {
675 *flags = mLoadFlags;
676 return NS_OK;
678 NS_IMETHODIMP
679 imgRequestProxy::SetLoadFlags(nsLoadFlags flags) {
680 mLoadFlags = flags;
681 return NS_OK;
684 NS_IMETHODIMP
685 imgRequestProxy::GetTRRMode(nsIRequest::TRRMode* aTRRMode) {
686 return GetTRRModeImpl(aTRRMode);
689 NS_IMETHODIMP
690 imgRequestProxy::SetTRRMode(nsIRequest::TRRMode aTRRMode) {
691 return SetTRRModeImpl(aTRRMode);
694 /** imgIRequest methods **/
696 NS_IMETHODIMP
697 imgRequestProxy::GetImage(imgIContainer** aImage) {
698 NS_ENSURE_TRUE(aImage, NS_ERROR_NULL_POINTER);
699 // It's possible that our owner has an image but hasn't notified us of it -
700 // that'll happen if we get Canceled before the owner instantiates its image
701 // (because Canceling unregisters us as a listener on mOwner). If we're
702 // in that situation, just grab the image off of mOwner.
703 RefPtr<Image> image = GetImage();
704 nsCOMPtr<imgIContainer> imageToReturn;
705 if (image) {
706 imageToReturn = image;
708 if (!imageToReturn && GetOwner()) {
709 imageToReturn = GetOwner()->GetImage();
711 if (!imageToReturn) {
712 return NS_ERROR_FAILURE;
715 imageToReturn.swap(*aImage);
717 return NS_OK;
720 NS_IMETHODIMP
721 imgRequestProxy::GetProviderId(uint32_t* aId) {
722 NS_ENSURE_TRUE(aId, NS_ERROR_NULL_POINTER);
724 nsCOMPtr<imgIContainer> image;
725 nsresult rv = GetImage(getter_AddRefs(image));
726 if (NS_SUCCEEDED(rv)) {
727 *aId = image->GetProviderId();
728 } else {
729 *aId = 0;
732 return NS_OK;
735 NS_IMETHODIMP
736 imgRequestProxy::GetImageStatus(uint32_t* aStatus) {
737 if (IsValidating()) {
738 // We are currently validating the image, and so our status could revert if
739 // we discard the cache. We should also be deferring notifications, such
740 // that the caller will be notified when validation completes. Rather than
741 // risk misleading the caller, return nothing.
742 *aStatus = imgIRequest::STATUS_NONE;
743 } else {
744 RefPtr<ProgressTracker> progressTracker = GetProgressTracker();
745 *aStatus = progressTracker->GetImageStatus();
748 return NS_OK;
751 NS_IMETHODIMP
752 imgRequestProxy::GetImageErrorCode(nsresult* aStatus) {
753 if (!GetOwner()) {
754 return NS_ERROR_FAILURE;
757 *aStatus = GetOwner()->GetImageErrorCode();
759 return NS_OK;
762 NS_IMETHODIMP
763 imgRequestProxy::GetURI(nsIURI** aURI) {
764 MOZ_ASSERT(NS_IsMainThread(), "Must be on main thread to convert URI");
765 nsCOMPtr<nsIURI> uri = mURI;
766 uri.forget(aURI);
767 return NS_OK;
770 nsresult imgRequestProxy::GetFinalURI(nsIURI** aURI) {
771 if (!GetOwner()) {
772 return NS_ERROR_FAILURE;
775 return GetOwner()->GetFinalURI(aURI);
778 NS_IMETHODIMP
779 imgRequestProxy::GetNotificationObserver(imgINotificationObserver** aObserver) {
780 *aObserver = mListener;
781 NS_IF_ADDREF(*aObserver);
782 return NS_OK;
785 NS_IMETHODIMP
786 imgRequestProxy::GetMimeType(char** aMimeType) {
787 if (!GetOwner()) {
788 return NS_ERROR_FAILURE;
791 const char* type = GetOwner()->GetMimeType();
792 if (!type) {
793 return NS_ERROR_FAILURE;
796 *aMimeType = NS_xstrdup(type);
798 return NS_OK;
801 NS_IMETHODIMP
802 imgRequestProxy::GetFileName(nsACString& aFileName) {
803 if (!GetOwner()) {
804 return NS_ERROR_FAILURE;
807 GetOwner()->GetFileName(aFileName);
808 return NS_OK;
811 imgRequestProxy* imgRequestProxy::NewClonedProxy() {
812 return new imgRequestProxy();
815 NS_IMETHODIMP
816 imgRequestProxy::Clone(imgINotificationObserver* aObserver,
817 imgIRequest** aClone) {
818 nsresult result;
819 imgRequestProxy* proxy;
820 result = PerformClone(aObserver, nullptr, /* aSyncNotify */ true, &proxy);
821 *aClone = proxy;
822 return result;
825 nsresult imgRequestProxy::SyncClone(imgINotificationObserver* aObserver,
826 Document* aLoadingDocument,
827 imgRequestProxy** aClone) {
828 return PerformClone(aObserver, aLoadingDocument,
829 /* aSyncNotify */ true, aClone);
832 nsresult imgRequestProxy::Clone(imgINotificationObserver* aObserver,
833 Document* aLoadingDocument,
834 imgRequestProxy** aClone) {
835 return PerformClone(aObserver, aLoadingDocument,
836 /* aSyncNotify */ false, aClone);
839 nsresult imgRequestProxy::PerformClone(imgINotificationObserver* aObserver,
840 Document* aLoadingDocument,
841 bool aSyncNotify,
842 imgRequestProxy** aClone) {
843 MOZ_ASSERT(aClone, "Null out param");
845 LOG_SCOPE(gImgLog, "imgRequestProxy::Clone");
847 *aClone = nullptr;
848 RefPtr<imgRequestProxy> clone = NewClonedProxy();
850 nsCOMPtr<nsILoadGroup> loadGroup;
851 if (aLoadingDocument) {
852 loadGroup = aLoadingDocument->GetDocumentLoadGroup();
855 // It is important to call |SetLoadFlags()| before calling |Init()| because
856 // |Init()| adds the request to the loadgroup.
857 // When a request is added to a loadgroup, its load flags are merged
858 // with the load flags of the loadgroup.
859 // XXXldb That's not true anymore. Stuff from imgLoader adds the
860 // request to the loadgroup.
861 clone->SetLoadFlags(mLoadFlags);
862 nsresult rv = clone->Init(mBehaviour->GetOwner(), loadGroup, aLoadingDocument,
863 mURI, aObserver);
864 if (NS_FAILED(rv)) {
865 return rv;
868 // Assign to *aClone before calling Notify so that if the caller expects to
869 // only be notified for requests it's already holding pointers to it won't be
870 // surprised.
871 NS_ADDREF(*aClone = clone);
873 imgCacheValidator* validator = GetValidator();
874 if (validator) {
875 // Note that if we have a validator, we don't want to issue notifications at
876 // here because we want to defer until that completes. AddProxy will add us
877 // to the load group; we cannot avoid that in this case, because we don't
878 // know when the validation will complete, and if it will cause us to
879 // discard our cached state anyways. We are probably already blocked by the
880 // original LoadImage(WithChannel) request in any event.
881 clone->MarkValidating();
882 validator->AddProxy(clone);
883 } else {
884 // We only want to add the request to the load group of the owning document
885 // if it is still in progress. Some callers cannot handle a supurious load
886 // group removal (e.g. print preview) so we must be careful. On the other
887 // hand, if after cloning, the original request proxy is cancelled /
888 // destroyed, we need to ensure that any clones still block the load group
889 // if it is incomplete.
890 bool addToLoadGroup = mIsInLoadGroup;
891 if (!addToLoadGroup) {
892 RefPtr<ProgressTracker> tracker = clone->GetProgressTracker();
893 addToLoadGroup =
894 tracker && !(tracker->GetProgress() & FLAG_LOAD_COMPLETE);
897 if (addToLoadGroup) {
898 clone->AddToLoadGroup();
901 if (aSyncNotify) {
902 // This is wrong!!! We need to notify asynchronously, but there's code
903 // that assumes that we don't. This will be fixed in bug 580466. Note that
904 // if we have a validator, we won't issue notifications anyways because
905 // they are deferred, so there is no point in requesting.
906 clone->mForceDispatchLoadGroup = true;
907 clone->SyncNotifyListener();
908 clone->mForceDispatchLoadGroup = false;
909 } else {
910 // Without a validator, we can request asynchronous notifications
911 // immediately. If there was a validator, this would override the deferral
912 // and that would be incorrect.
913 clone->NotifyListener();
917 return NS_OK;
920 NS_IMETHODIMP
921 imgRequestProxy::GetImagePrincipal(nsIPrincipal** aPrincipal) {
922 if (!GetOwner()) {
923 return NS_ERROR_FAILURE;
926 nsCOMPtr<nsIPrincipal> principal = GetOwner()->GetPrincipal();
927 principal.forget(aPrincipal);
928 return NS_OK;
931 NS_IMETHODIMP
932 imgRequestProxy::GetHadCrossOriginRedirects(bool* aHadCrossOriginRedirects) {
933 *aHadCrossOriginRedirects = false;
935 nsCOMPtr<nsITimedChannel> timedChannel = TimedChannel();
936 if (timedChannel) {
937 bool allRedirectsSameOrigin = false;
938 *aHadCrossOriginRedirects =
939 NS_SUCCEEDED(
940 timedChannel->GetAllRedirectsSameOrigin(&allRedirectsSameOrigin)) &&
941 !allRedirectsSameOrigin;
944 return NS_OK;
947 NS_IMETHODIMP
948 imgRequestProxy::GetMultipart(bool* aMultipart) {
949 if (!GetOwner()) {
950 return NS_ERROR_FAILURE;
953 *aMultipart = GetOwner()->GetMultipart();
954 return NS_OK;
957 NS_IMETHODIMP
958 imgRequestProxy::GetCORSMode(int32_t* aCorsMode) {
959 if (!GetOwner()) {
960 return NS_ERROR_FAILURE;
963 *aCorsMode = GetOwner()->GetCORSMode();
964 return NS_OK;
967 NS_IMETHODIMP
968 imgRequestProxy::GetReferrerInfo(nsIReferrerInfo** aReferrerInfo) {
969 if (!GetOwner()) {
970 return NS_ERROR_FAILURE;
973 nsCOMPtr<nsIReferrerInfo> referrerInfo = GetOwner()->GetReferrerInfo();
974 referrerInfo.forget(aReferrerInfo);
975 return NS_OK;
978 NS_IMETHODIMP
979 imgRequestProxy::BoostPriority(uint32_t aCategory) {
980 NS_ENSURE_STATE(GetOwner() && !mCanceled);
981 GetOwner()->BoostPriority(aCategory);
982 return NS_OK;
985 /** nsISupportsPriority methods **/
987 NS_IMETHODIMP
988 imgRequestProxy::GetPriority(int32_t* priority) {
989 NS_ENSURE_STATE(GetOwner());
990 *priority = GetOwner()->Priority();
991 return NS_OK;
994 NS_IMETHODIMP
995 imgRequestProxy::SetPriority(int32_t priority) {
996 NS_ENSURE_STATE(GetOwner() && !mCanceled);
997 GetOwner()->AdjustPriority(this, priority - GetOwner()->Priority());
998 return NS_OK;
1001 NS_IMETHODIMP
1002 imgRequestProxy::AdjustPriority(int32_t priority) {
1003 // We don't require |!mCanceled| here. This may be called even if we're
1004 // cancelled, because it's invoked as part of the process of removing an image
1005 // from the load group.
1006 NS_ENSURE_STATE(GetOwner());
1007 GetOwner()->AdjustPriority(this, priority);
1008 return NS_OK;
1011 static const char* NotificationTypeToString(int32_t aType) {
1012 switch (aType) {
1013 case imgINotificationObserver::SIZE_AVAILABLE:
1014 return "SIZE_AVAILABLE";
1015 case imgINotificationObserver::FRAME_UPDATE:
1016 return "FRAME_UPDATE";
1017 case imgINotificationObserver::FRAME_COMPLETE:
1018 return "FRAME_COMPLETE";
1019 case imgINotificationObserver::LOAD_COMPLETE:
1020 return "LOAD_COMPLETE";
1021 case imgINotificationObserver::DECODE_COMPLETE:
1022 return "DECODE_COMPLETE";
1023 case imgINotificationObserver::DISCARD:
1024 return "DISCARD";
1025 case imgINotificationObserver::UNLOCKED_DRAW:
1026 return "UNLOCKED_DRAW";
1027 case imgINotificationObserver::IS_ANIMATED:
1028 return "IS_ANIMATED";
1029 case imgINotificationObserver::HAS_TRANSPARENCY:
1030 return "HAS_TRANSPARENCY";
1031 default:
1032 MOZ_ASSERT_UNREACHABLE("Notification list should be exhaustive");
1033 return "(unknown notification)";
1037 void imgRequestProxy::Notify(int32_t aType,
1038 const mozilla::gfx::IntRect* aRect) {
1039 MOZ_ASSERT(aType != imgINotificationObserver::LOAD_COMPLETE,
1040 "Should call OnLoadComplete");
1042 LOG_FUNC_WITH_PARAM(gImgLog, "imgRequestProxy::Notify", "type",
1043 NotificationTypeToString(aType));
1045 if (!mListener || mCanceled) {
1046 return;
1049 // Make sure the listener stays alive while we notify.
1050 nsCOMPtr<imgINotificationObserver> listener(mListener);
1052 listener->Notify(this, aType, aRect);
1055 void imgRequestProxy::OnLoadComplete(bool aLastPart) {
1056 LOG_FUNC_WITH_PARAM(gImgLog, "imgRequestProxy::OnLoadComplete", "uri", mURI);
1058 // There's all sorts of stuff here that could kill us (the OnStopRequest call
1059 // on the listener, the removal from the loadgroup, the release of the
1060 // listener, etc). Don't let them do it.
1061 RefPtr<imgRequestProxy> self(this);
1063 if (mListener && !mCanceled) {
1064 // Hold a ref to the listener while we call it, just in case.
1065 nsCOMPtr<imgINotificationObserver> listener(mListener);
1066 listener->Notify(this, imgINotificationObserver::LOAD_COMPLETE, nullptr);
1069 // If we're expecting more data from a multipart channel, re-add ourself
1070 // to the loadgroup so that the document doesn't lose track of the load.
1071 // If the request is already a background request and there's more data
1072 // coming, we can just leave the request in the loadgroup as-is.
1073 if (aLastPart || (mLoadFlags & nsIRequest::LOAD_BACKGROUND) == 0) {
1074 if (aLastPart) {
1075 RemoveFromLoadGroup();
1077 nsresult errorCode = NS_OK;
1078 // if the load is cross origin without CORS, or the CORS access is
1079 // rejected, always fire load event to avoid leaking site information for
1080 // <link rel=preload>.
1081 // XXXedgar, currently we don't do the same thing for <img>.
1082 imgRequest* request = GetOwner();
1083 if (!request || !(request->IsDeniedCrossSiteCORSRequest() ||
1084 request->IsCrossSiteNoCORSRequest())) {
1085 uint32_t status = imgIRequest::STATUS_NONE;
1086 GetImageStatus(&status);
1087 if (status & imgIRequest::STATUS_ERROR) {
1088 errorCode = NS_ERROR_FAILURE;
1091 NotifyStop(errorCode);
1092 } else {
1093 // More data is coming, so change the request to be a background request
1094 // and put it back in the loadgroup.
1095 MoveToBackgroundInLoadGroup();
1099 if (mListenerIsStrongRef && aLastPart) {
1100 MOZ_ASSERT(mListener, "How did that happen?");
1101 // Drop our strong ref to the listener now that we're done with
1102 // everything. Note that this can cancel us and other fun things
1103 // like that. Don't add anything in this method after this point.
1104 imgINotificationObserver* obs = mListener;
1105 mListenerIsStrongRef = false;
1106 NS_RELEASE(obs);
1110 void imgRequestProxy::NullOutListener() {
1111 // If we have animation consumers, then they don't matter anymore
1112 if (mListener) {
1113 ClearAnimationConsumers();
1116 if (mListenerIsStrongRef) {
1117 // Releasing could do weird reentery stuff, so just play it super-safe
1118 nsCOMPtr<imgINotificationObserver> obs;
1119 obs.swap(mListener);
1120 mListenerIsStrongRef = false;
1121 } else {
1122 mListener = nullptr;
1126 NS_IMETHODIMP
1127 imgRequestProxy::GetStaticRequest(imgIRequest** aReturn) {
1128 RefPtr<imgRequestProxy> proxy =
1129 GetStaticRequest(static_cast<Document*>(nullptr));
1130 if (proxy != this) {
1131 RefPtr<Image> image = GetImage();
1132 if (image && image->HasError()) {
1133 // image/test/unit/test_async_notification_404.js needs this, but ideally
1134 // this special case can be removed from the scripted codepath.
1135 return NS_ERROR_FAILURE;
1138 proxy.forget(aReturn);
1139 return NS_OK;
1142 already_AddRefed<imgRequestProxy> imgRequestProxy::GetStaticRequest(
1143 Document* aLoadingDocument) {
1144 MOZ_DIAGNOSTIC_ASSERT(!aLoadingDocument ||
1145 aLoadingDocument->IsStaticDocument());
1146 RefPtr<Image> image = GetImage();
1148 bool animated;
1149 if (!image || (NS_SUCCEEDED(image->GetAnimated(&animated)) && !animated)) {
1150 // Early exit - we're not animated, so we don't have to do anything.
1151 return do_AddRef(this);
1154 // We are animated. We need to create a frozen version of this image.
1155 RefPtr<Image> frozenImage = ImageOps::Freeze(image);
1157 // Create a static imgRequestProxy with our new extracted frame.
1158 nsCOMPtr<nsIPrincipal> currentPrincipal;
1159 GetImagePrincipal(getter_AddRefs(currentPrincipal));
1160 bool hadCrossOriginRedirects = true;
1161 GetHadCrossOriginRedirects(&hadCrossOriginRedirects);
1162 nsCOMPtr<nsIPrincipal> triggeringPrincipal = GetTriggeringPrincipal();
1163 RefPtr<imgRequestProxy> req =
1164 new imgRequestProxyStatic(frozenImage, currentPrincipal,
1165 triggeringPrincipal, hadCrossOriginRedirects);
1166 req->Init(nullptr, nullptr, aLoadingDocument, mURI, nullptr);
1168 return req.forget();
1171 void imgRequestProxy::NotifyListener() {
1172 // It would be nice to notify the observer directly in the status tracker
1173 // instead of through the proxy, but there are several places we do extra
1174 // processing when we receive notifications (like OnStopRequest()), and we
1175 // need to check mCanceled everywhere too.
1177 RefPtr<ProgressTracker> progressTracker = GetProgressTracker();
1178 if (GetOwner()) {
1179 // Send the notifications to our listener asynchronously.
1180 progressTracker->Notify(this);
1181 } else {
1182 // We don't have an imgRequest, so we can only notify the clone of our
1183 // current state, but we still have to do that asynchronously.
1184 MOZ_ASSERT(HasImage(), "if we have no imgRequest, we should have an Image");
1185 progressTracker->NotifyCurrentState(this);
1189 void imgRequestProxy::SyncNotifyListener() {
1190 // It would be nice to notify the observer directly in the status tracker
1191 // instead of through the proxy, but there are several places we do extra
1192 // processing when we receive notifications (like OnStopRequest()), and we
1193 // need to check mCanceled everywhere too.
1195 RefPtr<ProgressTracker> progressTracker = GetProgressTracker();
1196 progressTracker->SyncNotify(this);
1199 void imgRequestProxy::SetHasImage() {
1200 RefPtr<ProgressTracker> progressTracker = GetProgressTracker();
1201 MOZ_ASSERT(progressTracker);
1202 RefPtr<Image> image = progressTracker->GetImage();
1203 MOZ_ASSERT(image);
1205 // Force any private status related to the owner to reflect
1206 // the presence of an image;
1207 mBehaviour->SetOwner(mBehaviour->GetOwner());
1209 // Apply any locks we have
1210 for (uint32_t i = 0; i < mLockCount; ++i) {
1211 image->LockImage();
1214 // Apply any animation consumers we have
1215 for (uint32_t i = 0; i < mAnimationConsumers; i++) {
1216 image->IncrementAnimationConsumers();
1220 already_AddRefed<ProgressTracker> imgRequestProxy::GetProgressTracker() const {
1221 return mBehaviour->GetProgressTracker();
1224 already_AddRefed<mozilla::image::Image> imgRequestProxy::GetImage() const {
1225 return mBehaviour->GetImage();
1228 bool RequestBehaviour::HasImage() const {
1229 if (!mOwnerHasImage) {
1230 return false;
1232 RefPtr<ProgressTracker> progressTracker = GetProgressTracker();
1233 return progressTracker ? progressTracker->HasImage() : false;
1236 bool imgRequestProxy::HasImage() const { return mBehaviour->HasImage(); }
1238 imgRequest* imgRequestProxy::GetOwner() const { return mBehaviour->GetOwner(); }
1240 imgCacheValidator* imgRequestProxy::GetValidator() const {
1241 imgRequest* owner = GetOwner();
1242 if (!owner) {
1243 return nullptr;
1245 return owner->GetValidator();
1248 nsITimedChannel* imgRequestProxy::TimedChannel() {
1249 if (!GetOwner()) {
1250 return nullptr;
1252 return GetOwner()->GetTimedChannel();
1255 ////////////////// imgRequestProxyStatic methods
1257 class StaticBehaviour : public ProxyBehaviour {
1258 public:
1259 explicit StaticBehaviour(mozilla::image::Image* aImage) : mImage(aImage) {}
1261 already_AddRefed<mozilla::image::Image> GetImage() const override {
1262 RefPtr<mozilla::image::Image> image = mImage;
1263 return image.forget();
1266 bool HasImage() const override { return mImage; }
1268 already_AddRefed<ProgressTracker> GetProgressTracker() const override {
1269 return mImage->GetProgressTracker();
1272 imgRequest* GetOwner() const override { return nullptr; }
1274 void SetOwner(imgRequest* aOwner) override {
1275 MOZ_ASSERT(!aOwner,
1276 "We shouldn't be giving static requests a non-null owner.");
1279 private:
1280 // Our image. We have to hold a strong reference here, because that's normally
1281 // the job of the underlying request.
1282 RefPtr<mozilla::image::Image> mImage;
1285 imgRequestProxyStatic::imgRequestProxyStatic(mozilla::image::Image* aImage,
1286 nsIPrincipal* aImagePrincipal,
1287 nsIPrincipal* aTriggeringPrincipal,
1288 bool aHadCrossOriginRedirects)
1289 : mImagePrincipal(aImagePrincipal),
1290 mTriggeringPrincipal(aTriggeringPrincipal),
1291 mHadCrossOriginRedirects(aHadCrossOriginRedirects) {
1292 mBehaviour = mozilla::MakeUnique<StaticBehaviour>(aImage);
1295 NS_IMETHODIMP
1296 imgRequestProxyStatic::GetImagePrincipal(nsIPrincipal** aPrincipal) {
1297 if (!mImagePrincipal) {
1298 return NS_ERROR_FAILURE;
1300 NS_ADDREF(*aPrincipal = mImagePrincipal);
1301 return NS_OK;
1304 NS_IMETHODIMP
1305 imgRequestProxyStatic::GetTriggeringPrincipal(nsIPrincipal** aPrincipal) {
1306 NS_IF_ADDREF(*aPrincipal = mTriggeringPrincipal);
1307 return NS_OK;
1310 NS_IMETHODIMP
1311 imgRequestProxyStatic::GetHadCrossOriginRedirects(
1312 bool* aHadCrossOriginRedirects) {
1313 *aHadCrossOriginRedirects = mHadCrossOriginRedirects;
1314 return NS_OK;
1317 imgRequestProxy* imgRequestProxyStatic::NewClonedProxy() {
1318 nsCOMPtr<nsIPrincipal> currentPrincipal;
1319 GetImagePrincipal(getter_AddRefs(currentPrincipal));
1320 nsCOMPtr<nsIPrincipal> triggeringPrincipal;
1321 GetTriggeringPrincipal(getter_AddRefs(triggeringPrincipal));
1322 bool hadCrossOriginRedirects = true;
1323 GetHadCrossOriginRedirects(&hadCrossOriginRedirects);
1324 RefPtr<mozilla::image::Image> image = GetImage();
1325 return new imgRequestProxyStatic(image, currentPrincipal, triggeringPrincipal,
1326 hadCrossOriginRedirects);