Bug 1608150 [wpt PR 21112] - Add missing space in `./wpt lint` command line docs...
[gecko.git] / image / imgRequestProxy.cpp
blobe0b60822cfe23facb478cd5878851597dc14b0ed
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 "ImageLogging.h"
10 #include "imgLoader.h"
11 #include "Image.h"
12 #include "ImageOps.h"
13 #include "ImageTypes.h"
14 #include "nsError.h"
15 #include "nsCRTGlue.h"
16 #include "imgINotificationObserver.h"
17 #include "mozilla/dom/TabGroup.h" // for TabGroup
18 #include "mozilla/dom/DocGroup.h" // for DocGroup
19 #include "mozilla/Move.h"
20 #include "mozilla/Telemetry.h" // for Telemetry
22 using namespace mozilla;
23 using namespace mozilla::image;
24 using mozilla::dom::Document;
26 // The split of imgRequestProxy and imgRequestProxyStatic means that
27 // certain overridden functions need to be usable in the destructor.
28 // Since virtual functions can't be used in that way, this class
29 // provides a behavioural trait for each class to use instead.
30 class ProxyBehaviour {
31 public:
32 virtual ~ProxyBehaviour() = default;
34 virtual already_AddRefed<mozilla::image::Image> GetImage() const = 0;
35 virtual bool HasImage() const = 0;
36 virtual already_AddRefed<ProgressTracker> GetProgressTracker() const = 0;
37 virtual imgRequest* GetOwner() const = 0;
38 virtual void SetOwner(imgRequest* aOwner) = 0;
41 class RequestBehaviour : public ProxyBehaviour {
42 public:
43 RequestBehaviour() : mOwner(nullptr), mOwnerHasImage(false) {}
45 already_AddRefed<mozilla::image::Image> GetImage() const override;
46 bool HasImage() const override;
47 already_AddRefed<ProgressTracker> GetProgressTracker() const override;
49 imgRequest* GetOwner() const override { return mOwner; }
51 void SetOwner(imgRequest* aOwner) override {
52 mOwner = aOwner;
54 if (mOwner) {
55 RefPtr<ProgressTracker> ownerProgressTracker = GetProgressTracker();
56 mOwnerHasImage = ownerProgressTracker && ownerProgressTracker->HasImage();
57 } else {
58 mOwnerHasImage = false;
62 private:
63 // We maintain the following invariant:
64 // The proxy is registered at most with a single imgRequest as an observer,
65 // and whenever it is, mOwner points to that object. This helps ensure that
66 // imgRequestProxy::~imgRequestProxy unregisters the proxy as an observer
67 // from whatever request it was registered with (if any). This, in turn,
68 // means that imgRequest::mObservers will not have any stale pointers in it.
69 RefPtr<imgRequest> mOwner;
71 bool mOwnerHasImage;
74 already_AddRefed<mozilla::image::Image> RequestBehaviour::GetImage() const {
75 if (!mOwnerHasImage) {
76 return nullptr;
78 RefPtr<ProgressTracker> progressTracker = GetProgressTracker();
79 return progressTracker->GetImage();
82 already_AddRefed<ProgressTracker> RequestBehaviour::GetProgressTracker() const {
83 // NOTE: It's possible that our mOwner has an Image that it didn't notify
84 // us about, if we were Canceled before its Image was constructed.
85 // (Canceling removes us as an observer, so mOwner has no way to notify us).
86 // That's why this method uses mOwner->GetProgressTracker() instead of just
87 // mOwner->mProgressTracker -- we might have a null mImage and yet have an
88 // mOwner with a non-null mImage (and a null mProgressTracker pointer).
89 return mOwner->GetProgressTracker();
92 NS_IMPL_ADDREF(imgRequestProxy)
93 NS_IMPL_RELEASE(imgRequestProxy)
95 NS_INTERFACE_MAP_BEGIN(imgRequestProxy)
96 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, imgIRequest)
97 NS_INTERFACE_MAP_ENTRY(imgIRequest)
98 NS_INTERFACE_MAP_ENTRY(nsIRequest)
99 NS_INTERFACE_MAP_ENTRY(nsISupportsPriority)
100 NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsITimedChannel, TimedChannel() != nullptr)
101 NS_INTERFACE_MAP_END
103 imgRequestProxy::imgRequestProxy()
104 : mBehaviour(new RequestBehaviour),
105 mURI(nullptr),
106 mListener(nullptr),
107 mLoadFlags(nsIRequest::LOAD_NORMAL),
108 mLockCount(0),
109 mAnimationConsumers(0),
110 mCanceled(false),
111 mIsInLoadGroup(false),
112 mForceDispatchLoadGroup(false),
113 mListenerIsStrongRef(false),
114 mDecodeRequested(false),
115 mPendingNotify(false),
116 mValidating(false),
117 mHadListener(false),
118 mHadDispatch(false) {
119 /* member initializers and constructor code */
120 LOG_FUNC(gImgLog, "imgRequestProxy::imgRequestProxy");
123 imgRequestProxy::~imgRequestProxy() {
124 /* destructor code */
125 MOZ_ASSERT(!mListener, "Someone forgot to properly cancel this request!");
127 // If we had a listener, that means we would have issued notifications. With
128 // bug 1359833, we added support for main thread scheduler groups. Each
129 // imgRequestProxy may have its own associated listener, document and/or
130 // scheduler group. Typically most imgRequestProxy belong to the same
131 // document, or have no listener, which means we will want to execute all main
132 // thread code in that shared scheduler group. Less frequently, there may be
133 // multiple imgRequests and they have separate documents, which means that
134 // when we issue state notifications, some or all need to be dispatched to the
135 // appropriate scheduler group for each request. This should be rare, so we
136 // want to monitor the frequency of dispatching in the wild.
137 if (mHadListener) {
138 mozilla::Telemetry::Accumulate(mozilla::Telemetry::IMAGE_REQUEST_DISPATCHED,
139 mHadDispatch);
142 MOZ_RELEASE_ASSERT(!mLockCount, "Someone forgot to unlock on time?");
144 ClearAnimationConsumers();
146 // Explicitly set mListener to null to ensure that the RemoveProxy
147 // call below can't send |this| to an arbitrary listener while |this|
148 // is being destroyed. This is all belt-and-suspenders in view of the
149 // above assert.
150 NullOutListener();
152 /* Call RemoveProxy with a successful status. This will keep the
153 channel, if still downloading data, from being canceled if 'this' is
154 the last observer. This allows the image to continue to download and
155 be cached even if no one is using it currently.
157 mCanceled = true;
158 RemoveFromOwner(NS_OK);
160 RemoveFromLoadGroup();
161 LOG_FUNC(gImgLog, "imgRequestProxy::~imgRequestProxy");
164 nsresult imgRequestProxy::Init(imgRequest* aOwner, nsILoadGroup* aLoadGroup,
165 Document* aLoadingDocument, nsIURI* aURI,
166 imgINotificationObserver* aObserver) {
167 MOZ_ASSERT(!GetOwner() && !mListener,
168 "imgRequestProxy is already initialized");
170 LOG_SCOPE_WITH_PARAM(gImgLog, "imgRequestProxy::Init", "request", aOwner);
172 MOZ_ASSERT(mAnimationConsumers == 0, "Cannot have animation before Init");
174 mBehaviour->SetOwner(aOwner);
175 mListener = aObserver;
176 // Make sure to addref mListener before the AddToOwner call below, since
177 // that call might well want to release it if the imgRequest has
178 // already seen OnStopRequest.
179 if (mListener) {
180 mHadListener = true;
181 mListenerIsStrongRef = true;
182 NS_ADDREF(mListener);
184 mLoadGroup = aLoadGroup;
185 mURI = aURI;
187 // Note: AddToOwner won't send all the On* notifications immediately
188 AddToOwner(aLoadingDocument);
190 return NS_OK;
193 nsresult imgRequestProxy::ChangeOwner(imgRequest* aNewOwner) {
194 MOZ_ASSERT(GetOwner(), "Cannot ChangeOwner on a proxy without an owner!");
196 if (mCanceled) {
197 // Ensure that this proxy has received all notifications to date
198 // before we clean it up when removing it from the old owner below.
199 SyncNotifyListener();
202 // If we're holding locks, unlock the old image.
203 // Note that UnlockImage decrements mLockCount each time it's called.
204 uint32_t oldLockCount = mLockCount;
205 while (mLockCount) {
206 UnlockImage();
209 // If we're holding animation requests, undo them.
210 uint32_t oldAnimationConsumers = mAnimationConsumers;
211 ClearAnimationConsumers();
213 GetOwner()->RemoveProxy(this, NS_OK);
215 mBehaviour->SetOwner(aNewOwner);
216 MOZ_ASSERT(!GetValidator(), "New owner cannot be validating!");
218 // If we were locked, apply the locks here
219 for (uint32_t i = 0; i < oldLockCount; i++) {
220 LockImage();
223 // If we had animation requests, restore them here. Note that we
224 // do this *after* RemoveProxy, which clears out animation consumers
225 // (see bug 601723).
226 for (uint32_t i = 0; i < oldAnimationConsumers; i++) {
227 IncrementAnimationConsumers();
230 AddToOwner(nullptr);
231 return NS_OK;
234 void imgRequestProxy::MarkValidating() {
235 MOZ_ASSERT(GetValidator());
236 mValidating = true;
239 void imgRequestProxy::ClearValidating() {
240 MOZ_ASSERT(mValidating);
241 MOZ_ASSERT(!GetValidator());
242 mValidating = false;
244 // If we'd previously requested a synchronous decode, request a decode on the
245 // new image.
246 if (mDecodeRequested) {
247 mDecodeRequested = false;
248 StartDecoding(imgIContainer::FLAG_NONE);
252 bool imgRequestProxy::IsOnEventTarget() const { return true; }
254 already_AddRefed<nsIEventTarget> imgRequestProxy::GetEventTarget() const {
255 nsCOMPtr<nsIEventTarget> target(mEventTarget);
256 return target.forget();
259 nsresult imgRequestProxy::DispatchWithTargetIfAvailable(
260 already_AddRefed<nsIRunnable> aEvent) {
261 LOG_FUNC(gImgLog, "imgRequestProxy::DispatchWithTargetIfAvailable");
263 // This method should only be used when it is *expected* that we are
264 // dispatching an event (e.g. we want to handle an event asynchronously)
265 // rather we need to (e.g. we are in the wrong scheduler group context).
266 // As such, we do not set mHadDispatch for telemetry purposes.
267 if (mEventTarget) {
268 mEventTarget->Dispatch(CreateMediumHighRunnable(std::move(aEvent)),
269 NS_DISPATCH_NORMAL);
270 return NS_OK;
273 return NS_DispatchToMainThread(CreateMediumHighRunnable(std::move(aEvent)));
276 void imgRequestProxy::DispatchWithTarget(already_AddRefed<nsIRunnable> aEvent) {
277 LOG_FUNC(gImgLog, "imgRequestProxy::DispatchWithTarget");
279 MOZ_ASSERT(mListener || mTabGroup);
280 MOZ_ASSERT(mEventTarget);
282 mHadDispatch = true;
283 mEventTarget->Dispatch(CreateMediumHighRunnable(std::move(aEvent)),
284 NS_DISPATCH_NORMAL);
287 void imgRequestProxy::AddToOwner(Document* aLoadingDocument) {
288 // An imgRequestProxy can be initialized with neither a listener nor a
289 // document. The caller could follow up later by cloning the canonical
290 // imgRequestProxy with the actual listener. This is possible because
291 // imgLoader::LoadImage does not require a valid listener to be provided.
293 // Without a listener, we don't need to set our scheduler group, because
294 // we have nothing to signal. However if we were told what document this
295 // is for, it is likely that future listeners will belong to the same
296 // scheduler group.
298 // With a listener, we always need to update our scheduler group. A null
299 // scheduler group is valid with or without a document, but that means
300 // we will use the most generic event target possible on dispatch.
301 if (aLoadingDocument) {
302 RefPtr<mozilla::dom::DocGroup> docGroup = aLoadingDocument->GetDocGroup();
303 if (docGroup) {
304 mTabGroup = docGroup->GetTabGroup();
305 MOZ_ASSERT(mTabGroup);
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 bool imgRequestProxy::RequestDecodeWithResult(uint32_t aFlags) {
528 if (IsValidating()) {
529 mDecodeRequested = true;
530 return false;
533 RefPtr<Image> image = GetImage();
534 if (image) {
535 return image->RequestDecodeWithResult(aFlags);
538 if (GetOwner()) {
539 GetOwner()->StartDecoding();
542 return false;
545 NS_IMETHODIMP
546 imgRequestProxy::LockImage() {
547 mLockCount++;
548 RefPtr<Image> image =
549 GetOwner() && GetOwner()->ImageAvailable() ? GetImage() : nullptr;
550 if (image) {
551 return image->LockImage();
553 return NS_OK;
556 NS_IMETHODIMP
557 imgRequestProxy::UnlockImage() {
558 MOZ_ASSERT(mLockCount > 0, "calling unlock but no locks!");
560 mLockCount--;
561 RefPtr<Image> image =
562 GetOwner() && GetOwner()->ImageAvailable() ? GetImage() : nullptr;
563 if (image) {
564 return image->UnlockImage();
566 return NS_OK;
569 NS_IMETHODIMP
570 imgRequestProxy::RequestDiscard() {
571 RefPtr<Image> image = GetImage();
572 if (image) {
573 return image->RequestDiscard();
575 return NS_OK;
578 NS_IMETHODIMP
579 imgRequestProxy::IncrementAnimationConsumers() {
580 mAnimationConsumers++;
581 RefPtr<Image> image =
582 GetOwner() && GetOwner()->ImageAvailable() ? GetImage() : nullptr;
583 if (image) {
584 image->IncrementAnimationConsumers();
586 return NS_OK;
589 NS_IMETHODIMP
590 imgRequestProxy::DecrementAnimationConsumers() {
591 // We may get here if some responsible code called Increment,
592 // then called us, but we have meanwhile called ClearAnimationConsumers
593 // because we needed to get rid of them earlier (see
594 // imgRequest::RemoveProxy), and hence have nothing left to
595 // decrement. (In such a case we got rid of the animation consumers
596 // early, but not the observer.)
597 if (mAnimationConsumers > 0) {
598 mAnimationConsumers--;
599 RefPtr<Image> image =
600 GetOwner() && GetOwner()->ImageAvailable() ? GetImage() : nullptr;
601 if (image) {
602 image->DecrementAnimationConsumers();
605 return NS_OK;
608 void imgRequestProxy::ClearAnimationConsumers() {
609 while (mAnimationConsumers > 0) {
610 DecrementAnimationConsumers();
614 NS_IMETHODIMP
615 imgRequestProxy::Suspend() { return NS_ERROR_NOT_IMPLEMENTED; }
617 NS_IMETHODIMP
618 imgRequestProxy::Resume() { return NS_ERROR_NOT_IMPLEMENTED; }
620 NS_IMETHODIMP
621 imgRequestProxy::GetLoadGroup(nsILoadGroup** loadGroup) {
622 NS_IF_ADDREF(*loadGroup = mLoadGroup.get());
623 return NS_OK;
625 NS_IMETHODIMP
626 imgRequestProxy::SetLoadGroup(nsILoadGroup* loadGroup) {
627 if (loadGroup != mLoadGroup) {
628 MOZ_ASSERT_UNREACHABLE("Switching load groups is unsupported!");
629 return NS_ERROR_NOT_IMPLEMENTED;
631 return NS_OK;
634 NS_IMETHODIMP
635 imgRequestProxy::GetLoadFlags(nsLoadFlags* flags) {
636 *flags = mLoadFlags;
637 return NS_OK;
639 NS_IMETHODIMP
640 imgRequestProxy::SetLoadFlags(nsLoadFlags flags) {
641 mLoadFlags = flags;
642 return NS_OK;
645 NS_IMETHODIMP
646 imgRequestProxy::GetTRRMode(nsIRequest::TRRMode* aTRRMode) {
647 return GetTRRModeImpl(aTRRMode);
650 NS_IMETHODIMP
651 imgRequestProxy::SetTRRMode(nsIRequest::TRRMode aTRRMode) {
652 return SetTRRModeImpl(aTRRMode);
655 /** imgIRequest methods **/
657 NS_IMETHODIMP
658 imgRequestProxy::GetImage(imgIContainer** aImage) {
659 NS_ENSURE_TRUE(aImage, NS_ERROR_NULL_POINTER);
660 // It's possible that our owner has an image but hasn't notified us of it -
661 // that'll happen if we get Canceled before the owner instantiates its image
662 // (because Canceling unregisters us as a listener on mOwner). If we're
663 // in that situation, just grab the image off of mOwner.
664 RefPtr<Image> image = GetImage();
665 nsCOMPtr<imgIContainer> imageToReturn;
666 if (image) {
667 imageToReturn = image;
669 if (!imageToReturn && GetOwner()) {
670 imageToReturn = GetOwner()->GetImage();
672 if (!imageToReturn) {
673 return NS_ERROR_FAILURE;
676 imageToReturn.swap(*aImage);
678 return NS_OK;
681 NS_IMETHODIMP
682 imgRequestProxy::GetProducerId(uint32_t* aId) {
683 NS_ENSURE_TRUE(aId, NS_ERROR_NULL_POINTER);
685 nsCOMPtr<imgIContainer> image;
686 nsresult rv = GetImage(getter_AddRefs(image));
687 if (NS_SUCCEEDED(rv)) {
688 *aId = image->GetProducerId();
689 } else {
690 *aId = layers::kContainerProducerID_Invalid;
693 return NS_OK;
696 NS_IMETHODIMP
697 imgRequestProxy::GetImageStatus(uint32_t* aStatus) {
698 if (IsValidating()) {
699 // We are currently validating the image, and so our status could revert if
700 // we discard the cache. We should also be deferring notifications, such
701 // that the caller will be notified when validation completes. Rather than
702 // risk misleading the caller, return nothing.
703 *aStatus = imgIRequest::STATUS_NONE;
704 } else {
705 RefPtr<ProgressTracker> progressTracker = GetProgressTracker();
706 *aStatus = progressTracker->GetImageStatus();
709 return NS_OK;
712 NS_IMETHODIMP
713 imgRequestProxy::GetImageErrorCode(nsresult* aStatus) {
714 if (!GetOwner()) {
715 return NS_ERROR_FAILURE;
718 *aStatus = GetOwner()->GetImageErrorCode();
720 return NS_OK;
723 NS_IMETHODIMP
724 imgRequestProxy::GetURI(nsIURI** aURI) {
725 MOZ_ASSERT(NS_IsMainThread(), "Must be on main thread to convert URI");
726 nsCOMPtr<nsIURI> uri = mURI;
727 uri.forget(aURI);
728 return NS_OK;
731 nsresult imgRequestProxy::GetFinalURI(nsIURI** aURI) {
732 if (!GetOwner()) {
733 return NS_ERROR_FAILURE;
736 return GetOwner()->GetFinalURI(aURI);
739 NS_IMETHODIMP
740 imgRequestProxy::GetNotificationObserver(imgINotificationObserver** aObserver) {
741 *aObserver = mListener;
742 NS_IF_ADDREF(*aObserver);
743 return NS_OK;
746 NS_IMETHODIMP
747 imgRequestProxy::GetMimeType(char** aMimeType) {
748 if (!GetOwner()) {
749 return NS_ERROR_FAILURE;
752 const char* type = GetOwner()->GetMimeType();
753 if (!type) {
754 return NS_ERROR_FAILURE;
757 *aMimeType = NS_xstrdup(type);
759 return NS_OK;
762 imgRequestProxy* imgRequestProxy::NewClonedProxy() {
763 return new imgRequestProxy();
766 NS_IMETHODIMP
767 imgRequestProxy::Clone(imgINotificationObserver* aObserver,
768 imgIRequest** aClone) {
769 nsresult result;
770 imgRequestProxy* proxy;
771 result = PerformClone(aObserver, nullptr, /* aSyncNotify */ true, &proxy);
772 *aClone = proxy;
773 return result;
776 nsresult imgRequestProxy::SyncClone(imgINotificationObserver* aObserver,
777 Document* aLoadingDocument,
778 imgRequestProxy** aClone) {
779 return PerformClone(aObserver, aLoadingDocument,
780 /* aSyncNotify */ true, aClone);
783 nsresult imgRequestProxy::Clone(imgINotificationObserver* aObserver,
784 Document* aLoadingDocument,
785 imgRequestProxy** aClone) {
786 return PerformClone(aObserver, aLoadingDocument,
787 /* aSyncNotify */ false, aClone);
790 nsresult imgRequestProxy::PerformClone(imgINotificationObserver* aObserver,
791 Document* aLoadingDocument,
792 bool aSyncNotify,
793 imgRequestProxy** aClone) {
794 MOZ_ASSERT(aClone, "Null out param");
796 LOG_SCOPE(gImgLog, "imgRequestProxy::Clone");
798 *aClone = nullptr;
799 RefPtr<imgRequestProxy> clone = NewClonedProxy();
801 nsCOMPtr<nsILoadGroup> loadGroup;
802 if (aLoadingDocument) {
803 loadGroup = aLoadingDocument->GetDocumentLoadGroup();
806 // It is important to call |SetLoadFlags()| before calling |Init()| because
807 // |Init()| adds the request to the loadgroup.
808 // When a request is added to a loadgroup, its load flags are merged
809 // with the load flags of the loadgroup.
810 // XXXldb That's not true anymore. Stuff from imgLoader adds the
811 // request to the loadgroup.
812 clone->SetLoadFlags(mLoadFlags);
813 nsresult rv = clone->Init(mBehaviour->GetOwner(), loadGroup, aLoadingDocument,
814 mURI, aObserver);
815 if (NS_FAILED(rv)) {
816 return rv;
819 // Assign to *aClone before calling Notify so that if the caller expects to
820 // only be notified for requests it's already holding pointers to it won't be
821 // surprised.
822 NS_ADDREF(*aClone = clone);
824 imgCacheValidator* validator = GetValidator();
825 if (validator) {
826 // Note that if we have a validator, we don't want to issue notifications at
827 // here because we want to defer until that completes. AddProxy will add us
828 // to the load group; we cannot avoid that in this case, because we don't
829 // know when the validation will complete, and if it will cause us to
830 // discard our cached state anyways. We are probably already blocked by the
831 // original LoadImage(WithChannel) request in any event.
832 clone->MarkValidating();
833 validator->AddProxy(clone);
834 } else {
835 // We only want to add the request to the load group of the owning document
836 // if it is still in progress. Some callers cannot handle a supurious load
837 // group removal (e.g. print preview) so we must be careful. On the other
838 // hand, if after cloning, the original request proxy is cancelled /
839 // destroyed, we need to ensure that any clones still block the load group
840 // if it is incomplete.
841 bool addToLoadGroup = mIsInLoadGroup;
842 if (!addToLoadGroup) {
843 RefPtr<ProgressTracker> tracker = clone->GetProgressTracker();
844 addToLoadGroup =
845 tracker && !(tracker->GetProgress() & FLAG_LOAD_COMPLETE);
848 if (addToLoadGroup) {
849 clone->AddToLoadGroup();
852 if (aSyncNotify) {
853 // This is wrong!!! We need to notify asynchronously, but there's code
854 // that assumes that we don't. This will be fixed in bug 580466. Note that
855 // if we have a validator, we won't issue notifications anyways because
856 // they are deferred, so there is no point in requesting.
857 clone->mForceDispatchLoadGroup = true;
858 clone->SyncNotifyListener();
859 clone->mForceDispatchLoadGroup = false;
860 } else {
861 // Without a validator, we can request asynchronous notifications
862 // immediately. If there was a validator, this would override the deferral
863 // and that would be incorrect.
864 clone->NotifyListener();
868 return NS_OK;
871 NS_IMETHODIMP
872 imgRequestProxy::GetImagePrincipal(nsIPrincipal** aPrincipal) {
873 if (!GetOwner()) {
874 return NS_ERROR_FAILURE;
877 nsCOMPtr<nsIPrincipal> principal = GetOwner()->GetPrincipal();
878 principal.forget(aPrincipal);
879 return NS_OK;
882 NS_IMETHODIMP
883 imgRequestProxy::GetHadCrossOriginRedirects(bool* aHadCrossOriginRedirects) {
884 *aHadCrossOriginRedirects = false;
886 nsCOMPtr<nsITimedChannel> timedChannel = TimedChannel();
887 if (timedChannel) {
888 bool allRedirectsSameOrigin = false;
889 *aHadCrossOriginRedirects =
890 NS_SUCCEEDED(
891 timedChannel->GetAllRedirectsSameOrigin(&allRedirectsSameOrigin)) &&
892 !allRedirectsSameOrigin;
895 return NS_OK;
898 NS_IMETHODIMP
899 imgRequestProxy::GetMultipart(bool* aMultipart) {
900 if (!GetOwner()) {
901 return NS_ERROR_FAILURE;
904 *aMultipart = GetOwner()->GetMultipart();
906 return NS_OK;
909 NS_IMETHODIMP
910 imgRequestProxy::GetCORSMode(int32_t* aCorsMode) {
911 if (!GetOwner()) {
912 return NS_ERROR_FAILURE;
915 *aCorsMode = GetOwner()->GetCORSMode();
917 return NS_OK;
920 NS_IMETHODIMP
921 imgRequestProxy::BoostPriority(uint32_t aCategory) {
922 NS_ENSURE_STATE(GetOwner() && !mCanceled);
923 GetOwner()->BoostPriority(aCategory);
924 return NS_OK;
927 /** nsISupportsPriority methods **/
929 NS_IMETHODIMP
930 imgRequestProxy::GetPriority(int32_t* priority) {
931 NS_ENSURE_STATE(GetOwner());
932 *priority = GetOwner()->Priority();
933 return NS_OK;
936 NS_IMETHODIMP
937 imgRequestProxy::SetPriority(int32_t priority) {
938 NS_ENSURE_STATE(GetOwner() && !mCanceled);
939 GetOwner()->AdjustPriority(this, priority - GetOwner()->Priority());
940 return NS_OK;
943 NS_IMETHODIMP
944 imgRequestProxy::AdjustPriority(int32_t priority) {
945 // We don't require |!mCanceled| here. This may be called even if we're
946 // cancelled, because it's invoked as part of the process of removing an image
947 // from the load group.
948 NS_ENSURE_STATE(GetOwner());
949 GetOwner()->AdjustPriority(this, priority);
950 return NS_OK;
953 static const char* NotificationTypeToString(int32_t aType) {
954 switch (aType) {
955 case imgINotificationObserver::SIZE_AVAILABLE:
956 return "SIZE_AVAILABLE";
957 case imgINotificationObserver::FRAME_UPDATE:
958 return "FRAME_UPDATE";
959 case imgINotificationObserver::FRAME_COMPLETE:
960 return "FRAME_COMPLETE";
961 case imgINotificationObserver::LOAD_COMPLETE:
962 return "LOAD_COMPLETE";
963 case imgINotificationObserver::DECODE_COMPLETE:
964 return "DECODE_COMPLETE";
965 case imgINotificationObserver::DISCARD:
966 return "DISCARD";
967 case imgINotificationObserver::UNLOCKED_DRAW:
968 return "UNLOCKED_DRAW";
969 case imgINotificationObserver::IS_ANIMATED:
970 return "IS_ANIMATED";
971 case imgINotificationObserver::HAS_TRANSPARENCY:
972 return "HAS_TRANSPARENCY";
973 default:
974 MOZ_ASSERT_UNREACHABLE("Notification list should be exhaustive");
975 return "(unknown notification)";
979 void imgRequestProxy::Notify(int32_t aType,
980 const mozilla::gfx::IntRect* aRect) {
981 MOZ_ASSERT(aType != imgINotificationObserver::LOAD_COMPLETE,
982 "Should call OnLoadComplete");
984 LOG_FUNC_WITH_PARAM(gImgLog, "imgRequestProxy::Notify", "type",
985 NotificationTypeToString(aType));
987 if (!mListener || mCanceled) {
988 return;
991 if (!IsOnEventTarget()) {
992 RefPtr<imgRequestProxy> self(this);
993 if (aRect) {
994 const mozilla::gfx::IntRect rect = *aRect;
995 DispatchWithTarget(NS_NewRunnableFunction(
996 "imgRequestProxy::Notify",
997 [self, rect, aType]() -> void { self->Notify(aType, &rect); }));
998 } else {
999 DispatchWithTarget(NS_NewRunnableFunction(
1000 "imgRequestProxy::Notify",
1001 [self, aType]() -> void { self->Notify(aType, nullptr); }));
1003 return;
1006 // Make sure the listener stays alive while we notify.
1007 nsCOMPtr<imgINotificationObserver> listener(mListener);
1009 listener->Notify(this, aType, aRect);
1012 void imgRequestProxy::OnLoadComplete(bool aLastPart) {
1013 LOG_FUNC_WITH_PARAM(gImgLog, "imgRequestProxy::OnLoadComplete", "uri", mURI);
1015 // There's all sorts of stuff here that could kill us (the OnStopRequest call
1016 // on the listener, the removal from the loadgroup, the release of the
1017 // listener, etc). Don't let them do it.
1018 RefPtr<imgRequestProxy> self(this);
1020 if (!IsOnEventTarget()) {
1021 DispatchWithTarget(NS_NewRunnableFunction(
1022 "imgRequestProxy::OnLoadComplete",
1023 [self, aLastPart]() -> void { self->OnLoadComplete(aLastPart); }));
1024 return;
1027 if (mListener && !mCanceled) {
1028 // Hold a ref to the listener while we call it, just in case.
1029 nsCOMPtr<imgINotificationObserver> listener(mListener);
1030 listener->Notify(this, imgINotificationObserver::LOAD_COMPLETE, nullptr);
1033 // If we're expecting more data from a multipart channel, re-add ourself
1034 // to the loadgroup so that the document doesn't lose track of the load.
1035 // If the request is already a background request and there's more data
1036 // coming, we can just leave the request in the loadgroup as-is.
1037 if (aLastPart || (mLoadFlags & nsIRequest::LOAD_BACKGROUND) == 0) {
1038 if (aLastPart) {
1039 RemoveFromLoadGroup();
1040 } else {
1041 // More data is coming, so change the request to be a background request
1042 // and put it back in the loadgroup.
1043 MoveToBackgroundInLoadGroup();
1047 if (mListenerIsStrongRef && aLastPart) {
1048 MOZ_ASSERT(mListener, "How did that happen?");
1049 // Drop our strong ref to the listener now that we're done with
1050 // everything. Note that this can cancel us and other fun things
1051 // like that. Don't add anything in this method after this point.
1052 imgINotificationObserver* obs = mListener;
1053 mListenerIsStrongRef = false;
1054 NS_RELEASE(obs);
1058 void imgRequestProxy::NullOutListener() {
1059 // If we have animation consumers, then they don't matter anymore
1060 if (mListener) {
1061 ClearAnimationConsumers();
1064 if (mListenerIsStrongRef) {
1065 // Releasing could do weird reentery stuff, so just play it super-safe
1066 nsCOMPtr<imgINotificationObserver> obs;
1067 obs.swap(mListener);
1068 mListenerIsStrongRef = false;
1069 } else {
1070 mListener = nullptr;
1073 // Note that we don't free the event target. We actually need that to ensure
1074 // we get removed from the ProgressTracker properly. No harm in keeping it
1075 // however.
1076 mTabGroup = nullptr;
1079 NS_IMETHODIMP
1080 imgRequestProxy::GetStaticRequest(imgIRequest** aReturn) {
1081 imgRequestProxy* proxy;
1082 nsresult result = GetStaticRequest(nullptr, &proxy);
1083 *aReturn = proxy;
1084 return result;
1087 nsresult imgRequestProxy::GetStaticRequest(Document* aLoadingDocument,
1088 imgRequestProxy** aReturn) {
1089 *aReturn = nullptr;
1090 RefPtr<Image> image = GetImage();
1092 bool animated;
1093 if (!image || (NS_SUCCEEDED(image->GetAnimated(&animated)) && !animated)) {
1094 // Early exit - we're not animated, so we don't have to do anything.
1095 NS_ADDREF(*aReturn = this);
1096 return NS_OK;
1099 // Check for errors in the image. Callers code rely on GetStaticRequest
1100 // failing in this case, though with FrozenImage there's no technical reason
1101 // for it anymore.
1102 if (image->HasError()) {
1103 return NS_ERROR_FAILURE;
1106 // We are animated. We need to create a frozen version of this image.
1107 RefPtr<Image> frozenImage = ImageOps::Freeze(image);
1109 // Create a static imgRequestProxy with our new extracted frame.
1110 nsCOMPtr<nsIPrincipal> currentPrincipal;
1111 GetImagePrincipal(getter_AddRefs(currentPrincipal));
1112 bool hadCrossOriginRedirects = true;
1113 GetHadCrossOriginRedirects(&hadCrossOriginRedirects);
1114 RefPtr<imgRequestProxy> req = new imgRequestProxyStatic(
1115 frozenImage, currentPrincipal, hadCrossOriginRedirects);
1116 req->Init(nullptr, nullptr, aLoadingDocument, mURI, nullptr);
1118 NS_ADDREF(*aReturn = req);
1120 return NS_OK;
1123 void imgRequestProxy::NotifyListener() {
1124 // It would be nice to notify the observer directly in the status tracker
1125 // instead of through the proxy, but there are several places we do extra
1126 // processing when we receive notifications (like OnStopRequest()), and we
1127 // need to check mCanceled everywhere too.
1129 RefPtr<ProgressTracker> progressTracker = GetProgressTracker();
1130 if (GetOwner()) {
1131 // Send the notifications to our listener asynchronously.
1132 progressTracker->Notify(this);
1133 } else {
1134 // We don't have an imgRequest, so we can only notify the clone of our
1135 // current state, but we still have to do that asynchronously.
1136 MOZ_ASSERT(HasImage(), "if we have no imgRequest, we should have an Image");
1137 progressTracker->NotifyCurrentState(this);
1141 void imgRequestProxy::SyncNotifyListener() {
1142 // It would be nice to notify the observer directly in the status tracker
1143 // instead of through the proxy, but there are several places we do extra
1144 // processing when we receive notifications (like OnStopRequest()), and we
1145 // need to check mCanceled everywhere too.
1147 RefPtr<ProgressTracker> progressTracker = GetProgressTracker();
1148 progressTracker->SyncNotify(this);
1151 void imgRequestProxy::SetHasImage() {
1152 RefPtr<ProgressTracker> progressTracker = GetProgressTracker();
1153 MOZ_ASSERT(progressTracker);
1154 RefPtr<Image> image = progressTracker->GetImage();
1155 MOZ_ASSERT(image);
1157 // Force any private status related to the owner to reflect
1158 // the presence of an image;
1159 mBehaviour->SetOwner(mBehaviour->GetOwner());
1161 // Apply any locks we have
1162 for (uint32_t i = 0; i < mLockCount; ++i) {
1163 image->LockImage();
1166 // Apply any animation consumers we have
1167 for (uint32_t i = 0; i < mAnimationConsumers; i++) {
1168 image->IncrementAnimationConsumers();
1172 already_AddRefed<ProgressTracker> imgRequestProxy::GetProgressTracker() const {
1173 return mBehaviour->GetProgressTracker();
1176 already_AddRefed<mozilla::image::Image> imgRequestProxy::GetImage() const {
1177 return mBehaviour->GetImage();
1180 bool RequestBehaviour::HasImage() const {
1181 if (!mOwnerHasImage) {
1182 return false;
1184 RefPtr<ProgressTracker> progressTracker = GetProgressTracker();
1185 return progressTracker ? progressTracker->HasImage() : false;
1188 bool imgRequestProxy::HasImage() const { return mBehaviour->HasImage(); }
1190 imgRequest* imgRequestProxy::GetOwner() const { return mBehaviour->GetOwner(); }
1192 imgCacheValidator* imgRequestProxy::GetValidator() const {
1193 imgRequest* owner = GetOwner();
1194 if (!owner) {
1195 return nullptr;
1197 return owner->GetValidator();
1200 ////////////////// imgRequestProxyStatic methods
1202 class StaticBehaviour : public ProxyBehaviour {
1203 public:
1204 explicit StaticBehaviour(mozilla::image::Image* aImage) : mImage(aImage) {}
1206 already_AddRefed<mozilla::image::Image> GetImage() const override {
1207 RefPtr<mozilla::image::Image> image = mImage;
1208 return image.forget();
1211 bool HasImage() const override { return mImage; }
1213 already_AddRefed<ProgressTracker> GetProgressTracker() const override {
1214 return mImage->GetProgressTracker();
1217 imgRequest* GetOwner() const override { return nullptr; }
1219 void SetOwner(imgRequest* aOwner) override {
1220 MOZ_ASSERT(!aOwner,
1221 "We shouldn't be giving static requests a non-null owner.");
1224 private:
1225 // Our image. We have to hold a strong reference here, because that's normally
1226 // the job of the underlying request.
1227 RefPtr<mozilla::image::Image> mImage;
1230 imgRequestProxyStatic::imgRequestProxyStatic(mozilla::image::Image* aImage,
1231 nsIPrincipal* aPrincipal,
1232 bool aHadCrossOriginRedirects)
1233 : mPrincipal(aPrincipal),
1234 mHadCrossOriginRedirects(aHadCrossOriginRedirects) {
1235 mBehaviour = mozilla::MakeUnique<StaticBehaviour>(aImage);
1238 NS_IMETHODIMP
1239 imgRequestProxyStatic::GetImagePrincipal(nsIPrincipal** aPrincipal) {
1240 if (!mPrincipal) {
1241 return NS_ERROR_FAILURE;
1244 NS_ADDREF(*aPrincipal = mPrincipal);
1246 return NS_OK;
1249 NS_IMETHODIMP
1250 imgRequestProxyStatic::GetHadCrossOriginRedirects(
1251 bool* aHadCrossOriginRedirects) {
1252 *aHadCrossOriginRedirects = mHadCrossOriginRedirects;
1253 return NS_OK;
1256 imgRequestProxy* imgRequestProxyStatic::NewClonedProxy() {
1257 nsCOMPtr<nsIPrincipal> currentPrincipal;
1258 GetImagePrincipal(getter_AddRefs(currentPrincipal));
1259 bool hadCrossOriginRedirects = true;
1260 GetHadCrossOriginRedirects(&hadCrossOriginRedirects);
1261 RefPtr<mozilla::image::Image> image = GetImage();
1262 return new imgRequestProxyStatic(image, currentPrincipal,
1263 hadCrossOriginRedirects);