Bug 1694101 [wpt PR 17571] - HTML: <tr height=0> is a thing, a=testonly
[gecko.git] / image / imgRequestProxy.cpp
blob81a88c1c5ee8219e33d70d4e099d47e76a423190
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(CreateMediumHighRunnable(std::move(aEvent)),
269 NS_DISPATCH_NORMAL);
270 return NS_OK;
273 return NS_DispatchToMainThread(CreateMediumHighRunnable(std::move(aEvent)));
276 void imgRequestProxy::AddToOwner(Document* aLoadingDocument) {
277 // An imgRequestProxy can be initialized with neither a listener nor a
278 // document. The caller could follow up later by cloning the canonical
279 // imgRequestProxy with the actual listener. This is possible because
280 // imgLoader::LoadImage does not require a valid listener to be provided.
282 // Without a listener, we don't need to set our scheduler group, because
283 // we have nothing to signal. However if we were told what document this
284 // is for, it is likely that future listeners will belong to the same
285 // scheduler group.
287 // With a listener, we always need to update our scheduler group. A null
288 // scheduler group is valid with or without a document, but that means
289 // we will use the most generic event target possible on dispatch.
290 if (aLoadingDocument) {
291 RefPtr<mozilla::dom::DocGroup> docGroup = aLoadingDocument->GetDocGroup();
292 if (docGroup) {
293 mEventTarget = docGroup->EventTargetFor(mozilla::TaskCategory::Other);
294 MOZ_ASSERT(mEventTarget);
298 if (mListener && !mEventTarget) {
299 mEventTarget = do_GetMainThread();
302 imgRequest* owner = GetOwner();
303 if (!owner) {
304 return;
307 owner->AddProxy(this);
310 void imgRequestProxy::RemoveFromOwner(nsresult aStatus) {
311 imgRequest* owner = GetOwner();
312 if (owner) {
313 if (mValidating) {
314 imgCacheValidator* validator = owner->GetValidator();
315 MOZ_ASSERT(validator);
316 validator->RemoveProxy(this);
317 mValidating = false;
320 owner->RemoveProxy(this, aStatus);
324 void imgRequestProxy::AddToLoadGroup() {
325 NS_ASSERTION(!mIsInLoadGroup, "Whaa, we're already in the loadgroup!");
326 MOZ_ASSERT(!mForceDispatchLoadGroup);
328 /* While in theory there could be a dispatch outstanding to remove this
329 request from the load group, in practice we only add to the load group
330 (when previously not in a load group) at initialization. */
331 if (!mIsInLoadGroup && mLoadGroup) {
332 LOG_FUNC(gImgLog, "imgRequestProxy::AddToLoadGroup");
333 mLoadGroup->AddRequest(this, nullptr);
334 mIsInLoadGroup = true;
338 void imgRequestProxy::RemoveFromLoadGroup() {
339 if (!mIsInLoadGroup || !mLoadGroup) {
340 return;
343 /* Sometimes we may not be able to remove ourselves from the load group in
344 the current context. This is because our listeners are not re-entrant (e.g.
345 we are in the middle of CancelAndForgetObserver or SyncClone). */
346 if (mForceDispatchLoadGroup) {
347 LOG_FUNC(gImgLog, "imgRequestProxy::RemoveFromLoadGroup -- dispatch");
349 /* We take away the load group from the request temporarily; this prevents
350 additional dispatches via RemoveFromLoadGroup occurring, as well as
351 MoveToBackgroundInLoadGroup from removing and readding. This is safe
352 because we know that once we get here, blocking the load group at all is
353 unnecessary. */
354 mIsInLoadGroup = false;
355 nsCOMPtr<nsILoadGroup> loadGroup = std::move(mLoadGroup);
356 RefPtr<imgRequestProxy> self(this);
357 DispatchWithTargetIfAvailable(NS_NewRunnableFunction(
358 "imgRequestProxy::RemoveFromLoadGroup", [self, loadGroup]() -> void {
359 loadGroup->RemoveRequest(self, nullptr, NS_OK);
360 }));
361 return;
364 LOG_FUNC(gImgLog, "imgRequestProxy::RemoveFromLoadGroup");
366 /* calling RemoveFromLoadGroup may cause the document to finish
367 loading, which could result in our death. We need to make sure
368 that we stay alive long enough to fight another battle... at
369 least until we exit this function. */
370 nsCOMPtr<imgIRequest> kungFuDeathGrip(this);
371 mLoadGroup->RemoveRequest(this, nullptr, NS_OK);
372 mLoadGroup = nullptr;
373 mIsInLoadGroup = false;
376 void imgRequestProxy::MoveToBackgroundInLoadGroup() {
377 /* Even if we are still in the load group, we may have taken away the load
378 group reference itself because we are in the process of leaving the group.
379 In that case, there is no need to background the request. */
380 if (!mLoadGroup) {
381 return;
384 /* There is no need to dispatch if we only need to add ourselves to the load
385 group without removal. It is the removal which causes the problematic
386 callbacks (see RemoveFromLoadGroup). */
387 if (mIsInLoadGroup && mForceDispatchLoadGroup) {
388 LOG_FUNC(gImgLog,
389 "imgRequestProxy::MoveToBackgroundInLoadGroup -- dispatch");
391 RefPtr<imgRequestProxy> self(this);
392 DispatchWithTargetIfAvailable(NS_NewRunnableFunction(
393 "imgRequestProxy::MoveToBackgroundInLoadGroup",
394 [self]() -> void { self->MoveToBackgroundInLoadGroup(); }));
395 return;
398 LOG_FUNC(gImgLog, "imgRequestProxy::MoveToBackgroundInLoadGroup");
399 nsCOMPtr<imgIRequest> kungFuDeathGrip(this);
400 if (mIsInLoadGroup) {
401 mLoadGroup->RemoveRequest(this, nullptr, NS_OK);
404 mLoadFlags |= nsIRequest::LOAD_BACKGROUND;
405 mLoadGroup->AddRequest(this, nullptr);
408 /** nsIRequest / imgIRequest methods **/
410 NS_IMETHODIMP
411 imgRequestProxy::GetName(nsACString& aName) {
412 aName.Truncate();
414 if (mURI) {
415 mURI->GetSpec(aName);
418 return NS_OK;
421 NS_IMETHODIMP
422 imgRequestProxy::IsPending(bool* _retval) { return NS_ERROR_NOT_IMPLEMENTED; }
424 NS_IMETHODIMP
425 imgRequestProxy::GetStatus(nsresult* aStatus) {
426 return NS_ERROR_NOT_IMPLEMENTED;
429 NS_IMETHODIMP
430 imgRequestProxy::Cancel(nsresult status) {
431 if (mCanceled) {
432 return NS_ERROR_FAILURE;
435 LOG_SCOPE(gImgLog, "imgRequestProxy::Cancel");
437 mCanceled = true;
439 nsCOMPtr<nsIRunnable> ev = new imgCancelRunnable(this, status);
440 return DispatchWithTargetIfAvailable(ev.forget());
443 void imgRequestProxy::DoCancel(nsresult status) {
444 RemoveFromOwner(status);
445 RemoveFromLoadGroup();
446 NullOutListener();
449 NS_IMETHODIMP
450 imgRequestProxy::CancelAndForgetObserver(nsresult aStatus) {
451 // If mCanceled is true but mListener is non-null, that means
452 // someone called Cancel() on us but the imgCancelRunnable is still
453 // pending. We still need to null out mListener before returning
454 // from this function in this case. That means we want to do the
455 // RemoveProxy call right now, because we need to deliver the
456 // onStopRequest.
457 if (mCanceled && !mListener) {
458 return NS_ERROR_FAILURE;
461 LOG_SCOPE(gImgLog, "imgRequestProxy::CancelAndForgetObserver");
463 mCanceled = true;
464 mForceDispatchLoadGroup = true;
465 RemoveFromOwner(aStatus);
466 RemoveFromLoadGroup();
467 mForceDispatchLoadGroup = false;
469 NullOutListener();
471 return NS_OK;
474 NS_IMETHODIMP
475 imgRequestProxy::StartDecoding(uint32_t aFlags) {
476 // Flag this, so we know to request after validation if pending.
477 if (IsValidating()) {
478 mDecodeRequested = true;
479 return NS_OK;
482 RefPtr<Image> image = GetImage();
483 if (image) {
484 return image->StartDecoding(aFlags);
487 if (GetOwner()) {
488 GetOwner()->StartDecoding();
491 return NS_OK;
494 bool imgRequestProxy::StartDecodingWithResult(uint32_t aFlags) {
495 // Flag this, so we know to request after validation if pending.
496 if (IsValidating()) {
497 mDecodeRequested = true;
498 return false;
501 RefPtr<Image> image = GetImage();
502 if (image) {
503 return image->StartDecodingWithResult(aFlags);
506 if (GetOwner()) {
507 GetOwner()->StartDecoding();
510 return false;
513 imgIContainer::DecodeResult imgRequestProxy::RequestDecodeWithResult(
514 uint32_t aFlags) {
515 if (IsValidating()) {
516 mDecodeRequested = true;
517 return imgIContainer::DECODE_REQUESTED;
520 RefPtr<Image> image = GetImage();
521 if (image) {
522 return image->RequestDecodeWithResult(aFlags);
525 if (GetOwner()) {
526 GetOwner()->StartDecoding();
529 return imgIContainer::DECODE_REQUESTED;
532 NS_IMETHODIMP
533 imgRequestProxy::LockImage() {
534 mLockCount++;
535 RefPtr<Image> image =
536 GetOwner() && GetOwner()->ImageAvailable() ? GetImage() : nullptr;
537 if (image) {
538 return image->LockImage();
540 return NS_OK;
543 NS_IMETHODIMP
544 imgRequestProxy::UnlockImage() {
545 MOZ_ASSERT(mLockCount > 0, "calling unlock but no locks!");
547 mLockCount--;
548 RefPtr<Image> image =
549 GetOwner() && GetOwner()->ImageAvailable() ? GetImage() : nullptr;
550 if (image) {
551 return image->UnlockImage();
553 return NS_OK;
556 NS_IMETHODIMP
557 imgRequestProxy::RequestDiscard() {
558 RefPtr<Image> image = GetImage();
559 if (image) {
560 return image->RequestDiscard();
562 return NS_OK;
565 NS_IMETHODIMP
566 imgRequestProxy::IncrementAnimationConsumers() {
567 mAnimationConsumers++;
568 RefPtr<Image> image =
569 GetOwner() && GetOwner()->ImageAvailable() ? GetImage() : nullptr;
570 if (image) {
571 image->IncrementAnimationConsumers();
573 return NS_OK;
576 NS_IMETHODIMP
577 imgRequestProxy::DecrementAnimationConsumers() {
578 // We may get here if some responsible code called Increment,
579 // then called us, but we have meanwhile called ClearAnimationConsumers
580 // because we needed to get rid of them earlier (see
581 // imgRequest::RemoveProxy), and hence have nothing left to
582 // decrement. (In such a case we got rid of the animation consumers
583 // early, but not the observer.)
584 if (mAnimationConsumers > 0) {
585 mAnimationConsumers--;
586 RefPtr<Image> image =
587 GetOwner() && GetOwner()->ImageAvailable() ? GetImage() : nullptr;
588 if (image) {
589 image->DecrementAnimationConsumers();
592 return NS_OK;
595 void imgRequestProxy::ClearAnimationConsumers() {
596 while (mAnimationConsumers > 0) {
597 DecrementAnimationConsumers();
601 NS_IMETHODIMP
602 imgRequestProxy::Suspend() { return NS_ERROR_NOT_IMPLEMENTED; }
604 NS_IMETHODIMP
605 imgRequestProxy::Resume() { return NS_ERROR_NOT_IMPLEMENTED; }
607 NS_IMETHODIMP
608 imgRequestProxy::GetLoadGroup(nsILoadGroup** loadGroup) {
609 NS_IF_ADDREF(*loadGroup = mLoadGroup.get());
610 return NS_OK;
612 NS_IMETHODIMP
613 imgRequestProxy::SetLoadGroup(nsILoadGroup* loadGroup) {
614 if (loadGroup != mLoadGroup) {
615 MOZ_ASSERT_UNREACHABLE("Switching load groups is unsupported!");
616 return NS_ERROR_NOT_IMPLEMENTED;
618 return NS_OK;
621 NS_IMETHODIMP
622 imgRequestProxy::GetLoadFlags(nsLoadFlags* flags) {
623 *flags = mLoadFlags;
624 return NS_OK;
626 NS_IMETHODIMP
627 imgRequestProxy::SetLoadFlags(nsLoadFlags flags) {
628 mLoadFlags = flags;
629 return NS_OK;
632 NS_IMETHODIMP
633 imgRequestProxy::GetTRRMode(nsIRequest::TRRMode* aTRRMode) {
634 return GetTRRModeImpl(aTRRMode);
637 NS_IMETHODIMP
638 imgRequestProxy::SetTRRMode(nsIRequest::TRRMode aTRRMode) {
639 return SetTRRModeImpl(aTRRMode);
642 /** imgIRequest methods **/
644 NS_IMETHODIMP
645 imgRequestProxy::GetImage(imgIContainer** aImage) {
646 NS_ENSURE_TRUE(aImage, NS_ERROR_NULL_POINTER);
647 // It's possible that our owner has an image but hasn't notified us of it -
648 // that'll happen if we get Canceled before the owner instantiates its image
649 // (because Canceling unregisters us as a listener on mOwner). If we're
650 // in that situation, just grab the image off of mOwner.
651 RefPtr<Image> image = GetImage();
652 nsCOMPtr<imgIContainer> imageToReturn;
653 if (image) {
654 imageToReturn = image;
656 if (!imageToReturn && GetOwner()) {
657 imageToReturn = GetOwner()->GetImage();
659 if (!imageToReturn) {
660 return NS_ERROR_FAILURE;
663 imageToReturn.swap(*aImage);
665 return NS_OK;
668 NS_IMETHODIMP
669 imgRequestProxy::GetProducerId(uint32_t* aId) {
670 NS_ENSURE_TRUE(aId, NS_ERROR_NULL_POINTER);
672 nsCOMPtr<imgIContainer> image;
673 nsresult rv = GetImage(getter_AddRefs(image));
674 if (NS_SUCCEEDED(rv)) {
675 *aId = image->GetProducerId();
676 } else {
677 *aId = layers::kContainerProducerID_Invalid;
680 return NS_OK;
683 NS_IMETHODIMP
684 imgRequestProxy::GetImageStatus(uint32_t* aStatus) {
685 if (IsValidating()) {
686 // We are currently validating the image, and so our status could revert if
687 // we discard the cache. We should also be deferring notifications, such
688 // that the caller will be notified when validation completes. Rather than
689 // risk misleading the caller, return nothing.
690 *aStatus = imgIRequest::STATUS_NONE;
691 } else {
692 RefPtr<ProgressTracker> progressTracker = GetProgressTracker();
693 *aStatus = progressTracker->GetImageStatus();
696 return NS_OK;
699 NS_IMETHODIMP
700 imgRequestProxy::GetImageErrorCode(nsresult* aStatus) {
701 if (!GetOwner()) {
702 return NS_ERROR_FAILURE;
705 *aStatus = GetOwner()->GetImageErrorCode();
707 return NS_OK;
710 NS_IMETHODIMP
711 imgRequestProxy::GetURI(nsIURI** aURI) {
712 MOZ_ASSERT(NS_IsMainThread(), "Must be on main thread to convert URI");
713 nsCOMPtr<nsIURI> uri = mURI;
714 uri.forget(aURI);
715 return NS_OK;
718 nsresult imgRequestProxy::GetFinalURI(nsIURI** aURI) {
719 if (!GetOwner()) {
720 return NS_ERROR_FAILURE;
723 return GetOwner()->GetFinalURI(aURI);
726 NS_IMETHODIMP
727 imgRequestProxy::GetNotificationObserver(imgINotificationObserver** aObserver) {
728 *aObserver = mListener;
729 NS_IF_ADDREF(*aObserver);
730 return NS_OK;
733 NS_IMETHODIMP
734 imgRequestProxy::GetMimeType(char** aMimeType) {
735 if (!GetOwner()) {
736 return NS_ERROR_FAILURE;
739 const char* type = GetOwner()->GetMimeType();
740 if (!type) {
741 return NS_ERROR_FAILURE;
744 *aMimeType = NS_xstrdup(type);
746 return NS_OK;
749 imgRequestProxy* imgRequestProxy::NewClonedProxy() {
750 return new imgRequestProxy();
753 NS_IMETHODIMP
754 imgRequestProxy::Clone(imgINotificationObserver* aObserver,
755 imgIRequest** aClone) {
756 nsresult result;
757 imgRequestProxy* proxy;
758 result = PerformClone(aObserver, nullptr, /* aSyncNotify */ true, &proxy);
759 *aClone = proxy;
760 return result;
763 nsresult imgRequestProxy::SyncClone(imgINotificationObserver* aObserver,
764 Document* aLoadingDocument,
765 imgRequestProxy** aClone) {
766 return PerformClone(aObserver, aLoadingDocument,
767 /* aSyncNotify */ true, aClone);
770 nsresult imgRequestProxy::Clone(imgINotificationObserver* aObserver,
771 Document* aLoadingDocument,
772 imgRequestProxy** aClone) {
773 return PerformClone(aObserver, aLoadingDocument,
774 /* aSyncNotify */ false, aClone);
777 nsresult imgRequestProxy::PerformClone(imgINotificationObserver* aObserver,
778 Document* aLoadingDocument,
779 bool aSyncNotify,
780 imgRequestProxy** aClone) {
781 MOZ_ASSERT(aClone, "Null out param");
783 LOG_SCOPE(gImgLog, "imgRequestProxy::Clone");
785 *aClone = nullptr;
786 RefPtr<imgRequestProxy> clone = NewClonedProxy();
788 nsCOMPtr<nsILoadGroup> loadGroup;
789 if (aLoadingDocument) {
790 loadGroup = aLoadingDocument->GetDocumentLoadGroup();
793 // It is important to call |SetLoadFlags()| before calling |Init()| because
794 // |Init()| adds the request to the loadgroup.
795 // When a request is added to a loadgroup, its load flags are merged
796 // with the load flags of the loadgroup.
797 // XXXldb That's not true anymore. Stuff from imgLoader adds the
798 // request to the loadgroup.
799 clone->SetLoadFlags(mLoadFlags);
800 nsresult rv = clone->Init(mBehaviour->GetOwner(), loadGroup, aLoadingDocument,
801 mURI, aObserver);
802 if (NS_FAILED(rv)) {
803 return rv;
806 // Assign to *aClone before calling Notify so that if the caller expects to
807 // only be notified for requests it's already holding pointers to it won't be
808 // surprised.
809 NS_ADDREF(*aClone = clone);
811 imgCacheValidator* validator = GetValidator();
812 if (validator) {
813 // Note that if we have a validator, we don't want to issue notifications at
814 // here because we want to defer until that completes. AddProxy will add us
815 // to the load group; we cannot avoid that in this case, because we don't
816 // know when the validation will complete, and if it will cause us to
817 // discard our cached state anyways. We are probably already blocked by the
818 // original LoadImage(WithChannel) request in any event.
819 clone->MarkValidating();
820 validator->AddProxy(clone);
821 } else {
822 // We only want to add the request to the load group of the owning document
823 // if it is still in progress. Some callers cannot handle a supurious load
824 // group removal (e.g. print preview) so we must be careful. On the other
825 // hand, if after cloning, the original request proxy is cancelled /
826 // destroyed, we need to ensure that any clones still block the load group
827 // if it is incomplete.
828 bool addToLoadGroup = mIsInLoadGroup;
829 if (!addToLoadGroup) {
830 RefPtr<ProgressTracker> tracker = clone->GetProgressTracker();
831 addToLoadGroup =
832 tracker && !(tracker->GetProgress() & FLAG_LOAD_COMPLETE);
835 if (addToLoadGroup) {
836 clone->AddToLoadGroup();
839 if (aSyncNotify) {
840 // This is wrong!!! We need to notify asynchronously, but there's code
841 // that assumes that we don't. This will be fixed in bug 580466. Note that
842 // if we have a validator, we won't issue notifications anyways because
843 // they are deferred, so there is no point in requesting.
844 clone->mForceDispatchLoadGroup = true;
845 clone->SyncNotifyListener();
846 clone->mForceDispatchLoadGroup = false;
847 } else {
848 // Without a validator, we can request asynchronous notifications
849 // immediately. If there was a validator, this would override the deferral
850 // and that would be incorrect.
851 clone->NotifyListener();
855 return NS_OK;
858 NS_IMETHODIMP
859 imgRequestProxy::GetImagePrincipal(nsIPrincipal** aPrincipal) {
860 if (!GetOwner()) {
861 return NS_ERROR_FAILURE;
864 nsCOMPtr<nsIPrincipal> principal = GetOwner()->GetPrincipal();
865 principal.forget(aPrincipal);
866 return NS_OK;
869 NS_IMETHODIMP
870 imgRequestProxy::GetHadCrossOriginRedirects(bool* aHadCrossOriginRedirects) {
871 *aHadCrossOriginRedirects = false;
873 nsCOMPtr<nsITimedChannel> timedChannel = TimedChannel();
874 if (timedChannel) {
875 bool allRedirectsSameOrigin = false;
876 *aHadCrossOriginRedirects =
877 NS_SUCCEEDED(
878 timedChannel->GetAllRedirectsSameOrigin(&allRedirectsSameOrigin)) &&
879 !allRedirectsSameOrigin;
882 return NS_OK;
885 NS_IMETHODIMP
886 imgRequestProxy::GetMultipart(bool* aMultipart) {
887 if (!GetOwner()) {
888 return NS_ERROR_FAILURE;
891 *aMultipart = GetOwner()->GetMultipart();
893 return NS_OK;
896 NS_IMETHODIMP
897 imgRequestProxy::GetCORSMode(int32_t* aCorsMode) {
898 if (!GetOwner()) {
899 return NS_ERROR_FAILURE;
902 *aCorsMode = GetOwner()->GetCORSMode();
904 return NS_OK;
907 NS_IMETHODIMP
908 imgRequestProxy::BoostPriority(uint32_t aCategory) {
909 NS_ENSURE_STATE(GetOwner() && !mCanceled);
910 GetOwner()->BoostPriority(aCategory);
911 return NS_OK;
914 /** nsISupportsPriority methods **/
916 NS_IMETHODIMP
917 imgRequestProxy::GetPriority(int32_t* priority) {
918 NS_ENSURE_STATE(GetOwner());
919 *priority = GetOwner()->Priority();
920 return NS_OK;
923 NS_IMETHODIMP
924 imgRequestProxy::SetPriority(int32_t priority) {
925 NS_ENSURE_STATE(GetOwner() && !mCanceled);
926 GetOwner()->AdjustPriority(this, priority - GetOwner()->Priority());
927 return NS_OK;
930 NS_IMETHODIMP
931 imgRequestProxy::AdjustPriority(int32_t priority) {
932 // We don't require |!mCanceled| here. This may be called even if we're
933 // cancelled, because it's invoked as part of the process of removing an image
934 // from the load group.
935 NS_ENSURE_STATE(GetOwner());
936 GetOwner()->AdjustPriority(this, priority);
937 return NS_OK;
940 static const char* NotificationTypeToString(int32_t aType) {
941 switch (aType) {
942 case imgINotificationObserver::SIZE_AVAILABLE:
943 return "SIZE_AVAILABLE";
944 case imgINotificationObserver::FRAME_UPDATE:
945 return "FRAME_UPDATE";
946 case imgINotificationObserver::FRAME_COMPLETE:
947 return "FRAME_COMPLETE";
948 case imgINotificationObserver::LOAD_COMPLETE:
949 return "LOAD_COMPLETE";
950 case imgINotificationObserver::DECODE_COMPLETE:
951 return "DECODE_COMPLETE";
952 case imgINotificationObserver::DISCARD:
953 return "DISCARD";
954 case imgINotificationObserver::UNLOCKED_DRAW:
955 return "UNLOCKED_DRAW";
956 case imgINotificationObserver::IS_ANIMATED:
957 return "IS_ANIMATED";
958 case imgINotificationObserver::HAS_TRANSPARENCY:
959 return "HAS_TRANSPARENCY";
960 default:
961 MOZ_ASSERT_UNREACHABLE("Notification list should be exhaustive");
962 return "(unknown notification)";
966 void imgRequestProxy::Notify(int32_t aType,
967 const mozilla::gfx::IntRect* aRect) {
968 MOZ_ASSERT(aType != imgINotificationObserver::LOAD_COMPLETE,
969 "Should call OnLoadComplete");
971 LOG_FUNC_WITH_PARAM(gImgLog, "imgRequestProxy::Notify", "type",
972 NotificationTypeToString(aType));
974 if (!mListener || mCanceled) {
975 return;
978 // Make sure the listener stays alive while we notify.
979 nsCOMPtr<imgINotificationObserver> listener(mListener);
981 listener->Notify(this, aType, aRect);
984 void imgRequestProxy::OnLoadComplete(bool aLastPart) {
985 LOG_FUNC_WITH_PARAM(gImgLog, "imgRequestProxy::OnLoadComplete", "uri", mURI);
987 // There's all sorts of stuff here that could kill us (the OnStopRequest call
988 // on the listener, the removal from the loadgroup, the release of the
989 // listener, etc). Don't let them do it.
990 RefPtr<imgRequestProxy> self(this);
992 if (mListener && !mCanceled) {
993 // Hold a ref to the listener while we call it, just in case.
994 nsCOMPtr<imgINotificationObserver> listener(mListener);
995 listener->Notify(this, imgINotificationObserver::LOAD_COMPLETE, nullptr);
998 // If we're expecting more data from a multipart channel, re-add ourself
999 // to the loadgroup so that the document doesn't lose track of the load.
1000 // If the request is already a background request and there's more data
1001 // coming, we can just leave the request in the loadgroup as-is.
1002 if (aLastPart || (mLoadFlags & nsIRequest::LOAD_BACKGROUND) == 0) {
1003 if (aLastPart) {
1004 RemoveFromLoadGroup();
1006 nsresult errorCode = NS_OK;
1007 // if the load is cross origin without CORS, or the CORS access is
1008 // rejected, always fire load event to avoid leaking site information for
1009 // <link rel=preload>.
1010 // XXXedgar, currently we don't do the same thing for <img>.
1011 imgRequest* request = GetOwner();
1012 if (!request || !(request->IsDeniedCrossSiteCORSRequest() ||
1013 request->IsCrossSiteNoCORSRequest())) {
1014 uint32_t status = imgIRequest::STATUS_NONE;
1015 GetImageStatus(&status);
1016 if (status & imgIRequest::STATUS_ERROR) {
1017 errorCode = NS_ERROR_FAILURE;
1020 NotifyStop(errorCode);
1021 } else {
1022 // More data is coming, so change the request to be a background request
1023 // and put it back in the loadgroup.
1024 MoveToBackgroundInLoadGroup();
1028 if (mListenerIsStrongRef && aLastPart) {
1029 MOZ_ASSERT(mListener, "How did that happen?");
1030 // Drop our strong ref to the listener now that we're done with
1031 // everything. Note that this can cancel us and other fun things
1032 // like that. Don't add anything in this method after this point.
1033 imgINotificationObserver* obs = mListener;
1034 mListenerIsStrongRef = false;
1035 NS_RELEASE(obs);
1039 void imgRequestProxy::NullOutListener() {
1040 // If we have animation consumers, then they don't matter anymore
1041 if (mListener) {
1042 ClearAnimationConsumers();
1045 if (mListenerIsStrongRef) {
1046 // Releasing could do weird reentery stuff, so just play it super-safe
1047 nsCOMPtr<imgINotificationObserver> obs;
1048 obs.swap(mListener);
1049 mListenerIsStrongRef = false;
1050 } else {
1051 mListener = nullptr;
1055 NS_IMETHODIMP
1056 imgRequestProxy::GetStaticRequest(imgIRequest** aReturn) {
1057 RefPtr<imgRequestProxy> proxy =
1058 GetStaticRequest(static_cast<Document*>(nullptr));
1059 if (proxy != this) {
1060 RefPtr<Image> image = GetImage();
1061 if (image && image->HasError()) {
1062 // image/test/unit/test_async_notification_404.js needs this, but ideally
1063 // this special case can be removed from the scripted codepath.
1064 return NS_ERROR_FAILURE;
1067 proxy.forget(aReturn);
1068 return NS_OK;
1071 already_AddRefed<imgRequestProxy> imgRequestProxy::GetStaticRequest(
1072 Document* aLoadingDocument) {
1073 MOZ_DIAGNOSTIC_ASSERT(!aLoadingDocument ||
1074 aLoadingDocument->IsStaticDocument());
1075 RefPtr<Image> image = GetImage();
1077 bool animated;
1078 if (!image || (NS_SUCCEEDED(image->GetAnimated(&animated)) && !animated)) {
1079 // Early exit - we're not animated, so we don't have to do anything.
1080 return do_AddRef(this);
1083 // We are animated. We need to create a frozen version of this image.
1084 RefPtr<Image> frozenImage = ImageOps::Freeze(image);
1086 // Create a static imgRequestProxy with our new extracted frame.
1087 nsCOMPtr<nsIPrincipal> currentPrincipal;
1088 GetImagePrincipal(getter_AddRefs(currentPrincipal));
1089 bool hadCrossOriginRedirects = true;
1090 GetHadCrossOriginRedirects(&hadCrossOriginRedirects);
1091 RefPtr<imgRequestProxy> req = new imgRequestProxyStatic(
1092 frozenImage, currentPrincipal, hadCrossOriginRedirects);
1093 req->Init(nullptr, nullptr, aLoadingDocument, mURI, nullptr);
1095 return req.forget();
1098 void imgRequestProxy::NotifyListener() {
1099 // It would be nice to notify the observer directly in the status tracker
1100 // instead of through the proxy, but there are several places we do extra
1101 // processing when we receive notifications (like OnStopRequest()), and we
1102 // need to check mCanceled everywhere too.
1104 RefPtr<ProgressTracker> progressTracker = GetProgressTracker();
1105 if (GetOwner()) {
1106 // Send the notifications to our listener asynchronously.
1107 progressTracker->Notify(this);
1108 } else {
1109 // We don't have an imgRequest, so we can only notify the clone of our
1110 // current state, but we still have to do that asynchronously.
1111 MOZ_ASSERT(HasImage(), "if we have no imgRequest, we should have an Image");
1112 progressTracker->NotifyCurrentState(this);
1116 void imgRequestProxy::SyncNotifyListener() {
1117 // It would be nice to notify the observer directly in the status tracker
1118 // instead of through the proxy, but there are several places we do extra
1119 // processing when we receive notifications (like OnStopRequest()), and we
1120 // need to check mCanceled everywhere too.
1122 RefPtr<ProgressTracker> progressTracker = GetProgressTracker();
1123 progressTracker->SyncNotify(this);
1126 void imgRequestProxy::SetHasImage() {
1127 RefPtr<ProgressTracker> progressTracker = GetProgressTracker();
1128 MOZ_ASSERT(progressTracker);
1129 RefPtr<Image> image = progressTracker->GetImage();
1130 MOZ_ASSERT(image);
1132 // Force any private status related to the owner to reflect
1133 // the presence of an image;
1134 mBehaviour->SetOwner(mBehaviour->GetOwner());
1136 // Apply any locks we have
1137 for (uint32_t i = 0; i < mLockCount; ++i) {
1138 image->LockImage();
1141 // Apply any animation consumers we have
1142 for (uint32_t i = 0; i < mAnimationConsumers; i++) {
1143 image->IncrementAnimationConsumers();
1147 already_AddRefed<ProgressTracker> imgRequestProxy::GetProgressTracker() const {
1148 return mBehaviour->GetProgressTracker();
1151 already_AddRefed<mozilla::image::Image> imgRequestProxy::GetImage() const {
1152 return mBehaviour->GetImage();
1155 bool RequestBehaviour::HasImage() const {
1156 if (!mOwnerHasImage) {
1157 return false;
1159 RefPtr<ProgressTracker> progressTracker = GetProgressTracker();
1160 return progressTracker ? progressTracker->HasImage() : false;
1163 bool imgRequestProxy::HasImage() const { return mBehaviour->HasImage(); }
1165 imgRequest* imgRequestProxy::GetOwner() const { return mBehaviour->GetOwner(); }
1167 imgCacheValidator* imgRequestProxy::GetValidator() const {
1168 imgRequest* owner = GetOwner();
1169 if (!owner) {
1170 return nullptr;
1172 return owner->GetValidator();
1175 nsITimedChannel* imgRequestProxy::TimedChannel() {
1176 if (!GetOwner()) {
1177 return nullptr;
1179 return GetOwner()->GetTimedChannel();
1182 ////////////////// imgRequestProxyStatic methods
1184 class StaticBehaviour : public ProxyBehaviour {
1185 public:
1186 explicit StaticBehaviour(mozilla::image::Image* aImage) : mImage(aImage) {}
1188 already_AddRefed<mozilla::image::Image> GetImage() const override {
1189 RefPtr<mozilla::image::Image> image = mImage;
1190 return image.forget();
1193 bool HasImage() const override { return mImage; }
1195 already_AddRefed<ProgressTracker> GetProgressTracker() const override {
1196 return mImage->GetProgressTracker();
1199 imgRequest* GetOwner() const override { return nullptr; }
1201 void SetOwner(imgRequest* aOwner) override {
1202 MOZ_ASSERT(!aOwner,
1203 "We shouldn't be giving static requests a non-null owner.");
1206 private:
1207 // Our image. We have to hold a strong reference here, because that's normally
1208 // the job of the underlying request.
1209 RefPtr<mozilla::image::Image> mImage;
1212 imgRequestProxyStatic::imgRequestProxyStatic(mozilla::image::Image* aImage,
1213 nsIPrincipal* aPrincipal,
1214 bool aHadCrossOriginRedirects)
1215 : mPrincipal(aPrincipal),
1216 mHadCrossOriginRedirects(aHadCrossOriginRedirects) {
1217 mBehaviour = mozilla::MakeUnique<StaticBehaviour>(aImage);
1220 NS_IMETHODIMP
1221 imgRequestProxyStatic::GetImagePrincipal(nsIPrincipal** aPrincipal) {
1222 if (!mPrincipal) {
1223 return NS_ERROR_FAILURE;
1226 NS_ADDREF(*aPrincipal = mPrincipal);
1228 return NS_OK;
1231 NS_IMETHODIMP
1232 imgRequestProxyStatic::GetHadCrossOriginRedirects(
1233 bool* aHadCrossOriginRedirects) {
1234 *aHadCrossOriginRedirects = mHadCrossOriginRedirects;
1235 return NS_OK;
1238 imgRequestProxy* imgRequestProxyStatic::NewClonedProxy() {
1239 nsCOMPtr<nsIPrincipal> currentPrincipal;
1240 GetImagePrincipal(getter_AddRefs(currentPrincipal));
1241 bool hadCrossOriginRedirects = true;
1242 GetHadCrossOriginRedirects(&hadCrossOriginRedirects);
1243 RefPtr<mozilla::image::Image> image = GetImage();
1244 return new imgRequestProxyStatic(image, currentPrincipal,
1245 hadCrossOriginRedirects);