Bug 1669129 - [devtools] Enable devtools.overflow.debugging.enabled. r=jdescottes
[gecko.git] / image / imgRequestProxy.cpp
blob3fb2f3a15f27de4c5dc23a3482bc67542a0d16d0
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 "nsCRTGlue.h"
20 #include "nsError.h"
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, 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 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 already_AddRefed<nsIEventTarget> imgRequestProxy::GetEventTarget() const {
254 nsCOMPtr<nsIEventTarget> target(mEventTarget);
255 return target.forget();
258 nsresult imgRequestProxy::DispatchWithTargetIfAvailable(
259 already_AddRefed<nsIRunnable> aEvent) {
260 LOG_FUNC(gImgLog, "imgRequestProxy::DispatchWithTargetIfAvailable");
262 // This method should only be used when it is *expected* that we are
263 // dispatching an event (e.g. we want to handle an event asynchronously)
264 // rather we need to (e.g. we are in the wrong scheduler group context).
265 // As such, we do not set mHadDispatch for telemetry purposes.
266 if (mEventTarget) {
267 mEventTarget->Dispatch(CreateMediumHighRunnable(std::move(aEvent)),
268 NS_DISPATCH_NORMAL);
269 return NS_OK;
272 return NS_DispatchToMainThread(CreateMediumHighRunnable(std::move(aEvent)));
275 void imgRequestProxy::AddToOwner(Document* aLoadingDocument) {
276 // An imgRequestProxy can be initialized with neither a listener nor a
277 // document. The caller could follow up later by cloning the canonical
278 // imgRequestProxy with the actual listener. This is possible because
279 // imgLoader::LoadImage does not require a valid listener to be provided.
281 // Without a listener, we don't need to set our scheduler group, because
282 // we have nothing to signal. However if we were told what document this
283 // is for, it is likely that future listeners will belong to the same
284 // scheduler group.
286 // With a listener, we always need to update our scheduler group. A null
287 // scheduler group is valid with or without a document, but that means
288 // we will use the most generic event target possible on dispatch.
289 if (aLoadingDocument) {
290 RefPtr<mozilla::dom::DocGroup> docGroup = aLoadingDocument->GetDocGroup();
291 if (docGroup) {
292 mEventTarget = docGroup->EventTargetFor(mozilla::TaskCategory::Other);
293 MOZ_ASSERT(mEventTarget);
297 if (mListener && !mEventTarget) {
298 mEventTarget = do_GetMainThread();
301 imgRequest* owner = GetOwner();
302 if (!owner) {
303 return;
306 owner->AddProxy(this);
309 void imgRequestProxy::RemoveFromOwner(nsresult aStatus) {
310 imgRequest* owner = GetOwner();
311 if (owner) {
312 if (mValidating) {
313 imgCacheValidator* validator = owner->GetValidator();
314 MOZ_ASSERT(validator);
315 validator->RemoveProxy(this);
316 mValidating = false;
319 owner->RemoveProxy(this, aStatus);
323 void imgRequestProxy::AddToLoadGroup() {
324 NS_ASSERTION(!mIsInLoadGroup, "Whaa, we're already in the loadgroup!");
325 MOZ_ASSERT(!mForceDispatchLoadGroup);
327 /* While in theory there could be a dispatch outstanding to remove this
328 request from the load group, in practice we only add to the load group
329 (when previously not in a load group) at initialization. */
330 if (!mIsInLoadGroup && mLoadGroup) {
331 LOG_FUNC(gImgLog, "imgRequestProxy::AddToLoadGroup");
332 mLoadGroup->AddRequest(this, nullptr);
333 mIsInLoadGroup = true;
337 void imgRequestProxy::RemoveFromLoadGroup() {
338 if (!mIsInLoadGroup || !mLoadGroup) {
339 return;
342 /* Sometimes we may not be able to remove ourselves from the load group in
343 the current context. This is because our listeners are not re-entrant (e.g.
344 we are in the middle of CancelAndForgetObserver or SyncClone). */
345 if (mForceDispatchLoadGroup) {
346 LOG_FUNC(gImgLog, "imgRequestProxy::RemoveFromLoadGroup -- dispatch");
348 /* We take away the load group from the request temporarily; this prevents
349 additional dispatches via RemoveFromLoadGroup occurring, as well as
350 MoveToBackgroundInLoadGroup from removing and readding. This is safe
351 because we know that once we get here, blocking the load group at all is
352 unnecessary. */
353 mIsInLoadGroup = false;
354 nsCOMPtr<nsILoadGroup> loadGroup = std::move(mLoadGroup);
355 RefPtr<imgRequestProxy> self(this);
356 DispatchWithTargetIfAvailable(NS_NewRunnableFunction(
357 "imgRequestProxy::RemoveFromLoadGroup", [self, loadGroup]() -> void {
358 loadGroup->RemoveRequest(self, nullptr, NS_OK);
359 }));
360 return;
363 LOG_FUNC(gImgLog, "imgRequestProxy::RemoveFromLoadGroup");
365 /* calling RemoveFromLoadGroup may cause the document to finish
366 loading, which could result in our death. We need to make sure
367 that we stay alive long enough to fight another battle... at
368 least until we exit this function. */
369 nsCOMPtr<imgIRequest> kungFuDeathGrip(this);
370 mLoadGroup->RemoveRequest(this, nullptr, NS_OK);
371 mLoadGroup = nullptr;
372 mIsInLoadGroup = false;
375 void imgRequestProxy::MoveToBackgroundInLoadGroup() {
376 /* Even if we are still in the load group, we may have taken away the load
377 group reference itself because we are in the process of leaving the group.
378 In that case, there is no need to background the request. */
379 if (!mLoadGroup) {
380 return;
383 /* There is no need to dispatch if we only need to add ourselves to the load
384 group without removal. It is the removal which causes the problematic
385 callbacks (see RemoveFromLoadGroup). */
386 if (mIsInLoadGroup && mForceDispatchLoadGroup) {
387 LOG_FUNC(gImgLog,
388 "imgRequestProxy::MoveToBackgroundInLoadGroup -- dispatch");
390 RefPtr<imgRequestProxy> self(this);
391 DispatchWithTargetIfAvailable(NS_NewRunnableFunction(
392 "imgRequestProxy::MoveToBackgroundInLoadGroup",
393 [self]() -> void { self->MoveToBackgroundInLoadGroup(); }));
394 return;
397 LOG_FUNC(gImgLog, "imgRequestProxy::MoveToBackgroundInLoadGroup");
398 nsCOMPtr<imgIRequest> kungFuDeathGrip(this);
399 if (mIsInLoadGroup) {
400 mLoadGroup->RemoveRequest(this, nullptr, NS_OK);
403 mLoadFlags |= nsIRequest::LOAD_BACKGROUND;
404 mLoadGroup->AddRequest(this, nullptr);
407 /** nsIRequest / imgIRequest methods **/
409 NS_IMETHODIMP
410 imgRequestProxy::GetName(nsACString& aName) {
411 aName.Truncate();
413 if (mURI) {
414 mURI->GetSpec(aName);
417 return NS_OK;
420 NS_IMETHODIMP
421 imgRequestProxy::IsPending(bool* _retval) { return NS_ERROR_NOT_IMPLEMENTED; }
423 NS_IMETHODIMP
424 imgRequestProxy::GetStatus(nsresult* aStatus) {
425 return NS_ERROR_NOT_IMPLEMENTED;
428 NS_IMETHODIMP
429 imgRequestProxy::Cancel(nsresult status) {
430 if (mCanceled) {
431 return NS_ERROR_FAILURE;
434 LOG_SCOPE(gImgLog, "imgRequestProxy::Cancel");
436 mCanceled = true;
438 nsCOMPtr<nsIRunnable> ev = new imgCancelRunnable(this, status);
439 return DispatchWithTargetIfAvailable(ev.forget());
442 void imgRequestProxy::DoCancel(nsresult status) {
443 RemoveFromOwner(status);
444 RemoveFromLoadGroup();
445 NullOutListener();
448 NS_IMETHODIMP
449 imgRequestProxy::CancelAndForgetObserver(nsresult aStatus) {
450 // If mCanceled is true but mListener is non-null, that means
451 // someone called Cancel() on us but the imgCancelRunnable is still
452 // pending. We still need to null out mListener before returning
453 // from this function in this case. That means we want to do the
454 // RemoveProxy call right now, because we need to deliver the
455 // onStopRequest.
456 if (mCanceled && !mListener) {
457 return NS_ERROR_FAILURE;
460 LOG_SCOPE(gImgLog, "imgRequestProxy::CancelAndForgetObserver");
462 mCanceled = true;
463 mForceDispatchLoadGroup = true;
464 RemoveFromOwner(aStatus);
465 RemoveFromLoadGroup();
466 mForceDispatchLoadGroup = false;
468 NullOutListener();
470 return NS_OK;
473 NS_IMETHODIMP
474 imgRequestProxy::StartDecoding(uint32_t aFlags) {
475 // Flag this, so we know to request after validation if pending.
476 if (IsValidating()) {
477 mDecodeRequested = true;
478 return NS_OK;
481 RefPtr<Image> image = GetImage();
482 if (image) {
483 return image->StartDecoding(aFlags);
486 if (GetOwner()) {
487 GetOwner()->StartDecoding();
490 return NS_OK;
493 bool imgRequestProxy::StartDecodingWithResult(uint32_t aFlags) {
494 // Flag this, so we know to request after validation if pending.
495 if (IsValidating()) {
496 mDecodeRequested = true;
497 return false;
500 RefPtr<Image> image = GetImage();
501 if (image) {
502 return image->StartDecodingWithResult(aFlags);
505 if (GetOwner()) {
506 GetOwner()->StartDecoding();
509 return false;
512 imgIContainer::DecodeResult imgRequestProxy::RequestDecodeWithResult(
513 uint32_t aFlags) {
514 if (IsValidating()) {
515 mDecodeRequested = true;
516 return imgIContainer::DECODE_REQUESTED;
519 RefPtr<Image> image = GetImage();
520 if (image) {
521 return image->RequestDecodeWithResult(aFlags);
524 if (GetOwner()) {
525 GetOwner()->StartDecoding();
528 return imgIContainer::DECODE_REQUESTED;
531 NS_IMETHODIMP
532 imgRequestProxy::LockImage() {
533 mLockCount++;
534 RefPtr<Image> image =
535 GetOwner() && GetOwner()->ImageAvailable() ? GetImage() : nullptr;
536 if (image) {
537 return image->LockImage();
539 return NS_OK;
542 NS_IMETHODIMP
543 imgRequestProxy::UnlockImage() {
544 MOZ_ASSERT(mLockCount > 0, "calling unlock but no locks!");
546 mLockCount--;
547 RefPtr<Image> image =
548 GetOwner() && GetOwner()->ImageAvailable() ? GetImage() : nullptr;
549 if (image) {
550 return image->UnlockImage();
552 return NS_OK;
555 NS_IMETHODIMP
556 imgRequestProxy::RequestDiscard() {
557 RefPtr<Image> image = GetImage();
558 if (image) {
559 return image->RequestDiscard();
561 return NS_OK;
564 NS_IMETHODIMP
565 imgRequestProxy::IncrementAnimationConsumers() {
566 mAnimationConsumers++;
567 RefPtr<Image> image =
568 GetOwner() && GetOwner()->ImageAvailable() ? GetImage() : nullptr;
569 if (image) {
570 image->IncrementAnimationConsumers();
572 return NS_OK;
575 NS_IMETHODIMP
576 imgRequestProxy::DecrementAnimationConsumers() {
577 // We may get here if some responsible code called Increment,
578 // then called us, but we have meanwhile called ClearAnimationConsumers
579 // because we needed to get rid of them earlier (see
580 // imgRequest::RemoveProxy), and hence have nothing left to
581 // decrement. (In such a case we got rid of the animation consumers
582 // early, but not the observer.)
583 if (mAnimationConsumers > 0) {
584 mAnimationConsumers--;
585 RefPtr<Image> image =
586 GetOwner() && GetOwner()->ImageAvailable() ? GetImage() : nullptr;
587 if (image) {
588 image->DecrementAnimationConsumers();
591 return NS_OK;
594 void imgRequestProxy::ClearAnimationConsumers() {
595 while (mAnimationConsumers > 0) {
596 DecrementAnimationConsumers();
600 NS_IMETHODIMP
601 imgRequestProxy::Suspend() { return NS_ERROR_NOT_IMPLEMENTED; }
603 NS_IMETHODIMP
604 imgRequestProxy::Resume() { return NS_ERROR_NOT_IMPLEMENTED; }
606 NS_IMETHODIMP
607 imgRequestProxy::GetLoadGroup(nsILoadGroup** loadGroup) {
608 NS_IF_ADDREF(*loadGroup = mLoadGroup.get());
609 return NS_OK;
611 NS_IMETHODIMP
612 imgRequestProxy::SetLoadGroup(nsILoadGroup* loadGroup) {
613 if (loadGroup != mLoadGroup) {
614 MOZ_ASSERT_UNREACHABLE("Switching load groups is unsupported!");
615 return NS_ERROR_NOT_IMPLEMENTED;
617 return NS_OK;
620 NS_IMETHODIMP
621 imgRequestProxy::GetLoadFlags(nsLoadFlags* flags) {
622 *flags = mLoadFlags;
623 return NS_OK;
625 NS_IMETHODIMP
626 imgRequestProxy::SetLoadFlags(nsLoadFlags flags) {
627 mLoadFlags = flags;
628 return NS_OK;
631 NS_IMETHODIMP
632 imgRequestProxy::GetTRRMode(nsIRequest::TRRMode* aTRRMode) {
633 return GetTRRModeImpl(aTRRMode);
636 NS_IMETHODIMP
637 imgRequestProxy::SetTRRMode(nsIRequest::TRRMode aTRRMode) {
638 return SetTRRModeImpl(aTRRMode);
641 /** imgIRequest methods **/
643 NS_IMETHODIMP
644 imgRequestProxy::GetImage(imgIContainer** aImage) {
645 NS_ENSURE_TRUE(aImage, NS_ERROR_NULL_POINTER);
646 // It's possible that our owner has an image but hasn't notified us of it -
647 // that'll happen if we get Canceled before the owner instantiates its image
648 // (because Canceling unregisters us as a listener on mOwner). If we're
649 // in that situation, just grab the image off of mOwner.
650 RefPtr<Image> image = GetImage();
651 nsCOMPtr<imgIContainer> imageToReturn;
652 if (image) {
653 imageToReturn = image;
655 if (!imageToReturn && GetOwner()) {
656 imageToReturn = GetOwner()->GetImage();
658 if (!imageToReturn) {
659 return NS_ERROR_FAILURE;
662 imageToReturn.swap(*aImage);
664 return NS_OK;
667 NS_IMETHODIMP
668 imgRequestProxy::GetProducerId(uint32_t* aId) {
669 NS_ENSURE_TRUE(aId, NS_ERROR_NULL_POINTER);
671 nsCOMPtr<imgIContainer> image;
672 nsresult rv = GetImage(getter_AddRefs(image));
673 if (NS_SUCCEEDED(rv)) {
674 *aId = image->GetProducerId();
675 } else {
676 *aId = layers::kContainerProducerID_Invalid;
679 return NS_OK;
682 NS_IMETHODIMP
683 imgRequestProxy::GetImageStatus(uint32_t* aStatus) {
684 if (IsValidating()) {
685 // We are currently validating the image, and so our status could revert if
686 // we discard the cache. We should also be deferring notifications, such
687 // that the caller will be notified when validation completes. Rather than
688 // risk misleading the caller, return nothing.
689 *aStatus = imgIRequest::STATUS_NONE;
690 } else {
691 RefPtr<ProgressTracker> progressTracker = GetProgressTracker();
692 *aStatus = progressTracker->GetImageStatus();
695 return NS_OK;
698 NS_IMETHODIMP
699 imgRequestProxy::GetImageErrorCode(nsresult* aStatus) {
700 if (!GetOwner()) {
701 return NS_ERROR_FAILURE;
704 *aStatus = GetOwner()->GetImageErrorCode();
706 return NS_OK;
709 NS_IMETHODIMP
710 imgRequestProxy::GetURI(nsIURI** aURI) {
711 MOZ_ASSERT(NS_IsMainThread(), "Must be on main thread to convert URI");
712 nsCOMPtr<nsIURI> uri = mURI;
713 uri.forget(aURI);
714 return NS_OK;
717 nsresult imgRequestProxy::GetFinalURI(nsIURI** aURI) {
718 if (!GetOwner()) {
719 return NS_ERROR_FAILURE;
722 return GetOwner()->GetFinalURI(aURI);
725 NS_IMETHODIMP
726 imgRequestProxy::GetNotificationObserver(imgINotificationObserver** aObserver) {
727 *aObserver = mListener;
728 NS_IF_ADDREF(*aObserver);
729 return NS_OK;
732 NS_IMETHODIMP
733 imgRequestProxy::GetMimeType(char** aMimeType) {
734 if (!GetOwner()) {
735 return NS_ERROR_FAILURE;
738 const char* type = GetOwner()->GetMimeType();
739 if (!type) {
740 return NS_ERROR_FAILURE;
743 *aMimeType = NS_xstrdup(type);
745 return NS_OK;
748 imgRequestProxy* imgRequestProxy::NewClonedProxy() {
749 return new imgRequestProxy();
752 NS_IMETHODIMP
753 imgRequestProxy::Clone(imgINotificationObserver* aObserver,
754 imgIRequest** aClone) {
755 nsresult result;
756 imgRequestProxy* proxy;
757 result = PerformClone(aObserver, nullptr, /* aSyncNotify */ true, &proxy);
758 *aClone = proxy;
759 return result;
762 nsresult imgRequestProxy::SyncClone(imgINotificationObserver* aObserver,
763 Document* aLoadingDocument,
764 imgRequestProxy** aClone) {
765 return PerformClone(aObserver, aLoadingDocument,
766 /* aSyncNotify */ true, aClone);
769 nsresult imgRequestProxy::Clone(imgINotificationObserver* aObserver,
770 Document* aLoadingDocument,
771 imgRequestProxy** aClone) {
772 return PerformClone(aObserver, aLoadingDocument,
773 /* aSyncNotify */ false, aClone);
776 nsresult imgRequestProxy::PerformClone(imgINotificationObserver* aObserver,
777 Document* aLoadingDocument,
778 bool aSyncNotify,
779 imgRequestProxy** aClone) {
780 MOZ_ASSERT(aClone, "Null out param");
782 LOG_SCOPE(gImgLog, "imgRequestProxy::Clone");
784 *aClone = nullptr;
785 RefPtr<imgRequestProxy> clone = NewClonedProxy();
787 nsCOMPtr<nsILoadGroup> loadGroup;
788 if (aLoadingDocument) {
789 loadGroup = aLoadingDocument->GetDocumentLoadGroup();
792 // It is important to call |SetLoadFlags()| before calling |Init()| because
793 // |Init()| adds the request to the loadgroup.
794 // When a request is added to a loadgroup, its load flags are merged
795 // with the load flags of the loadgroup.
796 // XXXldb That's not true anymore. Stuff from imgLoader adds the
797 // request to the loadgroup.
798 clone->SetLoadFlags(mLoadFlags);
799 nsresult rv = clone->Init(mBehaviour->GetOwner(), loadGroup, aLoadingDocument,
800 mURI, aObserver);
801 if (NS_FAILED(rv)) {
802 return rv;
805 // Assign to *aClone before calling Notify so that if the caller expects to
806 // only be notified for requests it's already holding pointers to it won't be
807 // surprised.
808 NS_ADDREF(*aClone = clone);
810 imgCacheValidator* validator = GetValidator();
811 if (validator) {
812 // Note that if we have a validator, we don't want to issue notifications at
813 // here because we want to defer until that completes. AddProxy will add us
814 // to the load group; we cannot avoid that in this case, because we don't
815 // know when the validation will complete, and if it will cause us to
816 // discard our cached state anyways. We are probably already blocked by the
817 // original LoadImage(WithChannel) request in any event.
818 clone->MarkValidating();
819 validator->AddProxy(clone);
820 } else {
821 // We only want to add the request to the load group of the owning document
822 // if it is still in progress. Some callers cannot handle a supurious load
823 // group removal (e.g. print preview) so we must be careful. On the other
824 // hand, if after cloning, the original request proxy is cancelled /
825 // destroyed, we need to ensure that any clones still block the load group
826 // if it is incomplete.
827 bool addToLoadGroup = mIsInLoadGroup;
828 if (!addToLoadGroup) {
829 RefPtr<ProgressTracker> tracker = clone->GetProgressTracker();
830 addToLoadGroup =
831 tracker && !(tracker->GetProgress() & FLAG_LOAD_COMPLETE);
834 if (addToLoadGroup) {
835 clone->AddToLoadGroup();
838 if (aSyncNotify) {
839 // This is wrong!!! We need to notify asynchronously, but there's code
840 // that assumes that we don't. This will be fixed in bug 580466. Note that
841 // if we have a validator, we won't issue notifications anyways because
842 // they are deferred, so there is no point in requesting.
843 clone->mForceDispatchLoadGroup = true;
844 clone->SyncNotifyListener();
845 clone->mForceDispatchLoadGroup = false;
846 } else {
847 // Without a validator, we can request asynchronous notifications
848 // immediately. If there was a validator, this would override the deferral
849 // and that would be incorrect.
850 clone->NotifyListener();
854 return NS_OK;
857 NS_IMETHODIMP
858 imgRequestProxy::GetImagePrincipal(nsIPrincipal** aPrincipal) {
859 if (!GetOwner()) {
860 return NS_ERROR_FAILURE;
863 nsCOMPtr<nsIPrincipal> principal = GetOwner()->GetPrincipal();
864 principal.forget(aPrincipal);
865 return NS_OK;
868 NS_IMETHODIMP
869 imgRequestProxy::GetHadCrossOriginRedirects(bool* aHadCrossOriginRedirects) {
870 *aHadCrossOriginRedirects = false;
872 nsCOMPtr<nsITimedChannel> timedChannel = TimedChannel();
873 if (timedChannel) {
874 bool allRedirectsSameOrigin = false;
875 *aHadCrossOriginRedirects =
876 NS_SUCCEEDED(
877 timedChannel->GetAllRedirectsSameOrigin(&allRedirectsSameOrigin)) &&
878 !allRedirectsSameOrigin;
881 return NS_OK;
884 NS_IMETHODIMP
885 imgRequestProxy::GetMultipart(bool* aMultipart) {
886 if (!GetOwner()) {
887 return NS_ERROR_FAILURE;
890 *aMultipart = GetOwner()->GetMultipart();
892 return NS_OK;
895 NS_IMETHODIMP
896 imgRequestProxy::GetCORSMode(int32_t* aCorsMode) {
897 if (!GetOwner()) {
898 return NS_ERROR_FAILURE;
901 *aCorsMode = GetOwner()->GetCORSMode();
903 return NS_OK;
906 NS_IMETHODIMP
907 imgRequestProxy::BoostPriority(uint32_t aCategory) {
908 NS_ENSURE_STATE(GetOwner() && !mCanceled);
909 GetOwner()->BoostPriority(aCategory);
910 return NS_OK;
913 /** nsISupportsPriority methods **/
915 NS_IMETHODIMP
916 imgRequestProxy::GetPriority(int32_t* priority) {
917 NS_ENSURE_STATE(GetOwner());
918 *priority = GetOwner()->Priority();
919 return NS_OK;
922 NS_IMETHODIMP
923 imgRequestProxy::SetPriority(int32_t priority) {
924 NS_ENSURE_STATE(GetOwner() && !mCanceled);
925 GetOwner()->AdjustPriority(this, priority - GetOwner()->Priority());
926 return NS_OK;
929 NS_IMETHODIMP
930 imgRequestProxy::AdjustPriority(int32_t priority) {
931 // We don't require |!mCanceled| here. This may be called even if we're
932 // cancelled, because it's invoked as part of the process of removing an image
933 // from the load group.
934 NS_ENSURE_STATE(GetOwner());
935 GetOwner()->AdjustPriority(this, priority);
936 return NS_OK;
939 static const char* NotificationTypeToString(int32_t aType) {
940 switch (aType) {
941 case imgINotificationObserver::SIZE_AVAILABLE:
942 return "SIZE_AVAILABLE";
943 case imgINotificationObserver::FRAME_UPDATE:
944 return "FRAME_UPDATE";
945 case imgINotificationObserver::FRAME_COMPLETE:
946 return "FRAME_COMPLETE";
947 case imgINotificationObserver::LOAD_COMPLETE:
948 return "LOAD_COMPLETE";
949 case imgINotificationObserver::DECODE_COMPLETE:
950 return "DECODE_COMPLETE";
951 case imgINotificationObserver::DISCARD:
952 return "DISCARD";
953 case imgINotificationObserver::UNLOCKED_DRAW:
954 return "UNLOCKED_DRAW";
955 case imgINotificationObserver::IS_ANIMATED:
956 return "IS_ANIMATED";
957 case imgINotificationObserver::HAS_TRANSPARENCY:
958 return "HAS_TRANSPARENCY";
959 default:
960 MOZ_ASSERT_UNREACHABLE("Notification list should be exhaustive");
961 return "(unknown notification)";
965 void imgRequestProxy::Notify(int32_t aType,
966 const mozilla::gfx::IntRect* aRect) {
967 MOZ_ASSERT(aType != imgINotificationObserver::LOAD_COMPLETE,
968 "Should call OnLoadComplete");
970 LOG_FUNC_WITH_PARAM(gImgLog, "imgRequestProxy::Notify", "type",
971 NotificationTypeToString(aType));
973 if (!mListener || mCanceled) {
974 return;
977 // Make sure the listener stays alive while we notify.
978 nsCOMPtr<imgINotificationObserver> listener(mListener);
980 listener->Notify(this, aType, aRect);
983 void imgRequestProxy::OnLoadComplete(bool aLastPart) {
984 LOG_FUNC_WITH_PARAM(gImgLog, "imgRequestProxy::OnLoadComplete", "uri", mURI);
986 // There's all sorts of stuff here that could kill us (the OnStopRequest call
987 // on the listener, the removal from the loadgroup, the release of the
988 // listener, etc). Don't let them do it.
989 RefPtr<imgRequestProxy> self(this);
991 if (mListener && !mCanceled) {
992 // Hold a ref to the listener while we call it, just in case.
993 nsCOMPtr<imgINotificationObserver> listener(mListener);
994 listener->Notify(this, imgINotificationObserver::LOAD_COMPLETE, nullptr);
997 // If we're expecting more data from a multipart channel, re-add ourself
998 // to the loadgroup so that the document doesn't lose track of the load.
999 // If the request is already a background request and there's more data
1000 // coming, we can just leave the request in the loadgroup as-is.
1001 if (aLastPart || (mLoadFlags & nsIRequest::LOAD_BACKGROUND) == 0) {
1002 if (aLastPart) {
1003 RemoveFromLoadGroup();
1005 nsresult errorCode = NS_OK;
1006 // if the load is cross origin without CORS, or the CORS access is
1007 // rejected, always fire load event to avoid leaking site information for
1008 // <link rel=preload>.
1009 // XXXedgar, currently we don't do the same thing for <img>.
1010 imgRequest* request = GetOwner();
1011 if (!request || !(request->IsDeniedCrossSiteCORSRequest() ||
1012 request->IsCrossSiteNoCORSRequest())) {
1013 uint32_t status = imgIRequest::STATUS_NONE;
1014 GetImageStatus(&status);
1015 if (status & imgIRequest::STATUS_ERROR) {
1016 errorCode = NS_ERROR_FAILURE;
1019 NotifyStop(errorCode);
1020 } else {
1021 // More data is coming, so change the request to be a background request
1022 // and put it back in the loadgroup.
1023 MoveToBackgroundInLoadGroup();
1027 if (mListenerIsStrongRef && aLastPart) {
1028 MOZ_ASSERT(mListener, "How did that happen?");
1029 // Drop our strong ref to the listener now that we're done with
1030 // everything. Note that this can cancel us and other fun things
1031 // like that. Don't add anything in this method after this point.
1032 imgINotificationObserver* obs = mListener;
1033 mListenerIsStrongRef = false;
1034 NS_RELEASE(obs);
1038 void imgRequestProxy::NullOutListener() {
1039 // If we have animation consumers, then they don't matter anymore
1040 if (mListener) {
1041 ClearAnimationConsumers();
1044 if (mListenerIsStrongRef) {
1045 // Releasing could do weird reentery stuff, so just play it super-safe
1046 nsCOMPtr<imgINotificationObserver> obs;
1047 obs.swap(mListener);
1048 mListenerIsStrongRef = false;
1049 } else {
1050 mListener = nullptr;
1054 NS_IMETHODIMP
1055 imgRequestProxy::GetStaticRequest(imgIRequest** aReturn) {
1056 RefPtr<imgRequestProxy> proxy =
1057 GetStaticRequest(static_cast<Document*>(nullptr));
1058 if (proxy != this) {
1059 RefPtr<Image> image = GetImage();
1060 if (image && image->HasError()) {
1061 // image/test/unit/test_async_notification_404.js needs this, but ideally
1062 // this special case can be removed from the scripted codepath.
1063 return NS_ERROR_FAILURE;
1066 proxy.forget(aReturn);
1067 return NS_OK;
1070 already_AddRefed<imgRequestProxy> imgRequestProxy::GetStaticRequest(
1071 Document* aLoadingDocument) {
1072 MOZ_DIAGNOSTIC_ASSERT(!aLoadingDocument ||
1073 aLoadingDocument->IsStaticDocument());
1074 RefPtr<Image> image = GetImage();
1076 bool animated;
1077 if (!image || (NS_SUCCEEDED(image->GetAnimated(&animated)) && !animated)) {
1078 // Early exit - we're not animated, so we don't have to do anything.
1079 return do_AddRef(this);
1082 // We are animated. We need to create a frozen version of this image.
1083 RefPtr<Image> frozenImage = ImageOps::Freeze(image);
1085 // Create a static imgRequestProxy with our new extracted frame.
1086 nsCOMPtr<nsIPrincipal> currentPrincipal;
1087 GetImagePrincipal(getter_AddRefs(currentPrincipal));
1088 bool hadCrossOriginRedirects = true;
1089 GetHadCrossOriginRedirects(&hadCrossOriginRedirects);
1090 RefPtr<imgRequestProxy> req = new imgRequestProxyStatic(
1091 frozenImage, currentPrincipal, hadCrossOriginRedirects);
1092 req->Init(nullptr, nullptr, aLoadingDocument, mURI, nullptr);
1094 return req.forget();
1097 void imgRequestProxy::NotifyListener() {
1098 // It would be nice to notify the observer directly in the status tracker
1099 // instead of through the proxy, but there are several places we do extra
1100 // processing when we receive notifications (like OnStopRequest()), and we
1101 // need to check mCanceled everywhere too.
1103 RefPtr<ProgressTracker> progressTracker = GetProgressTracker();
1104 if (GetOwner()) {
1105 // Send the notifications to our listener asynchronously.
1106 progressTracker->Notify(this);
1107 } else {
1108 // We don't have an imgRequest, so we can only notify the clone of our
1109 // current state, but we still have to do that asynchronously.
1110 MOZ_ASSERT(HasImage(), "if we have no imgRequest, we should have an Image");
1111 progressTracker->NotifyCurrentState(this);
1115 void imgRequestProxy::SyncNotifyListener() {
1116 // It would be nice to notify the observer directly in the status tracker
1117 // instead of through the proxy, but there are several places we do extra
1118 // processing when we receive notifications (like OnStopRequest()), and we
1119 // need to check mCanceled everywhere too.
1121 RefPtr<ProgressTracker> progressTracker = GetProgressTracker();
1122 progressTracker->SyncNotify(this);
1125 void imgRequestProxy::SetHasImage() {
1126 RefPtr<ProgressTracker> progressTracker = GetProgressTracker();
1127 MOZ_ASSERT(progressTracker);
1128 RefPtr<Image> image = progressTracker->GetImage();
1129 MOZ_ASSERT(image);
1131 // Force any private status related to the owner to reflect
1132 // the presence of an image;
1133 mBehaviour->SetOwner(mBehaviour->GetOwner());
1135 // Apply any locks we have
1136 for (uint32_t i = 0; i < mLockCount; ++i) {
1137 image->LockImage();
1140 // Apply any animation consumers we have
1141 for (uint32_t i = 0; i < mAnimationConsumers; i++) {
1142 image->IncrementAnimationConsumers();
1146 already_AddRefed<ProgressTracker> imgRequestProxy::GetProgressTracker() const {
1147 return mBehaviour->GetProgressTracker();
1150 already_AddRefed<mozilla::image::Image> imgRequestProxy::GetImage() const {
1151 return mBehaviour->GetImage();
1154 bool RequestBehaviour::HasImage() const {
1155 if (!mOwnerHasImage) {
1156 return false;
1158 RefPtr<ProgressTracker> progressTracker = GetProgressTracker();
1159 return progressTracker ? progressTracker->HasImage() : false;
1162 bool imgRequestProxy::HasImage() const { return mBehaviour->HasImage(); }
1164 imgRequest* imgRequestProxy::GetOwner() const { return mBehaviour->GetOwner(); }
1166 imgCacheValidator* imgRequestProxy::GetValidator() const {
1167 imgRequest* owner = GetOwner();
1168 if (!owner) {
1169 return nullptr;
1171 return owner->GetValidator();
1174 ////////////////// imgRequestProxyStatic methods
1176 class StaticBehaviour : public ProxyBehaviour {
1177 public:
1178 explicit StaticBehaviour(mozilla::image::Image* aImage) : mImage(aImage) {}
1180 already_AddRefed<mozilla::image::Image> GetImage() const override {
1181 RefPtr<mozilla::image::Image> image = mImage;
1182 return image.forget();
1185 bool HasImage() const override { return mImage; }
1187 already_AddRefed<ProgressTracker> GetProgressTracker() const override {
1188 return mImage->GetProgressTracker();
1191 imgRequest* GetOwner() const override { return nullptr; }
1193 void SetOwner(imgRequest* aOwner) override {
1194 MOZ_ASSERT(!aOwner,
1195 "We shouldn't be giving static requests a non-null owner.");
1198 private:
1199 // Our image. We have to hold a strong reference here, because that's normally
1200 // the job of the underlying request.
1201 RefPtr<mozilla::image::Image> mImage;
1204 imgRequestProxyStatic::imgRequestProxyStatic(mozilla::image::Image* aImage,
1205 nsIPrincipal* aPrincipal,
1206 bool aHadCrossOriginRedirects)
1207 : mPrincipal(aPrincipal),
1208 mHadCrossOriginRedirects(aHadCrossOriginRedirects) {
1209 mBehaviour = mozilla::MakeUnique<StaticBehaviour>(aImage);
1212 NS_IMETHODIMP
1213 imgRequestProxyStatic::GetImagePrincipal(nsIPrincipal** aPrincipal) {
1214 if (!mPrincipal) {
1215 return NS_ERROR_FAILURE;
1218 NS_ADDREF(*aPrincipal = mPrincipal);
1220 return NS_OK;
1223 NS_IMETHODIMP
1224 imgRequestProxyStatic::GetHadCrossOriginRedirects(
1225 bool* aHadCrossOriginRedirects) {
1226 *aHadCrossOriginRedirects = mHadCrossOriginRedirects;
1227 return NS_OK;
1230 imgRequestProxy* imgRequestProxyStatic::NewClonedProxy() {
1231 nsCOMPtr<nsIPrincipal> currentPrincipal;
1232 GetImagePrincipal(getter_AddRefs(currentPrincipal));
1233 bool hadCrossOriginRedirects = true;
1234 GetHadCrossOriginRedirects(&hadCrossOriginRedirects);
1235 RefPtr<mozilla::image::Image> image = GetImage();
1236 return new imgRequestProxyStatic(image, currentPrincipal,
1237 hadCrossOriginRedirects);