Bug 1622408 [wpt PR 22244] - Restore the event delegate for a CSSTransition after...
[gecko.git] / image / imgRequestProxy.cpp
blob2e472c69474f7dab5ce38809b4f663e5ba11efad
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
3 * This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "imgRequestProxy.h"
9 #include <utility>
11 #include "Image.h"
12 #include "ImageLogging.h"
13 #include "ImageOps.h"
14 #include "ImageTypes.h"
15 #include "imgINotificationObserver.h"
16 #include "imgLoader.h"
17 #include "mozilla/Telemetry.h" // for Telemetry
18 #include "mozilla/dom/DocGroup.h" // for DocGroup
19 #include "mozilla/dom/TabGroup.h" // for TabGroup
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, imgIRequest)
98 NS_INTERFACE_MAP_ENTRY(imgIRequest)
99 NS_INTERFACE_MAP_ENTRY(nsIRequest)
100 NS_INTERFACE_MAP_ENTRY(nsISupportsPriority)
101 NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsITimedChannel, TimedChannel() != nullptr)
102 NS_INTERFACE_MAP_END
104 imgRequestProxy::imgRequestProxy()
105 : mBehaviour(new RequestBehaviour),
106 mURI(nullptr),
107 mListener(nullptr),
108 mLoadFlags(nsIRequest::LOAD_NORMAL),
109 mLockCount(0),
110 mAnimationConsumers(0),
111 mCanceled(false),
112 mIsInLoadGroup(false),
113 mForceDispatchLoadGroup(false),
114 mListenerIsStrongRef(false),
115 mDecodeRequested(false),
116 mPendingNotify(false),
117 mValidating(false),
118 mHadListener(false),
119 mHadDispatch(false) {
120 /* member initializers and constructor code */
121 LOG_FUNC(gImgLog, "imgRequestProxy::imgRequestProxy");
124 imgRequestProxy::~imgRequestProxy() {
125 /* destructor code */
126 MOZ_ASSERT(!mListener, "Someone forgot to properly cancel this request!");
128 // If we had a listener, that means we would have issued notifications. With
129 // bug 1359833, we added support for main thread scheduler groups. Each
130 // imgRequestProxy may have its own associated listener, document and/or
131 // scheduler group. Typically most imgRequestProxy belong to the same
132 // document, or have no listener, which means we will want to execute all main
133 // thread code in that shared scheduler group. Less frequently, there may be
134 // multiple imgRequests and they have separate documents, which means that
135 // when we issue state notifications, some or all need to be dispatched to the
136 // appropriate scheduler group for each request. This should be rare, so we
137 // want to monitor the frequency of dispatching in the wild.
138 if (mHadListener) {
139 mozilla::Telemetry::Accumulate(mozilla::Telemetry::IMAGE_REQUEST_DISPATCHED,
140 mHadDispatch);
143 MOZ_RELEASE_ASSERT(!mLockCount, "Someone forgot to unlock on time?");
145 ClearAnimationConsumers();
147 // Explicitly set mListener to null to ensure that the RemoveProxy
148 // call below can't send |this| to an arbitrary listener while |this|
149 // is being destroyed. This is all belt-and-suspenders in view of the
150 // above assert.
151 NullOutListener();
153 /* Call RemoveProxy with a successful status. This will keep the
154 channel, if still downloading data, from being canceled if 'this' is
155 the last observer. This allows the image to continue to download and
156 be cached even if no one is using it currently.
158 mCanceled = true;
159 RemoveFromOwner(NS_OK);
161 RemoveFromLoadGroup();
162 LOG_FUNC(gImgLog, "imgRequestProxy::~imgRequestProxy");
165 nsresult imgRequestProxy::Init(imgRequest* aOwner, nsILoadGroup* aLoadGroup,
166 Document* aLoadingDocument, nsIURI* aURI,
167 imgINotificationObserver* aObserver) {
168 MOZ_ASSERT(!GetOwner() && !mListener,
169 "imgRequestProxy is already initialized");
171 LOG_SCOPE_WITH_PARAM(gImgLog, "imgRequestProxy::Init", "request", aOwner);
173 MOZ_ASSERT(mAnimationConsumers == 0, "Cannot have animation before Init");
175 mBehaviour->SetOwner(aOwner);
176 mListener = aObserver;
177 // Make sure to addref mListener before the AddToOwner call below, since
178 // that call might well want to release it if the imgRequest has
179 // already seen OnStopRequest.
180 if (mListener) {
181 mHadListener = true;
182 mListenerIsStrongRef = true;
183 NS_ADDREF(mListener);
185 mLoadGroup = aLoadGroup;
186 mURI = aURI;
188 // Note: AddToOwner won't send all the On* notifications immediately
189 AddToOwner(aLoadingDocument);
191 return NS_OK;
194 nsresult imgRequestProxy::ChangeOwner(imgRequest* aNewOwner) {
195 MOZ_ASSERT(GetOwner(), "Cannot ChangeOwner on a proxy without an owner!");
197 if (mCanceled) {
198 // Ensure that this proxy has received all notifications to date
199 // before we clean it up when removing it from the old owner below.
200 SyncNotifyListener();
203 // If we're holding locks, unlock the old image.
204 // Note that UnlockImage decrements mLockCount each time it's called.
205 uint32_t oldLockCount = mLockCount;
206 while (mLockCount) {
207 UnlockImage();
210 // If we're holding animation requests, undo them.
211 uint32_t oldAnimationConsumers = mAnimationConsumers;
212 ClearAnimationConsumers();
214 GetOwner()->RemoveProxy(this, NS_OK);
216 mBehaviour->SetOwner(aNewOwner);
217 MOZ_ASSERT(!GetValidator(), "New owner cannot be validating!");
219 // If we were locked, apply the locks here
220 for (uint32_t i = 0; i < oldLockCount; i++) {
221 LockImage();
224 // If we had animation requests, restore them here. Note that we
225 // do this *after* RemoveProxy, which clears out animation consumers
226 // (see bug 601723).
227 for (uint32_t i = 0; i < oldAnimationConsumers; i++) {
228 IncrementAnimationConsumers();
231 AddToOwner(nullptr);
232 return NS_OK;
235 void imgRequestProxy::MarkValidating() {
236 MOZ_ASSERT(GetValidator());
237 mValidating = true;
240 void imgRequestProxy::ClearValidating() {
241 MOZ_ASSERT(mValidating);
242 MOZ_ASSERT(!GetValidator());
243 mValidating = false;
245 // If we'd previously requested a synchronous decode, request a decode on the
246 // new image.
247 if (mDecodeRequested) {
248 mDecodeRequested = false;
249 StartDecoding(imgIContainer::FLAG_NONE);
253 bool imgRequestProxy::IsOnEventTarget() const { return true; }
255 already_AddRefed<nsIEventTarget> imgRequestProxy::GetEventTarget() const {
256 nsCOMPtr<nsIEventTarget> target(mEventTarget);
257 return target.forget();
260 nsresult imgRequestProxy::DispatchWithTargetIfAvailable(
261 already_AddRefed<nsIRunnable> aEvent) {
262 LOG_FUNC(gImgLog, "imgRequestProxy::DispatchWithTargetIfAvailable");
264 // This method should only be used when it is *expected* that we are
265 // dispatching an event (e.g. we want to handle an event asynchronously)
266 // rather we need to (e.g. we are in the wrong scheduler group context).
267 // As such, we do not set mHadDispatch for telemetry purposes.
268 if (mEventTarget) {
269 mEventTarget->Dispatch(CreateMediumHighRunnable(std::move(aEvent)),
270 NS_DISPATCH_NORMAL);
271 return NS_OK;
274 return NS_DispatchToMainThread(CreateMediumHighRunnable(std::move(aEvent)));
277 void imgRequestProxy::DispatchWithTarget(already_AddRefed<nsIRunnable> aEvent) {
278 LOG_FUNC(gImgLog, "imgRequestProxy::DispatchWithTarget");
280 MOZ_ASSERT(mListener || mTabGroup);
281 MOZ_ASSERT(mEventTarget);
283 mHadDispatch = true;
284 mEventTarget->Dispatch(CreateMediumHighRunnable(std::move(aEvent)),
285 NS_DISPATCH_NORMAL);
288 void imgRequestProxy::AddToOwner(Document* aLoadingDocument) {
289 // An imgRequestProxy can be initialized with neither a listener nor a
290 // document. The caller could follow up later by cloning the canonical
291 // imgRequestProxy with the actual listener. This is possible because
292 // imgLoader::LoadImage does not require a valid listener to be provided.
294 // Without a listener, we don't need to set our scheduler group, because
295 // we have nothing to signal. However if we were told what document this
296 // is for, it is likely that future listeners will belong to the same
297 // scheduler group.
299 // With a listener, we always need to update our scheduler group. A null
300 // scheduler group is valid with or without a document, but that means
301 // we will use the most generic event target possible on dispatch.
302 if (aLoadingDocument) {
303 RefPtr<mozilla::dom::DocGroup> docGroup = aLoadingDocument->GetDocGroup();
304 if (docGroup) {
305 mTabGroup = docGroup->GetTabGroup();
306 MOZ_ASSERT(mTabGroup);
308 mEventTarget = docGroup->EventTargetFor(mozilla::TaskCategory::Other);
309 MOZ_ASSERT(mEventTarget);
313 if (mListener && !mEventTarget) {
314 mEventTarget = do_GetMainThread();
317 imgRequest* owner = GetOwner();
318 if (!owner) {
319 return;
322 owner->AddProxy(this);
325 void imgRequestProxy::RemoveFromOwner(nsresult aStatus) {
326 imgRequest* owner = GetOwner();
327 if (owner) {
328 if (mValidating) {
329 imgCacheValidator* validator = owner->GetValidator();
330 MOZ_ASSERT(validator);
331 validator->RemoveProxy(this);
332 mValidating = false;
335 owner->RemoveProxy(this, aStatus);
339 void imgRequestProxy::AddToLoadGroup() {
340 NS_ASSERTION(!mIsInLoadGroup, "Whaa, we're already in the loadgroup!");
341 MOZ_ASSERT(!mForceDispatchLoadGroup);
343 /* While in theory there could be a dispatch outstanding to remove this
344 request from the load group, in practice we only add to the load group
345 (when previously not in a load group) at initialization. */
346 if (!mIsInLoadGroup && mLoadGroup) {
347 LOG_FUNC(gImgLog, "imgRequestProxy::AddToLoadGroup");
348 mLoadGroup->AddRequest(this, nullptr);
349 mIsInLoadGroup = true;
353 void imgRequestProxy::RemoveFromLoadGroup() {
354 if (!mIsInLoadGroup || !mLoadGroup) {
355 return;
358 /* Sometimes we may not be able to remove ourselves from the load group in
359 the current context. This is because our listeners are not re-entrant (e.g.
360 we are in the middle of CancelAndForgetObserver or SyncClone). */
361 if (mForceDispatchLoadGroup) {
362 LOG_FUNC(gImgLog, "imgRequestProxy::RemoveFromLoadGroup -- dispatch");
364 /* We take away the load group from the request temporarily; this prevents
365 additional dispatches via RemoveFromLoadGroup occurring, as well as
366 MoveToBackgroundInLoadGroup from removing and readding. This is safe
367 because we know that once we get here, blocking the load group at all is
368 unnecessary. */
369 mIsInLoadGroup = false;
370 nsCOMPtr<nsILoadGroup> loadGroup = std::move(mLoadGroup);
371 RefPtr<imgRequestProxy> self(this);
372 DispatchWithTargetIfAvailable(NS_NewRunnableFunction(
373 "imgRequestProxy::RemoveFromLoadGroup", [self, loadGroup]() -> void {
374 loadGroup->RemoveRequest(self, nullptr, NS_OK);
375 }));
376 return;
379 LOG_FUNC(gImgLog, "imgRequestProxy::RemoveFromLoadGroup");
381 /* calling RemoveFromLoadGroup may cause the document to finish
382 loading, which could result in our death. We need to make sure
383 that we stay alive long enough to fight another battle... at
384 least until we exit this function. */
385 nsCOMPtr<imgIRequest> kungFuDeathGrip(this);
386 mLoadGroup->RemoveRequest(this, nullptr, NS_OK);
387 mLoadGroup = nullptr;
388 mIsInLoadGroup = false;
391 void imgRequestProxy::MoveToBackgroundInLoadGroup() {
392 /* Even if we are still in the load group, we may have taken away the load
393 group reference itself because we are in the process of leaving the group.
394 In that case, there is no need to background the request. */
395 if (!mLoadGroup) {
396 return;
399 /* There is no need to dispatch if we only need to add ourselves to the load
400 group without removal. It is the removal which causes the problematic
401 callbacks (see RemoveFromLoadGroup). */
402 if (mIsInLoadGroup && mForceDispatchLoadGroup) {
403 LOG_FUNC(gImgLog,
404 "imgRequestProxy::MoveToBackgroundInLoadGroup -- dispatch");
406 RefPtr<imgRequestProxy> self(this);
407 DispatchWithTargetIfAvailable(NS_NewRunnableFunction(
408 "imgRequestProxy::MoveToBackgroundInLoadGroup",
409 [self]() -> void { self->MoveToBackgroundInLoadGroup(); }));
410 return;
413 LOG_FUNC(gImgLog, "imgRequestProxy::MoveToBackgroundInLoadGroup");
414 nsCOMPtr<imgIRequest> kungFuDeathGrip(this);
415 if (mIsInLoadGroup) {
416 mLoadGroup->RemoveRequest(this, nullptr, NS_OK);
419 mLoadFlags |= nsIRequest::LOAD_BACKGROUND;
420 mLoadGroup->AddRequest(this, nullptr);
423 /** nsIRequest / imgIRequest methods **/
425 NS_IMETHODIMP
426 imgRequestProxy::GetName(nsACString& aName) {
427 aName.Truncate();
429 if (mURI) {
430 mURI->GetSpec(aName);
433 return NS_OK;
436 NS_IMETHODIMP
437 imgRequestProxy::IsPending(bool* _retval) { return NS_ERROR_NOT_IMPLEMENTED; }
439 NS_IMETHODIMP
440 imgRequestProxy::GetStatus(nsresult* aStatus) {
441 return NS_ERROR_NOT_IMPLEMENTED;
444 NS_IMETHODIMP
445 imgRequestProxy::Cancel(nsresult status) {
446 if (mCanceled) {
447 return NS_ERROR_FAILURE;
450 LOG_SCOPE(gImgLog, "imgRequestProxy::Cancel");
452 mCanceled = true;
454 nsCOMPtr<nsIRunnable> ev = new imgCancelRunnable(this, status);
455 return DispatchWithTargetIfAvailable(ev.forget());
458 void imgRequestProxy::DoCancel(nsresult status) {
459 RemoveFromOwner(status);
460 RemoveFromLoadGroup();
461 NullOutListener();
464 NS_IMETHODIMP
465 imgRequestProxy::CancelAndForgetObserver(nsresult aStatus) {
466 // If mCanceled is true but mListener is non-null, that means
467 // someone called Cancel() on us but the imgCancelRunnable is still
468 // pending. We still need to null out mListener before returning
469 // from this function in this case. That means we want to do the
470 // RemoveProxy call right now, because we need to deliver the
471 // onStopRequest.
472 if (mCanceled && !mListener) {
473 return NS_ERROR_FAILURE;
476 LOG_SCOPE(gImgLog, "imgRequestProxy::CancelAndForgetObserver");
478 mCanceled = true;
479 mForceDispatchLoadGroup = true;
480 RemoveFromOwner(aStatus);
481 RemoveFromLoadGroup();
482 mForceDispatchLoadGroup = false;
484 NullOutListener();
486 return NS_OK;
489 NS_IMETHODIMP
490 imgRequestProxy::StartDecoding(uint32_t aFlags) {
491 // Flag this, so we know to request after validation if pending.
492 if (IsValidating()) {
493 mDecodeRequested = true;
494 return NS_OK;
497 RefPtr<Image> image = GetImage();
498 if (image) {
499 return image->StartDecoding(aFlags);
502 if (GetOwner()) {
503 GetOwner()->StartDecoding();
506 return NS_OK;
509 bool imgRequestProxy::StartDecodingWithResult(uint32_t aFlags) {
510 // Flag this, so we know to request after validation if pending.
511 if (IsValidating()) {
512 mDecodeRequested = true;
513 return false;
516 RefPtr<Image> image = GetImage();
517 if (image) {
518 return image->StartDecodingWithResult(aFlags);
521 if (GetOwner()) {
522 GetOwner()->StartDecoding();
525 return false;
528 bool imgRequestProxy::RequestDecodeWithResult(uint32_t aFlags) {
529 if (IsValidating()) {
530 mDecodeRequested = true;
531 return false;
534 RefPtr<Image> image = GetImage();
535 if (image) {
536 return image->RequestDecodeWithResult(aFlags);
539 if (GetOwner()) {
540 GetOwner()->StartDecoding();
543 return false;
546 NS_IMETHODIMP
547 imgRequestProxy::LockImage() {
548 mLockCount++;
549 RefPtr<Image> image =
550 GetOwner() && GetOwner()->ImageAvailable() ? GetImage() : nullptr;
551 if (image) {
552 return image->LockImage();
554 return NS_OK;
557 NS_IMETHODIMP
558 imgRequestProxy::UnlockImage() {
559 MOZ_ASSERT(mLockCount > 0, "calling unlock but no locks!");
561 mLockCount--;
562 RefPtr<Image> image =
563 GetOwner() && GetOwner()->ImageAvailable() ? GetImage() : nullptr;
564 if (image) {
565 return image->UnlockImage();
567 return NS_OK;
570 NS_IMETHODIMP
571 imgRequestProxy::RequestDiscard() {
572 RefPtr<Image> image = GetImage();
573 if (image) {
574 return image->RequestDiscard();
576 return NS_OK;
579 NS_IMETHODIMP
580 imgRequestProxy::IncrementAnimationConsumers() {
581 mAnimationConsumers++;
582 RefPtr<Image> image =
583 GetOwner() && GetOwner()->ImageAvailable() ? GetImage() : nullptr;
584 if (image) {
585 image->IncrementAnimationConsumers();
587 return NS_OK;
590 NS_IMETHODIMP
591 imgRequestProxy::DecrementAnimationConsumers() {
592 // We may get here if some responsible code called Increment,
593 // then called us, but we have meanwhile called ClearAnimationConsumers
594 // because we needed to get rid of them earlier (see
595 // imgRequest::RemoveProxy), and hence have nothing left to
596 // decrement. (In such a case we got rid of the animation consumers
597 // early, but not the observer.)
598 if (mAnimationConsumers > 0) {
599 mAnimationConsumers--;
600 RefPtr<Image> image =
601 GetOwner() && GetOwner()->ImageAvailable() ? GetImage() : nullptr;
602 if (image) {
603 image->DecrementAnimationConsumers();
606 return NS_OK;
609 void imgRequestProxy::ClearAnimationConsumers() {
610 while (mAnimationConsumers > 0) {
611 DecrementAnimationConsumers();
615 NS_IMETHODIMP
616 imgRequestProxy::Suspend() { return NS_ERROR_NOT_IMPLEMENTED; }
618 NS_IMETHODIMP
619 imgRequestProxy::Resume() { return NS_ERROR_NOT_IMPLEMENTED; }
621 NS_IMETHODIMP
622 imgRequestProxy::GetLoadGroup(nsILoadGroup** loadGroup) {
623 NS_IF_ADDREF(*loadGroup = mLoadGroup.get());
624 return NS_OK;
626 NS_IMETHODIMP
627 imgRequestProxy::SetLoadGroup(nsILoadGroup* loadGroup) {
628 if (loadGroup != mLoadGroup) {
629 MOZ_ASSERT_UNREACHABLE("Switching load groups is unsupported!");
630 return NS_ERROR_NOT_IMPLEMENTED;
632 return NS_OK;
635 NS_IMETHODIMP
636 imgRequestProxy::GetLoadFlags(nsLoadFlags* flags) {
637 *flags = mLoadFlags;
638 return NS_OK;
640 NS_IMETHODIMP
641 imgRequestProxy::SetLoadFlags(nsLoadFlags flags) {
642 mLoadFlags = flags;
643 return NS_OK;
646 NS_IMETHODIMP
647 imgRequestProxy::GetTRRMode(nsIRequest::TRRMode* aTRRMode) {
648 return GetTRRModeImpl(aTRRMode);
651 NS_IMETHODIMP
652 imgRequestProxy::SetTRRMode(nsIRequest::TRRMode aTRRMode) {
653 return SetTRRModeImpl(aTRRMode);
656 /** imgIRequest methods **/
658 NS_IMETHODIMP
659 imgRequestProxy::GetImage(imgIContainer** aImage) {
660 NS_ENSURE_TRUE(aImage, NS_ERROR_NULL_POINTER);
661 // It's possible that our owner has an image but hasn't notified us of it -
662 // that'll happen if we get Canceled before the owner instantiates its image
663 // (because Canceling unregisters us as a listener on mOwner). If we're
664 // in that situation, just grab the image off of mOwner.
665 RefPtr<Image> image = GetImage();
666 nsCOMPtr<imgIContainer> imageToReturn;
667 if (image) {
668 imageToReturn = image;
670 if (!imageToReturn && GetOwner()) {
671 imageToReturn = GetOwner()->GetImage();
673 if (!imageToReturn) {
674 return NS_ERROR_FAILURE;
677 imageToReturn.swap(*aImage);
679 return NS_OK;
682 NS_IMETHODIMP
683 imgRequestProxy::GetProducerId(uint32_t* aId) {
684 NS_ENSURE_TRUE(aId, NS_ERROR_NULL_POINTER);
686 nsCOMPtr<imgIContainer> image;
687 nsresult rv = GetImage(getter_AddRefs(image));
688 if (NS_SUCCEEDED(rv)) {
689 *aId = image->GetProducerId();
690 } else {
691 *aId = layers::kContainerProducerID_Invalid;
694 return NS_OK;
697 NS_IMETHODIMP
698 imgRequestProxy::GetImageStatus(uint32_t* aStatus) {
699 if (IsValidating()) {
700 // We are currently validating the image, and so our status could revert if
701 // we discard the cache. We should also be deferring notifications, such
702 // that the caller will be notified when validation completes. Rather than
703 // risk misleading the caller, return nothing.
704 *aStatus = imgIRequest::STATUS_NONE;
705 } else {
706 RefPtr<ProgressTracker> progressTracker = GetProgressTracker();
707 *aStatus = progressTracker->GetImageStatus();
710 return NS_OK;
713 NS_IMETHODIMP
714 imgRequestProxy::GetImageErrorCode(nsresult* aStatus) {
715 if (!GetOwner()) {
716 return NS_ERROR_FAILURE;
719 *aStatus = GetOwner()->GetImageErrorCode();
721 return NS_OK;
724 NS_IMETHODIMP
725 imgRequestProxy::GetURI(nsIURI** aURI) {
726 MOZ_ASSERT(NS_IsMainThread(), "Must be on main thread to convert URI");
727 nsCOMPtr<nsIURI> uri = mURI;
728 uri.forget(aURI);
729 return NS_OK;
732 nsresult imgRequestProxy::GetFinalURI(nsIURI** aURI) {
733 if (!GetOwner()) {
734 return NS_ERROR_FAILURE;
737 return GetOwner()->GetFinalURI(aURI);
740 NS_IMETHODIMP
741 imgRequestProxy::GetNotificationObserver(imgINotificationObserver** aObserver) {
742 *aObserver = mListener;
743 NS_IF_ADDREF(*aObserver);
744 return NS_OK;
747 NS_IMETHODIMP
748 imgRequestProxy::GetMimeType(char** aMimeType) {
749 if (!GetOwner()) {
750 return NS_ERROR_FAILURE;
753 const char* type = GetOwner()->GetMimeType();
754 if (!type) {
755 return NS_ERROR_FAILURE;
758 *aMimeType = NS_xstrdup(type);
760 return NS_OK;
763 imgRequestProxy* imgRequestProxy::NewClonedProxy() {
764 return new imgRequestProxy();
767 NS_IMETHODIMP
768 imgRequestProxy::Clone(imgINotificationObserver* aObserver,
769 imgIRequest** aClone) {
770 nsresult result;
771 imgRequestProxy* proxy;
772 result = PerformClone(aObserver, nullptr, /* aSyncNotify */ true, &proxy);
773 *aClone = proxy;
774 return result;
777 nsresult imgRequestProxy::SyncClone(imgINotificationObserver* aObserver,
778 Document* aLoadingDocument,
779 imgRequestProxy** aClone) {
780 return PerformClone(aObserver, aLoadingDocument,
781 /* aSyncNotify */ true, aClone);
784 nsresult imgRequestProxy::Clone(imgINotificationObserver* aObserver,
785 Document* aLoadingDocument,
786 imgRequestProxy** aClone) {
787 return PerformClone(aObserver, aLoadingDocument,
788 /* aSyncNotify */ false, aClone);
791 nsresult imgRequestProxy::PerformClone(imgINotificationObserver* aObserver,
792 Document* aLoadingDocument,
793 bool aSyncNotify,
794 imgRequestProxy** aClone) {
795 MOZ_ASSERT(aClone, "Null out param");
797 LOG_SCOPE(gImgLog, "imgRequestProxy::Clone");
799 *aClone = nullptr;
800 RefPtr<imgRequestProxy> clone = NewClonedProxy();
802 nsCOMPtr<nsILoadGroup> loadGroup;
803 if (aLoadingDocument) {
804 loadGroup = aLoadingDocument->GetDocumentLoadGroup();
807 // It is important to call |SetLoadFlags()| before calling |Init()| because
808 // |Init()| adds the request to the loadgroup.
809 // When a request is added to a loadgroup, its load flags are merged
810 // with the load flags of the loadgroup.
811 // XXXldb That's not true anymore. Stuff from imgLoader adds the
812 // request to the loadgroup.
813 clone->SetLoadFlags(mLoadFlags);
814 nsresult rv = clone->Init(mBehaviour->GetOwner(), loadGroup, aLoadingDocument,
815 mURI, aObserver);
816 if (NS_FAILED(rv)) {
817 return rv;
820 // Assign to *aClone before calling Notify so that if the caller expects to
821 // only be notified for requests it's already holding pointers to it won't be
822 // surprised.
823 NS_ADDREF(*aClone = clone);
825 imgCacheValidator* validator = GetValidator();
826 if (validator) {
827 // Note that if we have a validator, we don't want to issue notifications at
828 // here because we want to defer until that completes. AddProxy will add us
829 // to the load group; we cannot avoid that in this case, because we don't
830 // know when the validation will complete, and if it will cause us to
831 // discard our cached state anyways. We are probably already blocked by the
832 // original LoadImage(WithChannel) request in any event.
833 clone->MarkValidating();
834 validator->AddProxy(clone);
835 } else {
836 // We only want to add the request to the load group of the owning document
837 // if it is still in progress. Some callers cannot handle a supurious load
838 // group removal (e.g. print preview) so we must be careful. On the other
839 // hand, if after cloning, the original request proxy is cancelled /
840 // destroyed, we need to ensure that any clones still block the load group
841 // if it is incomplete.
842 bool addToLoadGroup = mIsInLoadGroup;
843 if (!addToLoadGroup) {
844 RefPtr<ProgressTracker> tracker = clone->GetProgressTracker();
845 addToLoadGroup =
846 tracker && !(tracker->GetProgress() & FLAG_LOAD_COMPLETE);
849 if (addToLoadGroup) {
850 clone->AddToLoadGroup();
853 if (aSyncNotify) {
854 // This is wrong!!! We need to notify asynchronously, but there's code
855 // that assumes that we don't. This will be fixed in bug 580466. Note that
856 // if we have a validator, we won't issue notifications anyways because
857 // they are deferred, so there is no point in requesting.
858 clone->mForceDispatchLoadGroup = true;
859 clone->SyncNotifyListener();
860 clone->mForceDispatchLoadGroup = false;
861 } else {
862 // Without a validator, we can request asynchronous notifications
863 // immediately. If there was a validator, this would override the deferral
864 // and that would be incorrect.
865 clone->NotifyListener();
869 return NS_OK;
872 NS_IMETHODIMP
873 imgRequestProxy::GetImagePrincipal(nsIPrincipal** aPrincipal) {
874 if (!GetOwner()) {
875 return NS_ERROR_FAILURE;
878 nsCOMPtr<nsIPrincipal> principal = GetOwner()->GetPrincipal();
879 principal.forget(aPrincipal);
880 return NS_OK;
883 NS_IMETHODIMP
884 imgRequestProxy::GetHadCrossOriginRedirects(bool* aHadCrossOriginRedirects) {
885 *aHadCrossOriginRedirects = false;
887 nsCOMPtr<nsITimedChannel> timedChannel = TimedChannel();
888 if (timedChannel) {
889 bool allRedirectsSameOrigin = false;
890 *aHadCrossOriginRedirects =
891 NS_SUCCEEDED(
892 timedChannel->GetAllRedirectsSameOrigin(&allRedirectsSameOrigin)) &&
893 !allRedirectsSameOrigin;
896 return NS_OK;
899 NS_IMETHODIMP
900 imgRequestProxy::GetMultipart(bool* aMultipart) {
901 if (!GetOwner()) {
902 return NS_ERROR_FAILURE;
905 *aMultipart = GetOwner()->GetMultipart();
907 return NS_OK;
910 NS_IMETHODIMP
911 imgRequestProxy::GetCORSMode(int32_t* aCorsMode) {
912 if (!GetOwner()) {
913 return NS_ERROR_FAILURE;
916 *aCorsMode = GetOwner()->GetCORSMode();
918 return NS_OK;
921 NS_IMETHODIMP
922 imgRequestProxy::BoostPriority(uint32_t aCategory) {
923 NS_ENSURE_STATE(GetOwner() && !mCanceled);
924 GetOwner()->BoostPriority(aCategory);
925 return NS_OK;
928 /** nsISupportsPriority methods **/
930 NS_IMETHODIMP
931 imgRequestProxy::GetPriority(int32_t* priority) {
932 NS_ENSURE_STATE(GetOwner());
933 *priority = GetOwner()->Priority();
934 return NS_OK;
937 NS_IMETHODIMP
938 imgRequestProxy::SetPriority(int32_t priority) {
939 NS_ENSURE_STATE(GetOwner() && !mCanceled);
940 GetOwner()->AdjustPriority(this, priority - GetOwner()->Priority());
941 return NS_OK;
944 NS_IMETHODIMP
945 imgRequestProxy::AdjustPriority(int32_t priority) {
946 // We don't require |!mCanceled| here. This may be called even if we're
947 // cancelled, because it's invoked as part of the process of removing an image
948 // from the load group.
949 NS_ENSURE_STATE(GetOwner());
950 GetOwner()->AdjustPriority(this, priority);
951 return NS_OK;
954 static const char* NotificationTypeToString(int32_t aType) {
955 switch (aType) {
956 case imgINotificationObserver::SIZE_AVAILABLE:
957 return "SIZE_AVAILABLE";
958 case imgINotificationObserver::FRAME_UPDATE:
959 return "FRAME_UPDATE";
960 case imgINotificationObserver::FRAME_COMPLETE:
961 return "FRAME_COMPLETE";
962 case imgINotificationObserver::LOAD_COMPLETE:
963 return "LOAD_COMPLETE";
964 case imgINotificationObserver::DECODE_COMPLETE:
965 return "DECODE_COMPLETE";
966 case imgINotificationObserver::DISCARD:
967 return "DISCARD";
968 case imgINotificationObserver::UNLOCKED_DRAW:
969 return "UNLOCKED_DRAW";
970 case imgINotificationObserver::IS_ANIMATED:
971 return "IS_ANIMATED";
972 case imgINotificationObserver::HAS_TRANSPARENCY:
973 return "HAS_TRANSPARENCY";
974 default:
975 MOZ_ASSERT_UNREACHABLE("Notification list should be exhaustive");
976 return "(unknown notification)";
980 void imgRequestProxy::Notify(int32_t aType,
981 const mozilla::gfx::IntRect* aRect) {
982 MOZ_ASSERT(aType != imgINotificationObserver::LOAD_COMPLETE,
983 "Should call OnLoadComplete");
985 LOG_FUNC_WITH_PARAM(gImgLog, "imgRequestProxy::Notify", "type",
986 NotificationTypeToString(aType));
988 if (!mListener || mCanceled) {
989 return;
992 if (!IsOnEventTarget()) {
993 RefPtr<imgRequestProxy> self(this);
994 if (aRect) {
995 const mozilla::gfx::IntRect rect = *aRect;
996 DispatchWithTarget(NS_NewRunnableFunction(
997 "imgRequestProxy::Notify",
998 [self, rect, aType]() -> void { self->Notify(aType, &rect); }));
999 } else {
1000 DispatchWithTarget(NS_NewRunnableFunction(
1001 "imgRequestProxy::Notify",
1002 [self, aType]() -> void { self->Notify(aType, nullptr); }));
1004 return;
1007 // Make sure the listener stays alive while we notify.
1008 nsCOMPtr<imgINotificationObserver> listener(mListener);
1010 listener->Notify(this, aType, aRect);
1013 void imgRequestProxy::OnLoadComplete(bool aLastPart) {
1014 LOG_FUNC_WITH_PARAM(gImgLog, "imgRequestProxy::OnLoadComplete", "uri", mURI);
1016 // There's all sorts of stuff here that could kill us (the OnStopRequest call
1017 // on the listener, the removal from the loadgroup, the release of the
1018 // listener, etc). Don't let them do it.
1019 RefPtr<imgRequestProxy> self(this);
1021 if (!IsOnEventTarget()) {
1022 DispatchWithTarget(NS_NewRunnableFunction(
1023 "imgRequestProxy::OnLoadComplete",
1024 [self, aLastPart]() -> void { self->OnLoadComplete(aLastPart); }));
1025 return;
1028 if (mListener && !mCanceled) {
1029 // Hold a ref to the listener while we call it, just in case.
1030 nsCOMPtr<imgINotificationObserver> listener(mListener);
1031 listener->Notify(this, imgINotificationObserver::LOAD_COMPLETE, nullptr);
1034 // If we're expecting more data from a multipart channel, re-add ourself
1035 // to the loadgroup so that the document doesn't lose track of the load.
1036 // If the request is already a background request and there's more data
1037 // coming, we can just leave the request in the loadgroup as-is.
1038 if (aLastPart || (mLoadFlags & nsIRequest::LOAD_BACKGROUND) == 0) {
1039 if (aLastPart) {
1040 RemoveFromLoadGroup();
1041 } else {
1042 // More data is coming, so change the request to be a background request
1043 // and put it back in the loadgroup.
1044 MoveToBackgroundInLoadGroup();
1048 if (mListenerIsStrongRef && aLastPart) {
1049 MOZ_ASSERT(mListener, "How did that happen?");
1050 // Drop our strong ref to the listener now that we're done with
1051 // everything. Note that this can cancel us and other fun things
1052 // like that. Don't add anything in this method after this point.
1053 imgINotificationObserver* obs = mListener;
1054 mListenerIsStrongRef = false;
1055 NS_RELEASE(obs);
1059 void imgRequestProxy::NullOutListener() {
1060 // If we have animation consumers, then they don't matter anymore
1061 if (mListener) {
1062 ClearAnimationConsumers();
1065 if (mListenerIsStrongRef) {
1066 // Releasing could do weird reentery stuff, so just play it super-safe
1067 nsCOMPtr<imgINotificationObserver> obs;
1068 obs.swap(mListener);
1069 mListenerIsStrongRef = false;
1070 } else {
1071 mListener = nullptr;
1074 // Note that we don't free the event target. We actually need that to ensure
1075 // we get removed from the ProgressTracker properly. No harm in keeping it
1076 // however.
1077 mTabGroup = nullptr;
1080 NS_IMETHODIMP
1081 imgRequestProxy::GetStaticRequest(imgIRequest** aReturn) {
1082 imgRequestProxy* proxy;
1083 nsresult result = GetStaticRequest(nullptr, &proxy);
1084 *aReturn = proxy;
1085 return result;
1088 nsresult imgRequestProxy::GetStaticRequest(Document* aLoadingDocument,
1089 imgRequestProxy** aReturn) {
1090 *aReturn = nullptr;
1091 RefPtr<Image> image = GetImage();
1093 bool animated;
1094 if (!image || (NS_SUCCEEDED(image->GetAnimated(&animated)) && !animated)) {
1095 // Early exit - we're not animated, so we don't have to do anything.
1096 NS_ADDREF(*aReturn = this);
1097 return NS_OK;
1100 // Check for errors in the image. Callers code rely on GetStaticRequest
1101 // failing in this case, though with FrozenImage there's no technical reason
1102 // for it anymore.
1103 if (image->HasError()) {
1104 return NS_ERROR_FAILURE;
1107 // We are animated. We need to create a frozen version of this image.
1108 RefPtr<Image> frozenImage = ImageOps::Freeze(image);
1110 // Create a static imgRequestProxy with our new extracted frame.
1111 nsCOMPtr<nsIPrincipal> currentPrincipal;
1112 GetImagePrincipal(getter_AddRefs(currentPrincipal));
1113 bool hadCrossOriginRedirects = true;
1114 GetHadCrossOriginRedirects(&hadCrossOriginRedirects);
1115 RefPtr<imgRequestProxy> req = new imgRequestProxyStatic(
1116 frozenImage, currentPrincipal, hadCrossOriginRedirects);
1117 req->Init(nullptr, nullptr, aLoadingDocument, mURI, nullptr);
1119 NS_ADDREF(*aReturn = req);
1121 return NS_OK;
1124 void imgRequestProxy::NotifyListener() {
1125 // It would be nice to notify the observer directly in the status tracker
1126 // instead of through the proxy, but there are several places we do extra
1127 // processing when we receive notifications (like OnStopRequest()), and we
1128 // need to check mCanceled everywhere too.
1130 RefPtr<ProgressTracker> progressTracker = GetProgressTracker();
1131 if (GetOwner()) {
1132 // Send the notifications to our listener asynchronously.
1133 progressTracker->Notify(this);
1134 } else {
1135 // We don't have an imgRequest, so we can only notify the clone of our
1136 // current state, but we still have to do that asynchronously.
1137 MOZ_ASSERT(HasImage(), "if we have no imgRequest, we should have an Image");
1138 progressTracker->NotifyCurrentState(this);
1142 void imgRequestProxy::SyncNotifyListener() {
1143 // It would be nice to notify the observer directly in the status tracker
1144 // instead of through the proxy, but there are several places we do extra
1145 // processing when we receive notifications (like OnStopRequest()), and we
1146 // need to check mCanceled everywhere too.
1148 RefPtr<ProgressTracker> progressTracker = GetProgressTracker();
1149 progressTracker->SyncNotify(this);
1152 void imgRequestProxy::SetHasImage() {
1153 RefPtr<ProgressTracker> progressTracker = GetProgressTracker();
1154 MOZ_ASSERT(progressTracker);
1155 RefPtr<Image> image = progressTracker->GetImage();
1156 MOZ_ASSERT(image);
1158 // Force any private status related to the owner to reflect
1159 // the presence of an image;
1160 mBehaviour->SetOwner(mBehaviour->GetOwner());
1162 // Apply any locks we have
1163 for (uint32_t i = 0; i < mLockCount; ++i) {
1164 image->LockImage();
1167 // Apply any animation consumers we have
1168 for (uint32_t i = 0; i < mAnimationConsumers; i++) {
1169 image->IncrementAnimationConsumers();
1173 already_AddRefed<ProgressTracker> imgRequestProxy::GetProgressTracker() const {
1174 return mBehaviour->GetProgressTracker();
1177 already_AddRefed<mozilla::image::Image> imgRequestProxy::GetImage() const {
1178 return mBehaviour->GetImage();
1181 bool RequestBehaviour::HasImage() const {
1182 if (!mOwnerHasImage) {
1183 return false;
1185 RefPtr<ProgressTracker> progressTracker = GetProgressTracker();
1186 return progressTracker ? progressTracker->HasImage() : false;
1189 bool imgRequestProxy::HasImage() const { return mBehaviour->HasImage(); }
1191 imgRequest* imgRequestProxy::GetOwner() const { return mBehaviour->GetOwner(); }
1193 imgCacheValidator* imgRequestProxy::GetValidator() const {
1194 imgRequest* owner = GetOwner();
1195 if (!owner) {
1196 return nullptr;
1198 return owner->GetValidator();
1201 ////////////////// imgRequestProxyStatic methods
1203 class StaticBehaviour : public ProxyBehaviour {
1204 public:
1205 explicit StaticBehaviour(mozilla::image::Image* aImage) : mImage(aImage) {}
1207 already_AddRefed<mozilla::image::Image> GetImage() const override {
1208 RefPtr<mozilla::image::Image> image = mImage;
1209 return image.forget();
1212 bool HasImage() const override { return mImage; }
1214 already_AddRefed<ProgressTracker> GetProgressTracker() const override {
1215 return mImage->GetProgressTracker();
1218 imgRequest* GetOwner() const override { return nullptr; }
1220 void SetOwner(imgRequest* aOwner) override {
1221 MOZ_ASSERT(!aOwner,
1222 "We shouldn't be giving static requests a non-null owner.");
1225 private:
1226 // Our image. We have to hold a strong reference here, because that's normally
1227 // the job of the underlying request.
1228 RefPtr<mozilla::image::Image> mImage;
1231 imgRequestProxyStatic::imgRequestProxyStatic(mozilla::image::Image* aImage,
1232 nsIPrincipal* aPrincipal,
1233 bool aHadCrossOriginRedirects)
1234 : mPrincipal(aPrincipal),
1235 mHadCrossOriginRedirects(aHadCrossOriginRedirects) {
1236 mBehaviour = mozilla::MakeUnique<StaticBehaviour>(aImage);
1239 NS_IMETHODIMP
1240 imgRequestProxyStatic::GetImagePrincipal(nsIPrincipal** aPrincipal) {
1241 if (!mPrincipal) {
1242 return NS_ERROR_FAILURE;
1245 NS_ADDREF(*aPrincipal = mPrincipal);
1247 return NS_OK;
1250 NS_IMETHODIMP
1251 imgRequestProxyStatic::GetHadCrossOriginRedirects(
1252 bool* aHadCrossOriginRedirects) {
1253 *aHadCrossOriginRedirects = mHadCrossOriginRedirects;
1254 return NS_OK;
1257 imgRequestProxy* imgRequestProxyStatic::NewClonedProxy() {
1258 nsCOMPtr<nsIPrincipal> currentPrincipal;
1259 GetImagePrincipal(getter_AddRefs(currentPrincipal));
1260 bool hadCrossOriginRedirects = true;
1261 GetHadCrossOriginRedirects(&hadCrossOriginRedirects);
1262 RefPtr<mozilla::image::Image> image = GetImage();
1263 return new imgRequestProxyStatic(image, currentPrincipal,
1264 hadCrossOriginRedirects);