Bug 1679927 Part 1: Make AppleVTDecoder check color depth and use 10-bit YUV420 when...
[gecko.git] / image / imgRequestProxy.cpp
blob575b8fd486e38f4980b3fad99685636f966eadbb
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
3 * This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "imgRequestProxy.h"
9 #include <utility>
11 #include "Image.h"
12 #include "ImageLogging.h"
13 #include "ImageOps.h"
14 #include "ImageTypes.h"
15 #include "imgINotificationObserver.h"
16 #include "imgLoader.h"
17 #include "mozilla/dom/Document.h"
18 #include "mozilla/Telemetry.h" // for Telemetry
19 #include "mozilla/dom/DocGroup.h" // for DocGroup
20 #include "nsCRTGlue.h"
21 #include "nsError.h"
23 using namespace mozilla;
24 using namespace mozilla::image;
25 using mozilla::dom::Document;
27 // The split of imgRequestProxy and imgRequestProxyStatic means that
28 // certain overridden functions need to be usable in the destructor.
29 // Since virtual functions can't be used in that way, this class
30 // provides a behavioural trait for each class to use instead.
31 class ProxyBehaviour {
32 public:
33 virtual ~ProxyBehaviour() = default;
35 virtual already_AddRefed<mozilla::image::Image> GetImage() const = 0;
36 virtual bool HasImage() const = 0;
37 virtual already_AddRefed<ProgressTracker> GetProgressTracker() const = 0;
38 virtual imgRequest* GetOwner() const = 0;
39 virtual void SetOwner(imgRequest* aOwner) = 0;
42 class RequestBehaviour : public ProxyBehaviour {
43 public:
44 RequestBehaviour() : mOwner(nullptr), mOwnerHasImage(false) {}
46 already_AddRefed<mozilla::image::Image> GetImage() const override;
47 bool HasImage() const override;
48 already_AddRefed<ProgressTracker> GetProgressTracker() const override;
50 imgRequest* GetOwner() const override { return mOwner; }
52 void SetOwner(imgRequest* aOwner) override {
53 mOwner = aOwner;
55 if (mOwner) {
56 RefPtr<ProgressTracker> ownerProgressTracker = GetProgressTracker();
57 mOwnerHasImage = ownerProgressTracker && ownerProgressTracker->HasImage();
58 } else {
59 mOwnerHasImage = false;
63 private:
64 // We maintain the following invariant:
65 // The proxy is registered at most with a single imgRequest as an observer,
66 // and whenever it is, mOwner points to that object. This helps ensure that
67 // imgRequestProxy::~imgRequestProxy unregisters the proxy as an observer
68 // from whatever request it was registered with (if any). This, in turn,
69 // means that imgRequest::mObservers will not have any stale pointers in it.
70 RefPtr<imgRequest> mOwner;
72 bool mOwnerHasImage;
75 already_AddRefed<mozilla::image::Image> RequestBehaviour::GetImage() const {
76 if (!mOwnerHasImage) {
77 return nullptr;
79 RefPtr<ProgressTracker> progressTracker = GetProgressTracker();
80 return progressTracker->GetImage();
83 already_AddRefed<ProgressTracker> RequestBehaviour::GetProgressTracker() const {
84 // NOTE: It's possible that our mOwner has an Image that it didn't notify
85 // us about, if we were Canceled before its Image was constructed.
86 // (Canceling removes us as an observer, so mOwner has no way to notify us).
87 // That's why this method uses mOwner->GetProgressTracker() instead of just
88 // mOwner->mProgressTracker -- we might have a null mImage and yet have an
89 // mOwner with a non-null mImage (and a null mProgressTracker pointer).
90 return mOwner->GetProgressTracker();
93 NS_IMPL_ADDREF(imgRequestProxy)
94 NS_IMPL_RELEASE(imgRequestProxy)
96 NS_INTERFACE_MAP_BEGIN(imgRequestProxy)
97 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, PreloaderBase)
98 NS_INTERFACE_MAP_ENTRY(imgIRequest)
99 NS_INTERFACE_MAP_ENTRY(nsIRequest)
100 NS_INTERFACE_MAP_ENTRY(nsISupportsPriority)
101 NS_INTERFACE_MAP_ENTRY_CONCRETE(imgRequestProxy)
102 NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsITimedChannel, TimedChannel() != nullptr)
103 NS_INTERFACE_MAP_END
105 imgRequestProxy::imgRequestProxy()
106 : mBehaviour(new RequestBehaviour),
107 mURI(nullptr),
108 mListener(nullptr),
109 mLoadFlags(nsIRequest::LOAD_NORMAL),
110 mLockCount(0),
111 mAnimationConsumers(0),
112 mCanceled(false),
113 mIsInLoadGroup(false),
114 mForceDispatchLoadGroup(false),
115 mListenerIsStrongRef(false),
116 mDecodeRequested(false),
117 mPendingNotify(false),
118 mValidating(false),
119 mHadListener(false),
120 mHadDispatch(false) {
121 /* member initializers and constructor code */
122 LOG_FUNC(gImgLog, "imgRequestProxy::imgRequestProxy");
125 imgRequestProxy::~imgRequestProxy() {
126 /* destructor code */
127 MOZ_ASSERT(!mListener, "Someone forgot to properly cancel this request!");
129 // If we had a listener, that means we would have issued notifications. With
130 // bug 1359833, we added support for main thread scheduler groups. Each
131 // imgRequestProxy may have its own associated listener, document and/or
132 // scheduler group. Typically most imgRequestProxy belong to the same
133 // document, or have no listener, which means we will want to execute all main
134 // thread code in that shared scheduler group. Less frequently, there may be
135 // multiple imgRequests and they have separate documents, which means that
136 // when we issue state notifications, some or all need to be dispatched to the
137 // appropriate scheduler group for each request. This should be rare, so we
138 // want to monitor the frequency of dispatching in the wild.
139 if (mHadListener) {
140 mozilla::Telemetry::Accumulate(mozilla::Telemetry::IMAGE_REQUEST_DISPATCHED,
141 mHadDispatch);
144 MOZ_RELEASE_ASSERT(!mLockCount, "Someone forgot to unlock on time?");
146 ClearAnimationConsumers();
148 // Explicitly set mListener to null to ensure that the RemoveProxy
149 // call below can't send |this| to an arbitrary listener while |this|
150 // is being destroyed. This is all belt-and-suspenders in view of the
151 // above assert.
152 NullOutListener();
154 /* Call RemoveProxy with a successful status. This will keep the
155 channel, if still downloading data, from being canceled if 'this' is
156 the last observer. This allows the image to continue to download and
157 be cached even if no one is using it currently.
159 mCanceled = true;
160 RemoveFromOwner(NS_OK);
162 RemoveFromLoadGroup();
163 LOG_FUNC(gImgLog, "imgRequestProxy::~imgRequestProxy");
166 nsresult imgRequestProxy::Init(imgRequest* aOwner, nsILoadGroup* aLoadGroup,
167 Document* aLoadingDocument, nsIURI* aURI,
168 imgINotificationObserver* aObserver) {
169 MOZ_ASSERT(!GetOwner() && !mListener,
170 "imgRequestProxy is already initialized");
172 LOG_SCOPE_WITH_PARAM(gImgLog, "imgRequestProxy::Init", "request", aOwner);
174 MOZ_ASSERT(mAnimationConsumers == 0, "Cannot have animation before Init");
176 mBehaviour->SetOwner(aOwner);
177 mListener = aObserver;
178 // Make sure to addref mListener before the AddToOwner call below, since
179 // that call might well want to release it if the imgRequest has
180 // already seen OnStopRequest.
181 if (mListener) {
182 mHadListener = true;
183 mListenerIsStrongRef = true;
184 NS_ADDREF(mListener);
186 mLoadGroup = aLoadGroup;
187 mURI = aURI;
189 // Note: AddToOwner won't send all the On* notifications immediately
190 AddToOwner(aLoadingDocument);
192 return NS_OK;
195 nsresult imgRequestProxy::ChangeOwner(imgRequest* aNewOwner) {
196 MOZ_ASSERT(GetOwner(), "Cannot ChangeOwner on a proxy without an owner!");
198 if (mCanceled) {
199 // Ensure that this proxy has received all notifications to date
200 // before we clean it up when removing it from the old owner below.
201 SyncNotifyListener();
204 // If we're holding locks, unlock the old image.
205 // Note that UnlockImage decrements mLockCount each time it's called.
206 uint32_t oldLockCount = mLockCount;
207 while (mLockCount) {
208 UnlockImage();
211 // If we're holding animation requests, undo them.
212 uint32_t oldAnimationConsumers = mAnimationConsumers;
213 ClearAnimationConsumers();
215 GetOwner()->RemoveProxy(this, NS_OK);
217 mBehaviour->SetOwner(aNewOwner);
218 MOZ_ASSERT(!GetValidator(), "New owner cannot be validating!");
220 // If we were locked, apply the locks here
221 for (uint32_t i = 0; i < oldLockCount; i++) {
222 LockImage();
225 // If we had animation requests, restore them here. Note that we
226 // do this *after* RemoveProxy, which clears out animation consumers
227 // (see bug 601723).
228 for (uint32_t i = 0; i < oldAnimationConsumers; i++) {
229 IncrementAnimationConsumers();
232 AddToOwner(nullptr);
233 return NS_OK;
236 void imgRequestProxy::MarkValidating() {
237 MOZ_ASSERT(GetValidator());
238 mValidating = true;
241 void imgRequestProxy::ClearValidating() {
242 MOZ_ASSERT(mValidating);
243 MOZ_ASSERT(!GetValidator());
244 mValidating = false;
246 // If we'd previously requested a synchronous decode, request a decode on the
247 // new image.
248 if (mDecodeRequested) {
249 mDecodeRequested = false;
250 StartDecoding(imgIContainer::FLAG_NONE);
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(CreateRenderBlockingRunnable(std::move(aEvent)),
269 NS_DISPATCH_NORMAL);
270 return NS_OK;
273 return NS_DispatchToMainThread(
274 CreateRenderBlockingRunnable(std::move(aEvent)));
277 void imgRequestProxy::AddToOwner(Document* aLoadingDocument) {
278 // An imgRequestProxy can be initialized with neither a listener nor a
279 // document. The caller could follow up later by cloning the canonical
280 // imgRequestProxy with the actual listener. This is possible because
281 // imgLoader::LoadImage does not require a valid listener to be provided.
283 // Without a listener, we don't need to set our scheduler group, because
284 // we have nothing to signal. However if we were told what document this
285 // is for, it is likely that future listeners will belong to the same
286 // scheduler group.
288 // With a listener, we always need to update our scheduler group. A null
289 // scheduler group is valid with or without a document, but that means
290 // we will use the most generic event target possible on dispatch.
291 if (aLoadingDocument) {
292 RefPtr<mozilla::dom::DocGroup> docGroup = aLoadingDocument->GetDocGroup();
293 if (docGroup) {
294 mEventTarget = docGroup->EventTargetFor(mozilla::TaskCategory::Other);
295 MOZ_ASSERT(mEventTarget);
299 if (mListener && !mEventTarget) {
300 mEventTarget = do_GetMainThread();
303 imgRequest* owner = GetOwner();
304 if (!owner) {
305 return;
308 owner->AddProxy(this);
311 void imgRequestProxy::RemoveFromOwner(nsresult aStatus) {
312 imgRequest* owner = GetOwner();
313 if (owner) {
314 if (mValidating) {
315 imgCacheValidator* validator = owner->GetValidator();
316 MOZ_ASSERT(validator);
317 validator->RemoveProxy(this);
318 mValidating = false;
321 owner->RemoveProxy(this, aStatus);
325 void imgRequestProxy::AddToLoadGroup() {
326 NS_ASSERTION(!mIsInLoadGroup, "Whaa, we're already in the loadgroup!");
327 MOZ_ASSERT(!mForceDispatchLoadGroup);
329 /* While in theory there could be a dispatch outstanding to remove this
330 request from the load group, in practice we only add to the load group
331 (when previously not in a load group) at initialization. */
332 if (!mIsInLoadGroup && mLoadGroup) {
333 LOG_FUNC(gImgLog, "imgRequestProxy::AddToLoadGroup");
334 mLoadGroup->AddRequest(this, nullptr);
335 mIsInLoadGroup = true;
339 void imgRequestProxy::RemoveFromLoadGroup() {
340 if (!mIsInLoadGroup || !mLoadGroup) {
341 return;
344 /* Sometimes we may not be able to remove ourselves from the load group in
345 the current context. This is because our listeners are not re-entrant (e.g.
346 we are in the middle of CancelAndForgetObserver or SyncClone). */
347 if (mForceDispatchLoadGroup) {
348 LOG_FUNC(gImgLog, "imgRequestProxy::RemoveFromLoadGroup -- dispatch");
350 /* We take away the load group from the request temporarily; this prevents
351 additional dispatches via RemoveFromLoadGroup occurring, as well as
352 MoveToBackgroundInLoadGroup from removing and readding. This is safe
353 because we know that once we get here, blocking the load group at all is
354 unnecessary. */
355 mIsInLoadGroup = false;
356 nsCOMPtr<nsILoadGroup> loadGroup = std::move(mLoadGroup);
357 RefPtr<imgRequestProxy> self(this);
358 DispatchWithTargetIfAvailable(NS_NewRunnableFunction(
359 "imgRequestProxy::RemoveFromLoadGroup", [self, loadGroup]() -> void {
360 loadGroup->RemoveRequest(self, nullptr, NS_OK);
361 }));
362 return;
365 LOG_FUNC(gImgLog, "imgRequestProxy::RemoveFromLoadGroup");
367 /* calling RemoveFromLoadGroup may cause the document to finish
368 loading, which could result in our death. We need to make sure
369 that we stay alive long enough to fight another battle... at
370 least until we exit this function. */
371 nsCOMPtr<imgIRequest> kungFuDeathGrip(this);
372 mLoadGroup->RemoveRequest(this, nullptr, NS_OK);
373 mLoadGroup = nullptr;
374 mIsInLoadGroup = false;
377 void imgRequestProxy::MoveToBackgroundInLoadGroup() {
378 /* Even if we are still in the load group, we may have taken away the load
379 group reference itself because we are in the process of leaving the group.
380 In that case, there is no need to background the request. */
381 if (!mLoadGroup) {
382 return;
385 /* There is no need to dispatch if we only need to add ourselves to the load
386 group without removal. It is the removal which causes the problematic
387 callbacks (see RemoveFromLoadGroup). */
388 if (mIsInLoadGroup && mForceDispatchLoadGroup) {
389 LOG_FUNC(gImgLog,
390 "imgRequestProxy::MoveToBackgroundInLoadGroup -- dispatch");
392 RefPtr<imgRequestProxy> self(this);
393 DispatchWithTargetIfAvailable(NS_NewRunnableFunction(
394 "imgRequestProxy::MoveToBackgroundInLoadGroup",
395 [self]() -> void { self->MoveToBackgroundInLoadGroup(); }));
396 return;
399 LOG_FUNC(gImgLog, "imgRequestProxy::MoveToBackgroundInLoadGroup");
400 nsCOMPtr<imgIRequest> kungFuDeathGrip(this);
401 if (mIsInLoadGroup) {
402 mLoadGroup->RemoveRequest(this, nullptr, NS_OK);
405 mLoadFlags |= nsIRequest::LOAD_BACKGROUND;
406 mLoadGroup->AddRequest(this, nullptr);
409 /** nsIRequest / imgIRequest methods **/
411 NS_IMETHODIMP
412 imgRequestProxy::GetName(nsACString& aName) {
413 aName.Truncate();
415 if (mURI) {
416 mURI->GetSpec(aName);
419 return NS_OK;
422 NS_IMETHODIMP
423 imgRequestProxy::IsPending(bool* _retval) { return NS_ERROR_NOT_IMPLEMENTED; }
425 NS_IMETHODIMP
426 imgRequestProxy::GetStatus(nsresult* aStatus) {
427 return NS_ERROR_NOT_IMPLEMENTED;
430 NS_IMETHODIMP
431 imgRequestProxy::Cancel(nsresult status) {
432 if (mCanceled) {
433 return NS_ERROR_FAILURE;
436 LOG_SCOPE(gImgLog, "imgRequestProxy::Cancel");
438 mCanceled = true;
440 nsCOMPtr<nsIRunnable> ev = new imgCancelRunnable(this, status);
441 return DispatchWithTargetIfAvailable(ev.forget());
444 void imgRequestProxy::DoCancel(nsresult status) {
445 RemoveFromOwner(status);
446 RemoveFromLoadGroup();
447 NullOutListener();
450 NS_IMETHODIMP
451 imgRequestProxy::CancelAndForgetObserver(nsresult aStatus) {
452 // If mCanceled is true but mListener is non-null, that means
453 // someone called Cancel() on us but the imgCancelRunnable is still
454 // pending. We still need to null out mListener before returning
455 // from this function in this case. That means we want to do the
456 // RemoveProxy call right now, because we need to deliver the
457 // onStopRequest.
458 if (mCanceled && !mListener) {
459 return NS_ERROR_FAILURE;
462 LOG_SCOPE(gImgLog, "imgRequestProxy::CancelAndForgetObserver");
464 mCanceled = true;
465 mForceDispatchLoadGroup = true;
466 RemoveFromOwner(aStatus);
467 RemoveFromLoadGroup();
468 mForceDispatchLoadGroup = false;
470 NullOutListener();
472 return NS_OK;
475 NS_IMETHODIMP
476 imgRequestProxy::StartDecoding(uint32_t aFlags) {
477 // Flag this, so we know to request after validation if pending.
478 if (IsValidating()) {
479 mDecodeRequested = true;
480 return NS_OK;
483 RefPtr<Image> image = GetImage();
484 if (image) {
485 return image->StartDecoding(aFlags);
488 if (GetOwner()) {
489 GetOwner()->StartDecoding();
492 return NS_OK;
495 bool imgRequestProxy::StartDecodingWithResult(uint32_t aFlags) {
496 // Flag this, so we know to request after validation if pending.
497 if (IsValidating()) {
498 mDecodeRequested = true;
499 return false;
502 RefPtr<Image> image = GetImage();
503 if (image) {
504 return image->StartDecodingWithResult(aFlags);
507 if (GetOwner()) {
508 GetOwner()->StartDecoding();
511 return false;
514 imgIContainer::DecodeResult imgRequestProxy::RequestDecodeWithResult(
515 uint32_t aFlags) {
516 if (IsValidating()) {
517 mDecodeRequested = true;
518 return imgIContainer::DECODE_REQUESTED;
521 RefPtr<Image> image = GetImage();
522 if (image) {
523 return image->RequestDecodeWithResult(aFlags);
526 if (GetOwner()) {
527 GetOwner()->StartDecoding();
530 return imgIContainer::DECODE_REQUESTED;
533 NS_IMETHODIMP
534 imgRequestProxy::LockImage() {
535 mLockCount++;
536 RefPtr<Image> image =
537 GetOwner() && GetOwner()->ImageAvailable() ? GetImage() : nullptr;
538 if (image) {
539 return image->LockImage();
541 return NS_OK;
544 NS_IMETHODIMP
545 imgRequestProxy::UnlockImage() {
546 MOZ_ASSERT(mLockCount > 0, "calling unlock but no locks!");
548 mLockCount--;
549 RefPtr<Image> image =
550 GetOwner() && GetOwner()->ImageAvailable() ? GetImage() : nullptr;
551 if (image) {
552 return image->UnlockImage();
554 return NS_OK;
557 NS_IMETHODIMP
558 imgRequestProxy::RequestDiscard() {
559 RefPtr<Image> image = GetImage();
560 if (image) {
561 return image->RequestDiscard();
563 return NS_OK;
566 NS_IMETHODIMP
567 imgRequestProxy::IncrementAnimationConsumers() {
568 mAnimationConsumers++;
569 RefPtr<Image> image =
570 GetOwner() && GetOwner()->ImageAvailable() ? GetImage() : nullptr;
571 if (image) {
572 image->IncrementAnimationConsumers();
574 return NS_OK;
577 NS_IMETHODIMP
578 imgRequestProxy::DecrementAnimationConsumers() {
579 // We may get here if some responsible code called Increment,
580 // then called us, but we have meanwhile called ClearAnimationConsumers
581 // because we needed to get rid of them earlier (see
582 // imgRequest::RemoveProxy), and hence have nothing left to
583 // decrement. (In such a case we got rid of the animation consumers
584 // early, but not the observer.)
585 if (mAnimationConsumers > 0) {
586 mAnimationConsumers--;
587 RefPtr<Image> image =
588 GetOwner() && GetOwner()->ImageAvailable() ? GetImage() : nullptr;
589 if (image) {
590 image->DecrementAnimationConsumers();
593 return NS_OK;
596 void imgRequestProxy::ClearAnimationConsumers() {
597 while (mAnimationConsumers > 0) {
598 DecrementAnimationConsumers();
602 NS_IMETHODIMP
603 imgRequestProxy::Suspend() { return NS_ERROR_NOT_IMPLEMENTED; }
605 NS_IMETHODIMP
606 imgRequestProxy::Resume() { return NS_ERROR_NOT_IMPLEMENTED; }
608 NS_IMETHODIMP
609 imgRequestProxy::GetLoadGroup(nsILoadGroup** loadGroup) {
610 NS_IF_ADDREF(*loadGroup = mLoadGroup.get());
611 return NS_OK;
613 NS_IMETHODIMP
614 imgRequestProxy::SetLoadGroup(nsILoadGroup* loadGroup) {
615 if (loadGroup != mLoadGroup) {
616 MOZ_ASSERT_UNREACHABLE("Switching load groups is unsupported!");
617 return NS_ERROR_NOT_IMPLEMENTED;
619 return NS_OK;
622 NS_IMETHODIMP
623 imgRequestProxy::GetLoadFlags(nsLoadFlags* flags) {
624 *flags = mLoadFlags;
625 return NS_OK;
627 NS_IMETHODIMP
628 imgRequestProxy::SetLoadFlags(nsLoadFlags flags) {
629 mLoadFlags = flags;
630 return NS_OK;
633 NS_IMETHODIMP
634 imgRequestProxy::GetTRRMode(nsIRequest::TRRMode* aTRRMode) {
635 return GetTRRModeImpl(aTRRMode);
638 NS_IMETHODIMP
639 imgRequestProxy::SetTRRMode(nsIRequest::TRRMode aTRRMode) {
640 return SetTRRModeImpl(aTRRMode);
643 /** imgIRequest methods **/
645 NS_IMETHODIMP
646 imgRequestProxy::GetImage(imgIContainer** aImage) {
647 NS_ENSURE_TRUE(aImage, NS_ERROR_NULL_POINTER);
648 // It's possible that our owner has an image but hasn't notified us of it -
649 // that'll happen if we get Canceled before the owner instantiates its image
650 // (because Canceling unregisters us as a listener on mOwner). If we're
651 // in that situation, just grab the image off of mOwner.
652 RefPtr<Image> image = GetImage();
653 nsCOMPtr<imgIContainer> imageToReturn;
654 if (image) {
655 imageToReturn = image;
657 if (!imageToReturn && GetOwner()) {
658 imageToReturn = GetOwner()->GetImage();
660 if (!imageToReturn) {
661 return NS_ERROR_FAILURE;
664 imageToReturn.swap(*aImage);
666 return NS_OK;
669 NS_IMETHODIMP
670 imgRequestProxy::GetProviderId(uint32_t* aId) {
671 NS_ENSURE_TRUE(aId, NS_ERROR_NULL_POINTER);
673 nsCOMPtr<imgIContainer> image;
674 nsresult rv = GetImage(getter_AddRefs(image));
675 if (NS_SUCCEEDED(rv)) {
676 *aId = image->GetProviderId();
677 } else {
678 *aId = 0;
681 return NS_OK;
684 NS_IMETHODIMP
685 imgRequestProxy::GetImageStatus(uint32_t* aStatus) {
686 if (IsValidating()) {
687 // We are currently validating the image, and so our status could revert if
688 // we discard the cache. We should also be deferring notifications, such
689 // that the caller will be notified when validation completes. Rather than
690 // risk misleading the caller, return nothing.
691 *aStatus = imgIRequest::STATUS_NONE;
692 } else {
693 RefPtr<ProgressTracker> progressTracker = GetProgressTracker();
694 *aStatus = progressTracker->GetImageStatus();
697 return NS_OK;
700 NS_IMETHODIMP
701 imgRequestProxy::GetImageErrorCode(nsresult* aStatus) {
702 if (!GetOwner()) {
703 return NS_ERROR_FAILURE;
706 *aStatus = GetOwner()->GetImageErrorCode();
708 return NS_OK;
711 NS_IMETHODIMP
712 imgRequestProxy::GetURI(nsIURI** aURI) {
713 MOZ_ASSERT(NS_IsMainThread(), "Must be on main thread to convert URI");
714 nsCOMPtr<nsIURI> uri = mURI;
715 uri.forget(aURI);
716 return NS_OK;
719 nsresult imgRequestProxy::GetFinalURI(nsIURI** aURI) {
720 if (!GetOwner()) {
721 return NS_ERROR_FAILURE;
724 return GetOwner()->GetFinalURI(aURI);
727 NS_IMETHODIMP
728 imgRequestProxy::GetNotificationObserver(imgINotificationObserver** aObserver) {
729 *aObserver = mListener;
730 NS_IF_ADDREF(*aObserver);
731 return NS_OK;
734 NS_IMETHODIMP
735 imgRequestProxy::GetMimeType(char** aMimeType) {
736 if (!GetOwner()) {
737 return NS_ERROR_FAILURE;
740 const char* type = GetOwner()->GetMimeType();
741 if (!type) {
742 return NS_ERROR_FAILURE;
745 *aMimeType = NS_xstrdup(type);
747 return NS_OK;
750 imgRequestProxy* imgRequestProxy::NewClonedProxy() {
751 return new imgRequestProxy();
754 NS_IMETHODIMP
755 imgRequestProxy::Clone(imgINotificationObserver* aObserver,
756 imgIRequest** aClone) {
757 nsresult result;
758 imgRequestProxy* proxy;
759 result = PerformClone(aObserver, nullptr, /* aSyncNotify */ true, &proxy);
760 *aClone = proxy;
761 return result;
764 nsresult imgRequestProxy::SyncClone(imgINotificationObserver* aObserver,
765 Document* aLoadingDocument,
766 imgRequestProxy** aClone) {
767 return PerformClone(aObserver, aLoadingDocument,
768 /* aSyncNotify */ true, aClone);
771 nsresult imgRequestProxy::Clone(imgINotificationObserver* aObserver,
772 Document* aLoadingDocument,
773 imgRequestProxy** aClone) {
774 return PerformClone(aObserver, aLoadingDocument,
775 /* aSyncNotify */ false, aClone);
778 nsresult imgRequestProxy::PerformClone(imgINotificationObserver* aObserver,
779 Document* aLoadingDocument,
780 bool aSyncNotify,
781 imgRequestProxy** aClone) {
782 MOZ_ASSERT(aClone, "Null out param");
784 LOG_SCOPE(gImgLog, "imgRequestProxy::Clone");
786 *aClone = nullptr;
787 RefPtr<imgRequestProxy> clone = NewClonedProxy();
789 nsCOMPtr<nsILoadGroup> loadGroup;
790 if (aLoadingDocument) {
791 loadGroup = aLoadingDocument->GetDocumentLoadGroup();
794 // It is important to call |SetLoadFlags()| before calling |Init()| because
795 // |Init()| adds the request to the loadgroup.
796 // When a request is added to a loadgroup, its load flags are merged
797 // with the load flags of the loadgroup.
798 // XXXldb That's not true anymore. Stuff from imgLoader adds the
799 // request to the loadgroup.
800 clone->SetLoadFlags(mLoadFlags);
801 nsresult rv = clone->Init(mBehaviour->GetOwner(), loadGroup, aLoadingDocument,
802 mURI, aObserver);
803 if (NS_FAILED(rv)) {
804 return rv;
807 // Assign to *aClone before calling Notify so that if the caller expects to
808 // only be notified for requests it's already holding pointers to it won't be
809 // surprised.
810 NS_ADDREF(*aClone = clone);
812 imgCacheValidator* validator = GetValidator();
813 if (validator) {
814 // Note that if we have a validator, we don't want to issue notifications at
815 // here because we want to defer until that completes. AddProxy will add us
816 // to the load group; we cannot avoid that in this case, because we don't
817 // know when the validation will complete, and if it will cause us to
818 // discard our cached state anyways. We are probably already blocked by the
819 // original LoadImage(WithChannel) request in any event.
820 clone->MarkValidating();
821 validator->AddProxy(clone);
822 } else {
823 // We only want to add the request to the load group of the owning document
824 // if it is still in progress. Some callers cannot handle a supurious load
825 // group removal (e.g. print preview) so we must be careful. On the other
826 // hand, if after cloning, the original request proxy is cancelled /
827 // destroyed, we need to ensure that any clones still block the load group
828 // if it is incomplete.
829 bool addToLoadGroup = mIsInLoadGroup;
830 if (!addToLoadGroup) {
831 RefPtr<ProgressTracker> tracker = clone->GetProgressTracker();
832 addToLoadGroup =
833 tracker && !(tracker->GetProgress() & FLAG_LOAD_COMPLETE);
836 if (addToLoadGroup) {
837 clone->AddToLoadGroup();
840 if (aSyncNotify) {
841 // This is wrong!!! We need to notify asynchronously, but there's code
842 // that assumes that we don't. This will be fixed in bug 580466. Note that
843 // if we have a validator, we won't issue notifications anyways because
844 // they are deferred, so there is no point in requesting.
845 clone->mForceDispatchLoadGroup = true;
846 clone->SyncNotifyListener();
847 clone->mForceDispatchLoadGroup = false;
848 } else {
849 // Without a validator, we can request asynchronous notifications
850 // immediately. If there was a validator, this would override the deferral
851 // and that would be incorrect.
852 clone->NotifyListener();
856 return NS_OK;
859 NS_IMETHODIMP
860 imgRequestProxy::GetImagePrincipal(nsIPrincipal** aPrincipal) {
861 if (!GetOwner()) {
862 return NS_ERROR_FAILURE;
865 nsCOMPtr<nsIPrincipal> principal = GetOwner()->GetPrincipal();
866 principal.forget(aPrincipal);
867 return NS_OK;
870 NS_IMETHODIMP
871 imgRequestProxy::GetHadCrossOriginRedirects(bool* aHadCrossOriginRedirects) {
872 *aHadCrossOriginRedirects = false;
874 nsCOMPtr<nsITimedChannel> timedChannel = TimedChannel();
875 if (timedChannel) {
876 bool allRedirectsSameOrigin = false;
877 *aHadCrossOriginRedirects =
878 NS_SUCCEEDED(
879 timedChannel->GetAllRedirectsSameOrigin(&allRedirectsSameOrigin)) &&
880 !allRedirectsSameOrigin;
883 return NS_OK;
886 NS_IMETHODIMP
887 imgRequestProxy::GetMultipart(bool* aMultipart) {
888 if (!GetOwner()) {
889 return NS_ERROR_FAILURE;
892 *aMultipart = GetOwner()->GetMultipart();
894 return NS_OK;
897 NS_IMETHODIMP
898 imgRequestProxy::GetCORSMode(int32_t* aCorsMode) {
899 if (!GetOwner()) {
900 return NS_ERROR_FAILURE;
903 *aCorsMode = GetOwner()->GetCORSMode();
905 return NS_OK;
908 NS_IMETHODIMP
909 imgRequestProxy::BoostPriority(uint32_t aCategory) {
910 NS_ENSURE_STATE(GetOwner() && !mCanceled);
911 GetOwner()->BoostPriority(aCategory);
912 return NS_OK;
915 /** nsISupportsPriority methods **/
917 NS_IMETHODIMP
918 imgRequestProxy::GetPriority(int32_t* priority) {
919 NS_ENSURE_STATE(GetOwner());
920 *priority = GetOwner()->Priority();
921 return NS_OK;
924 NS_IMETHODIMP
925 imgRequestProxy::SetPriority(int32_t priority) {
926 NS_ENSURE_STATE(GetOwner() && !mCanceled);
927 GetOwner()->AdjustPriority(this, priority - GetOwner()->Priority());
928 return NS_OK;
931 NS_IMETHODIMP
932 imgRequestProxy::AdjustPriority(int32_t priority) {
933 // We don't require |!mCanceled| here. This may be called even if we're
934 // cancelled, because it's invoked as part of the process of removing an image
935 // from the load group.
936 NS_ENSURE_STATE(GetOwner());
937 GetOwner()->AdjustPriority(this, priority);
938 return NS_OK;
941 static const char* NotificationTypeToString(int32_t aType) {
942 switch (aType) {
943 case imgINotificationObserver::SIZE_AVAILABLE:
944 return "SIZE_AVAILABLE";
945 case imgINotificationObserver::FRAME_UPDATE:
946 return "FRAME_UPDATE";
947 case imgINotificationObserver::FRAME_COMPLETE:
948 return "FRAME_COMPLETE";
949 case imgINotificationObserver::LOAD_COMPLETE:
950 return "LOAD_COMPLETE";
951 case imgINotificationObserver::DECODE_COMPLETE:
952 return "DECODE_COMPLETE";
953 case imgINotificationObserver::DISCARD:
954 return "DISCARD";
955 case imgINotificationObserver::UNLOCKED_DRAW:
956 return "UNLOCKED_DRAW";
957 case imgINotificationObserver::IS_ANIMATED:
958 return "IS_ANIMATED";
959 case imgINotificationObserver::HAS_TRANSPARENCY:
960 return "HAS_TRANSPARENCY";
961 default:
962 MOZ_ASSERT_UNREACHABLE("Notification list should be exhaustive");
963 return "(unknown notification)";
967 void imgRequestProxy::Notify(int32_t aType,
968 const mozilla::gfx::IntRect* aRect) {
969 MOZ_ASSERT(aType != imgINotificationObserver::LOAD_COMPLETE,
970 "Should call OnLoadComplete");
972 LOG_FUNC_WITH_PARAM(gImgLog, "imgRequestProxy::Notify", "type",
973 NotificationTypeToString(aType));
975 if (!mListener || mCanceled) {
976 return;
979 // Make sure the listener stays alive while we notify.
980 nsCOMPtr<imgINotificationObserver> listener(mListener);
982 listener->Notify(this, aType, aRect);
985 void imgRequestProxy::OnLoadComplete(bool aLastPart) {
986 LOG_FUNC_WITH_PARAM(gImgLog, "imgRequestProxy::OnLoadComplete", "uri", mURI);
988 // There's all sorts of stuff here that could kill us (the OnStopRequest call
989 // on the listener, the removal from the loadgroup, the release of the
990 // listener, etc). Don't let them do it.
991 RefPtr<imgRequestProxy> self(this);
993 if (mListener && !mCanceled) {
994 // Hold a ref to the listener while we call it, just in case.
995 nsCOMPtr<imgINotificationObserver> listener(mListener);
996 listener->Notify(this, imgINotificationObserver::LOAD_COMPLETE, nullptr);
999 // If we're expecting more data from a multipart channel, re-add ourself
1000 // to the loadgroup so that the document doesn't lose track of the load.
1001 // If the request is already a background request and there's more data
1002 // coming, we can just leave the request in the loadgroup as-is.
1003 if (aLastPart || (mLoadFlags & nsIRequest::LOAD_BACKGROUND) == 0) {
1004 if (aLastPart) {
1005 RemoveFromLoadGroup();
1007 nsresult errorCode = NS_OK;
1008 // if the load is cross origin without CORS, or the CORS access is
1009 // rejected, always fire load event to avoid leaking site information for
1010 // <link rel=preload>.
1011 // XXXedgar, currently we don't do the same thing for <img>.
1012 imgRequest* request = GetOwner();
1013 if (!request || !(request->IsDeniedCrossSiteCORSRequest() ||
1014 request->IsCrossSiteNoCORSRequest())) {
1015 uint32_t status = imgIRequest::STATUS_NONE;
1016 GetImageStatus(&status);
1017 if (status & imgIRequest::STATUS_ERROR) {
1018 errorCode = NS_ERROR_FAILURE;
1021 NotifyStop(errorCode);
1022 } else {
1023 // More data is coming, so change the request to be a background request
1024 // and put it back in the loadgroup.
1025 MoveToBackgroundInLoadGroup();
1029 if (mListenerIsStrongRef && aLastPart) {
1030 MOZ_ASSERT(mListener, "How did that happen?");
1031 // Drop our strong ref to the listener now that we're done with
1032 // everything. Note that this can cancel us and other fun things
1033 // like that. Don't add anything in this method after this point.
1034 imgINotificationObserver* obs = mListener;
1035 mListenerIsStrongRef = false;
1036 NS_RELEASE(obs);
1040 void imgRequestProxy::NullOutListener() {
1041 // If we have animation consumers, then they don't matter anymore
1042 if (mListener) {
1043 ClearAnimationConsumers();
1046 if (mListenerIsStrongRef) {
1047 // Releasing could do weird reentery stuff, so just play it super-safe
1048 nsCOMPtr<imgINotificationObserver> obs;
1049 obs.swap(mListener);
1050 mListenerIsStrongRef = false;
1051 } else {
1052 mListener = nullptr;
1056 NS_IMETHODIMP
1057 imgRequestProxy::GetStaticRequest(imgIRequest** aReturn) {
1058 RefPtr<imgRequestProxy> proxy =
1059 GetStaticRequest(static_cast<Document*>(nullptr));
1060 if (proxy != this) {
1061 RefPtr<Image> image = GetImage();
1062 if (image && image->HasError()) {
1063 // image/test/unit/test_async_notification_404.js needs this, but ideally
1064 // this special case can be removed from the scripted codepath.
1065 return NS_ERROR_FAILURE;
1068 proxy.forget(aReturn);
1069 return NS_OK;
1072 already_AddRefed<imgRequestProxy> imgRequestProxy::GetStaticRequest(
1073 Document* aLoadingDocument) {
1074 MOZ_DIAGNOSTIC_ASSERT(!aLoadingDocument ||
1075 aLoadingDocument->IsStaticDocument());
1076 RefPtr<Image> image = GetImage();
1078 bool animated;
1079 if (!image || (NS_SUCCEEDED(image->GetAnimated(&animated)) && !animated)) {
1080 // Early exit - we're not animated, so we don't have to do anything.
1081 return do_AddRef(this);
1084 // We are animated. We need to create a frozen version of this image.
1085 RefPtr<Image> frozenImage = ImageOps::Freeze(image);
1087 // Create a static imgRequestProxy with our new extracted frame.
1088 nsCOMPtr<nsIPrincipal> currentPrincipal;
1089 GetImagePrincipal(getter_AddRefs(currentPrincipal));
1090 bool hadCrossOriginRedirects = true;
1091 GetHadCrossOriginRedirects(&hadCrossOriginRedirects);
1092 RefPtr<imgRequestProxy> req = new imgRequestProxyStatic(
1093 frozenImage, currentPrincipal, hadCrossOriginRedirects);
1094 req->Init(nullptr, nullptr, aLoadingDocument, mURI, nullptr);
1096 return req.forget();
1099 void imgRequestProxy::NotifyListener() {
1100 // It would be nice to notify the observer directly in the status tracker
1101 // instead of through the proxy, but there are several places we do extra
1102 // processing when we receive notifications (like OnStopRequest()), and we
1103 // need to check mCanceled everywhere too.
1105 RefPtr<ProgressTracker> progressTracker = GetProgressTracker();
1106 if (GetOwner()) {
1107 // Send the notifications to our listener asynchronously.
1108 progressTracker->Notify(this);
1109 } else {
1110 // We don't have an imgRequest, so we can only notify the clone of our
1111 // current state, but we still have to do that asynchronously.
1112 MOZ_ASSERT(HasImage(), "if we have no imgRequest, we should have an Image");
1113 progressTracker->NotifyCurrentState(this);
1117 void imgRequestProxy::SyncNotifyListener() {
1118 // It would be nice to notify the observer directly in the status tracker
1119 // instead of through the proxy, but there are several places we do extra
1120 // processing when we receive notifications (like OnStopRequest()), and we
1121 // need to check mCanceled everywhere too.
1123 RefPtr<ProgressTracker> progressTracker = GetProgressTracker();
1124 progressTracker->SyncNotify(this);
1127 void imgRequestProxy::SetHasImage() {
1128 RefPtr<ProgressTracker> progressTracker = GetProgressTracker();
1129 MOZ_ASSERT(progressTracker);
1130 RefPtr<Image> image = progressTracker->GetImage();
1131 MOZ_ASSERT(image);
1133 // Force any private status related to the owner to reflect
1134 // the presence of an image;
1135 mBehaviour->SetOwner(mBehaviour->GetOwner());
1137 // Apply any locks we have
1138 for (uint32_t i = 0; i < mLockCount; ++i) {
1139 image->LockImage();
1142 // Apply any animation consumers we have
1143 for (uint32_t i = 0; i < mAnimationConsumers; i++) {
1144 image->IncrementAnimationConsumers();
1148 already_AddRefed<ProgressTracker> imgRequestProxy::GetProgressTracker() const {
1149 return mBehaviour->GetProgressTracker();
1152 already_AddRefed<mozilla::image::Image> imgRequestProxy::GetImage() const {
1153 return mBehaviour->GetImage();
1156 bool RequestBehaviour::HasImage() const {
1157 if (!mOwnerHasImage) {
1158 return false;
1160 RefPtr<ProgressTracker> progressTracker = GetProgressTracker();
1161 return progressTracker ? progressTracker->HasImage() : false;
1164 bool imgRequestProxy::HasImage() const { return mBehaviour->HasImage(); }
1166 imgRequest* imgRequestProxy::GetOwner() const { return mBehaviour->GetOwner(); }
1168 imgCacheValidator* imgRequestProxy::GetValidator() const {
1169 imgRequest* owner = GetOwner();
1170 if (!owner) {
1171 return nullptr;
1173 return owner->GetValidator();
1176 nsITimedChannel* imgRequestProxy::TimedChannel() {
1177 if (!GetOwner()) {
1178 return nullptr;
1180 return GetOwner()->GetTimedChannel();
1183 ////////////////// imgRequestProxyStatic methods
1185 class StaticBehaviour : public ProxyBehaviour {
1186 public:
1187 explicit StaticBehaviour(mozilla::image::Image* aImage) : mImage(aImage) {}
1189 already_AddRefed<mozilla::image::Image> GetImage() const override {
1190 RefPtr<mozilla::image::Image> image = mImage;
1191 return image.forget();
1194 bool HasImage() const override { return mImage; }
1196 already_AddRefed<ProgressTracker> GetProgressTracker() const override {
1197 return mImage->GetProgressTracker();
1200 imgRequest* GetOwner() const override { return nullptr; }
1202 void SetOwner(imgRequest* aOwner) override {
1203 MOZ_ASSERT(!aOwner,
1204 "We shouldn't be giving static requests a non-null owner.");
1207 private:
1208 // Our image. We have to hold a strong reference here, because that's normally
1209 // the job of the underlying request.
1210 RefPtr<mozilla::image::Image> mImage;
1213 imgRequestProxyStatic::imgRequestProxyStatic(mozilla::image::Image* aImage,
1214 nsIPrincipal* aPrincipal,
1215 bool aHadCrossOriginRedirects)
1216 : mPrincipal(aPrincipal),
1217 mHadCrossOriginRedirects(aHadCrossOriginRedirects) {
1218 mBehaviour = mozilla::MakeUnique<StaticBehaviour>(aImage);
1221 NS_IMETHODIMP
1222 imgRequestProxyStatic::GetImagePrincipal(nsIPrincipal** aPrincipal) {
1223 if (!mPrincipal) {
1224 return NS_ERROR_FAILURE;
1227 NS_ADDREF(*aPrincipal = mPrincipal);
1229 return NS_OK;
1232 NS_IMETHODIMP
1233 imgRequestProxyStatic::GetHadCrossOriginRedirects(
1234 bool* aHadCrossOriginRedirects) {
1235 *aHadCrossOriginRedirects = mHadCrossOriginRedirects;
1236 return NS_OK;
1239 imgRequestProxy* imgRequestProxyStatic::NewClonedProxy() {
1240 nsCOMPtr<nsIPrincipal> currentPrincipal;
1241 GetImagePrincipal(getter_AddRefs(currentPrincipal));
1242 bool hadCrossOriginRedirects = true;
1243 GetHadCrossOriginRedirects(&hadCrossOriginRedirects);
1244 RefPtr<mozilla::image::Image> image = GetImage();
1245 return new imgRequestProxyStatic(image, currentPrincipal,
1246 hadCrossOriginRedirects);