Bug 1604730 [wpt PR 20829] - XPath: Fix context node after evaluating an expression...
[gecko.git] / image / imgRequestProxy.cpp
blob3492d600521a6b648d1eaebaf632bcf06b313696
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
3 * This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "imgRequestProxy.h"
9 #include "ImageLogging.h"
10 #include "imgLoader.h"
11 #include "Image.h"
12 #include "ImageOps.h"
13 #include "ImageTypes.h"
14 #include "nsError.h"
15 #include "nsCRTGlue.h"
16 #include "imgINotificationObserver.h"
17 #include "mozilla/dom/TabGroup.h" // for TabGroup
18 #include "mozilla/dom/DocGroup.h" // for DocGroup
19 #include "mozilla/Move.h"
20 #include "mozilla/Telemetry.h" // for Telemetry
22 using namespace mozilla;
23 using namespace mozilla::image;
24 using mozilla::dom::Document;
26 // The split of imgRequestProxy and imgRequestProxyStatic means that
27 // certain overridden functions need to be usable in the destructor.
28 // Since virtual functions can't be used in that way, this class
29 // provides a behavioural trait for each class to use instead.
30 class ProxyBehaviour {
31 public:
32 virtual ~ProxyBehaviour() = default;
34 virtual already_AddRefed<mozilla::image::Image> GetImage() const = 0;
35 virtual bool HasImage() const = 0;
36 virtual already_AddRefed<ProgressTracker> GetProgressTracker() const = 0;
37 virtual imgRequest* GetOwner() const = 0;
38 virtual void SetOwner(imgRequest* aOwner) = 0;
41 class RequestBehaviour : public ProxyBehaviour {
42 public:
43 RequestBehaviour() : mOwner(nullptr), mOwnerHasImage(false) {}
45 already_AddRefed<mozilla::image::Image> GetImage() const override;
46 bool HasImage() const override;
47 already_AddRefed<ProgressTracker> GetProgressTracker() const override;
49 imgRequest* GetOwner() const override { return mOwner; }
51 void SetOwner(imgRequest* aOwner) override {
52 mOwner = aOwner;
54 if (mOwner) {
55 RefPtr<ProgressTracker> ownerProgressTracker = GetProgressTracker();
56 mOwnerHasImage = ownerProgressTracker && ownerProgressTracker->HasImage();
57 } else {
58 mOwnerHasImage = false;
62 private:
63 // We maintain the following invariant:
64 // The proxy is registered at most with a single imgRequest as an observer,
65 // and whenever it is, mOwner points to that object. This helps ensure that
66 // imgRequestProxy::~imgRequestProxy unregisters the proxy as an observer
67 // from whatever request it was registered with (if any). This, in turn,
68 // means that imgRequest::mObservers will not have any stale pointers in it.
69 RefPtr<imgRequest> mOwner;
71 bool mOwnerHasImage;
74 already_AddRefed<mozilla::image::Image> RequestBehaviour::GetImage() const {
75 if (!mOwnerHasImage) {
76 return nullptr;
78 RefPtr<ProgressTracker> progressTracker = GetProgressTracker();
79 return progressTracker->GetImage();
82 already_AddRefed<ProgressTracker> RequestBehaviour::GetProgressTracker() const {
83 // NOTE: It's possible that our mOwner has an Image that it didn't notify
84 // us about, if we were Canceled before its Image was constructed.
85 // (Canceling removes us as an observer, so mOwner has no way to notify us).
86 // That's why this method uses mOwner->GetProgressTracker() instead of just
87 // mOwner->mProgressTracker -- we might have a null mImage and yet have an
88 // mOwner with a non-null mImage (and a null mProgressTracker pointer).
89 return mOwner->GetProgressTracker();
92 NS_IMPL_ADDREF(imgRequestProxy)
93 NS_IMPL_RELEASE(imgRequestProxy)
95 NS_INTERFACE_MAP_BEGIN(imgRequestProxy)
96 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, imgIRequest)
97 NS_INTERFACE_MAP_ENTRY(imgIRequest)
98 NS_INTERFACE_MAP_ENTRY(nsIRequest)
99 NS_INTERFACE_MAP_ENTRY(nsISupportsPriority)
100 NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsITimedChannel, TimedChannel() != nullptr)
101 NS_INTERFACE_MAP_END
103 imgRequestProxy::imgRequestProxy()
104 : mBehaviour(new RequestBehaviour),
105 mURI(nullptr),
106 mListener(nullptr),
107 mLoadFlags(nsIRequest::LOAD_NORMAL),
108 mLockCount(0),
109 mAnimationConsumers(0),
110 mCanceled(false),
111 mIsInLoadGroup(false),
112 mForceDispatchLoadGroup(false),
113 mListenerIsStrongRef(false),
114 mDecodeRequested(false),
115 mPendingNotify(false),
116 mValidating(false),
117 mHadListener(false),
118 mHadDispatch(false) {
119 /* member initializers and constructor code */
120 LOG_FUNC(gImgLog, "imgRequestProxy::imgRequestProxy");
123 imgRequestProxy::~imgRequestProxy() {
124 /* destructor code */
125 MOZ_ASSERT(!mListener, "Someone forgot to properly cancel this request!");
127 // If we had a listener, that means we would have issued notifications. With
128 // bug 1359833, we added support for main thread scheduler groups. Each
129 // imgRequestProxy may have its own associated listener, document and/or
130 // scheduler group. Typically most imgRequestProxy belong to the same
131 // document, or have no listener, which means we will want to execute all main
132 // thread code in that shared scheduler group. Less frequently, there may be
133 // multiple imgRequests and they have separate documents, which means that
134 // when we issue state notifications, some or all need to be dispatched to the
135 // appropriate scheduler group for each request. This should be rare, so we
136 // want to monitor the frequency of dispatching in the wild.
137 if (mHadListener) {
138 mozilla::Telemetry::Accumulate(mozilla::Telemetry::IMAGE_REQUEST_DISPATCHED,
139 mHadDispatch);
142 MOZ_RELEASE_ASSERT(!mLockCount, "Someone forgot to unlock on time?");
144 ClearAnimationConsumers();
146 // Explicitly set mListener to null to ensure that the RemoveProxy
147 // call below can't send |this| to an arbitrary listener while |this|
148 // is being destroyed. This is all belt-and-suspenders in view of the
149 // above assert.
150 NullOutListener();
152 /* Call RemoveProxy with a successful status. This will keep the
153 channel, if still downloading data, from being canceled if 'this' is
154 the last observer. This allows the image to continue to download and
155 be cached even if no one is using it currently.
157 mCanceled = true;
158 RemoveFromOwner(NS_OK);
160 RemoveFromLoadGroup();
161 LOG_FUNC(gImgLog, "imgRequestProxy::~imgRequestProxy");
164 nsresult imgRequestProxy::Init(imgRequest* aOwner, nsILoadGroup* aLoadGroup,
165 Document* aLoadingDocument, nsIURI* aURI,
166 imgINotificationObserver* aObserver) {
167 MOZ_ASSERT(!GetOwner() && !mListener,
168 "imgRequestProxy is already initialized");
170 LOG_SCOPE_WITH_PARAM(gImgLog, "imgRequestProxy::Init", "request", aOwner);
172 MOZ_ASSERT(mAnimationConsumers == 0, "Cannot have animation before Init");
174 mBehaviour->SetOwner(aOwner);
175 mListener = aObserver;
176 // Make sure to addref mListener before the AddToOwner call below, since
177 // that call might well want to release it if the imgRequest has
178 // already seen OnStopRequest.
179 if (mListener) {
180 mHadListener = true;
181 mListenerIsStrongRef = true;
182 NS_ADDREF(mListener);
184 mLoadGroup = aLoadGroup;
185 mURI = aURI;
187 // Note: AddToOwner won't send all the On* notifications immediately
188 AddToOwner(aLoadingDocument);
190 return NS_OK;
193 nsresult imgRequestProxy::ChangeOwner(imgRequest* aNewOwner) {
194 MOZ_ASSERT(GetOwner(), "Cannot ChangeOwner on a proxy without an owner!");
196 if (mCanceled) {
197 // Ensure that this proxy has received all notifications to date
198 // before we clean it up when removing it from the old owner below.
199 SyncNotifyListener();
202 // If we're holding locks, unlock the old image.
203 // Note that UnlockImage decrements mLockCount each time it's called.
204 uint32_t oldLockCount = mLockCount;
205 while (mLockCount) {
206 UnlockImage();
209 // If we're holding animation requests, undo them.
210 uint32_t oldAnimationConsumers = mAnimationConsumers;
211 ClearAnimationConsumers();
213 GetOwner()->RemoveProxy(this, NS_OK);
215 mBehaviour->SetOwner(aNewOwner);
216 MOZ_ASSERT(!GetValidator(), "New owner cannot be validating!");
218 // If we were locked, apply the locks here
219 for (uint32_t i = 0; i < oldLockCount; i++) {
220 LockImage();
223 // If we had animation requests, restore them here. Note that we
224 // do this *after* RemoveProxy, which clears out animation consumers
225 // (see bug 601723).
226 for (uint32_t i = 0; i < oldAnimationConsumers; i++) {
227 IncrementAnimationConsumers();
230 AddToOwner(nullptr);
231 return NS_OK;
234 void imgRequestProxy::MarkValidating() {
235 MOZ_ASSERT(GetValidator());
236 mValidating = true;
239 void imgRequestProxy::ClearValidating() {
240 MOZ_ASSERT(mValidating);
241 MOZ_ASSERT(!GetValidator());
242 mValidating = false;
244 // If we'd previously requested a synchronous decode, request a decode on the
245 // new image.
246 if (mDecodeRequested) {
247 mDecodeRequested = false;
248 StartDecoding(imgIContainer::FLAG_NONE);
252 bool imgRequestProxy::IsOnEventTarget() const {
253 return true;
256 already_AddRefed<nsIEventTarget> imgRequestProxy::GetEventTarget() const {
257 nsCOMPtr<nsIEventTarget> target(mEventTarget);
258 return target.forget();
261 nsresult imgRequestProxy::DispatchWithTargetIfAvailable(
262 already_AddRefed<nsIRunnable> aEvent) {
263 LOG_FUNC(gImgLog, "imgRequestProxy::DispatchWithTargetIfAvailable");
265 // This method should only be used when it is *expected* that we are
266 // dispatching an event (e.g. we want to handle an event asynchronously)
267 // rather we need to (e.g. we are in the wrong scheduler group context).
268 // As such, we do not set mHadDispatch for telemetry purposes.
269 if (mEventTarget) {
270 mEventTarget->Dispatch(CreateMediumHighRunnable(std::move(aEvent)),
271 NS_DISPATCH_NORMAL);
272 return NS_OK;
275 return NS_DispatchToMainThread(CreateMediumHighRunnable(std::move(aEvent)));
278 void imgRequestProxy::DispatchWithTarget(already_AddRefed<nsIRunnable> aEvent) {
279 LOG_FUNC(gImgLog, "imgRequestProxy::DispatchWithTarget");
281 MOZ_ASSERT(mListener || mTabGroup);
282 MOZ_ASSERT(mEventTarget);
284 mHadDispatch = true;
285 mEventTarget->Dispatch(CreateMediumHighRunnable(std::move(aEvent)),
286 NS_DISPATCH_NORMAL);
289 void imgRequestProxy::AddToOwner(Document* aLoadingDocument) {
290 // An imgRequestProxy can be initialized with neither a listener nor a
291 // document. The caller could follow up later by cloning the canonical
292 // imgRequestProxy with the actual listener. This is possible because
293 // imgLoader::LoadImage does not require a valid listener to be provided.
295 // Without a listener, we don't need to set our scheduler group, because
296 // we have nothing to signal. However if we were told what document this
297 // is for, it is likely that future listeners will belong to the same
298 // scheduler group.
300 // With a listener, we always need to update our scheduler group. A null
301 // scheduler group is valid with or without a document, but that means
302 // we will use the most generic event target possible on dispatch.
303 if (aLoadingDocument) {
304 RefPtr<mozilla::dom::DocGroup> docGroup = aLoadingDocument->GetDocGroup();
305 if (docGroup) {
306 mTabGroup = docGroup->GetTabGroup();
307 MOZ_ASSERT(mTabGroup);
309 mEventTarget = docGroup->EventTargetFor(mozilla::TaskCategory::Other);
310 MOZ_ASSERT(mEventTarget);
314 if (mListener && !mEventTarget) {
315 mEventTarget = do_GetMainThread();
318 imgRequest* owner = GetOwner();
319 if (!owner) {
320 return;
323 owner->AddProxy(this);
326 void imgRequestProxy::RemoveFromOwner(nsresult aStatus) {
327 imgRequest* owner = GetOwner();
328 if (owner) {
329 if (mValidating) {
330 imgCacheValidator* validator = owner->GetValidator();
331 MOZ_ASSERT(validator);
332 validator->RemoveProxy(this);
333 mValidating = false;
336 owner->RemoveProxy(this, aStatus);
340 void imgRequestProxy::AddToLoadGroup() {
341 NS_ASSERTION(!mIsInLoadGroup, "Whaa, we're already in the loadgroup!");
342 MOZ_ASSERT(!mForceDispatchLoadGroup);
344 /* While in theory there could be a dispatch outstanding to remove this
345 request from the load group, in practice we only add to the load group
346 (when previously not in a load group) at initialization. */
347 if (!mIsInLoadGroup && mLoadGroup) {
348 LOG_FUNC(gImgLog, "imgRequestProxy::AddToLoadGroup");
349 mLoadGroup->AddRequest(this, nullptr);
350 mIsInLoadGroup = true;
354 void imgRequestProxy::RemoveFromLoadGroup() {
355 if (!mIsInLoadGroup || !mLoadGroup) {
356 return;
359 /* Sometimes we may not be able to remove ourselves from the load group in
360 the current context. This is because our listeners are not re-entrant (e.g.
361 we are in the middle of CancelAndForgetObserver or SyncClone). */
362 if (mForceDispatchLoadGroup) {
363 LOG_FUNC(gImgLog, "imgRequestProxy::RemoveFromLoadGroup -- dispatch");
365 /* We take away the load group from the request temporarily; this prevents
366 additional dispatches via RemoveFromLoadGroup occurring, as well as
367 MoveToBackgroundInLoadGroup from removing and readding. This is safe
368 because we know that once we get here, blocking the load group at all is
369 unnecessary. */
370 mIsInLoadGroup = false;
371 nsCOMPtr<nsILoadGroup> loadGroup = std::move(mLoadGroup);
372 RefPtr<imgRequestProxy> self(this);
373 DispatchWithTargetIfAvailable(NS_NewRunnableFunction(
374 "imgRequestProxy::RemoveFromLoadGroup", [self, loadGroup]() -> void {
375 loadGroup->RemoveRequest(self, nullptr, NS_OK);
376 }));
377 return;
380 LOG_FUNC(gImgLog, "imgRequestProxy::RemoveFromLoadGroup");
382 /* calling RemoveFromLoadGroup may cause the document to finish
383 loading, which could result in our death. We need to make sure
384 that we stay alive long enough to fight another battle... at
385 least until we exit this function. */
386 nsCOMPtr<imgIRequest> kungFuDeathGrip(this);
387 mLoadGroup->RemoveRequest(this, nullptr, NS_OK);
388 mLoadGroup = nullptr;
389 mIsInLoadGroup = false;
392 void imgRequestProxy::MoveToBackgroundInLoadGroup() {
393 /* Even if we are still in the load group, we may have taken away the load
394 group reference itself because we are in the process of leaving the group.
395 In that case, there is no need to background the request. */
396 if (!mLoadGroup) {
397 return;
400 /* There is no need to dispatch if we only need to add ourselves to the load
401 group without removal. It is the removal which causes the problematic
402 callbacks (see RemoveFromLoadGroup). */
403 if (mIsInLoadGroup && mForceDispatchLoadGroup) {
404 LOG_FUNC(gImgLog,
405 "imgRequestProxy::MoveToBackgroundInLoadGroup -- dispatch");
407 RefPtr<imgRequestProxy> self(this);
408 DispatchWithTargetIfAvailable(NS_NewRunnableFunction(
409 "imgRequestProxy::MoveToBackgroundInLoadGroup",
410 [self]() -> void { self->MoveToBackgroundInLoadGroup(); }));
411 return;
414 LOG_FUNC(gImgLog, "imgRequestProxy::MoveToBackgroundInLoadGroup");
415 nsCOMPtr<imgIRequest> kungFuDeathGrip(this);
416 if (mIsInLoadGroup) {
417 mLoadGroup->RemoveRequest(this, nullptr, NS_OK);
420 mLoadFlags |= nsIRequest::LOAD_BACKGROUND;
421 mLoadGroup->AddRequest(this, nullptr);
424 /** nsIRequest / imgIRequest methods **/
426 NS_IMETHODIMP
427 imgRequestProxy::GetName(nsACString& aName) {
428 aName.Truncate();
430 if (mURI) {
431 mURI->GetSpec(aName);
434 return NS_OK;
437 NS_IMETHODIMP
438 imgRequestProxy::IsPending(bool* _retval) { return NS_ERROR_NOT_IMPLEMENTED; }
440 NS_IMETHODIMP
441 imgRequestProxy::GetStatus(nsresult* aStatus) {
442 return NS_ERROR_NOT_IMPLEMENTED;
445 NS_IMETHODIMP
446 imgRequestProxy::Cancel(nsresult status) {
447 if (mCanceled) {
448 return NS_ERROR_FAILURE;
451 LOG_SCOPE(gImgLog, "imgRequestProxy::Cancel");
453 mCanceled = true;
455 nsCOMPtr<nsIRunnable> ev = new imgCancelRunnable(this, status);
456 return DispatchWithTargetIfAvailable(ev.forget());
459 void imgRequestProxy::DoCancel(nsresult status) {
460 RemoveFromOwner(status);
461 RemoveFromLoadGroup();
462 NullOutListener();
465 NS_IMETHODIMP
466 imgRequestProxy::CancelAndForgetObserver(nsresult aStatus) {
467 // If mCanceled is true but mListener is non-null, that means
468 // someone called Cancel() on us but the imgCancelRunnable is still
469 // pending. We still need to null out mListener before returning
470 // from this function in this case. That means we want to do the
471 // RemoveProxy call right now, because we need to deliver the
472 // onStopRequest.
473 if (mCanceled && !mListener) {
474 return NS_ERROR_FAILURE;
477 LOG_SCOPE(gImgLog, "imgRequestProxy::CancelAndForgetObserver");
479 mCanceled = true;
480 mForceDispatchLoadGroup = true;
481 RemoveFromOwner(aStatus);
482 RemoveFromLoadGroup();
483 mForceDispatchLoadGroup = false;
485 NullOutListener();
487 return NS_OK;
490 NS_IMETHODIMP
491 imgRequestProxy::StartDecoding(uint32_t aFlags) {
492 // Flag this, so we know to request after validation if pending.
493 if (IsValidating()) {
494 mDecodeRequested = true;
495 return NS_OK;
498 RefPtr<Image> image = GetImage();
499 if (image) {
500 return image->StartDecoding(aFlags);
503 if (GetOwner()) {
504 GetOwner()->StartDecoding();
507 return NS_OK;
510 bool imgRequestProxy::StartDecodingWithResult(uint32_t aFlags) {
511 // Flag this, so we know to request after validation if pending.
512 if (IsValidating()) {
513 mDecodeRequested = true;
514 return false;
517 RefPtr<Image> image = GetImage();
518 if (image) {
519 return image->StartDecodingWithResult(aFlags);
522 if (GetOwner()) {
523 GetOwner()->StartDecoding();
526 return false;
529 bool imgRequestProxy::RequestDecodeWithResult(uint32_t aFlags) {
530 if (IsValidating()) {
531 mDecodeRequested = true;
532 return false;
535 RefPtr<Image> image = GetImage();
536 if (image) {
537 return image->RequestDecodeWithResult(aFlags);
540 if (GetOwner()) {
541 GetOwner()->StartDecoding();
544 return false;
547 NS_IMETHODIMP
548 imgRequestProxy::LockImage() {
549 mLockCount++;
550 RefPtr<Image> image =
551 GetOwner() && GetOwner()->ImageAvailable() ? GetImage() : nullptr;
552 if (image) {
553 return image->LockImage();
555 return NS_OK;
558 NS_IMETHODIMP
559 imgRequestProxy::UnlockImage() {
560 MOZ_ASSERT(mLockCount > 0, "calling unlock but no locks!");
562 mLockCount--;
563 RefPtr<Image> image =
564 GetOwner() && GetOwner()->ImageAvailable() ? GetImage() : nullptr;
565 if (image) {
566 return image->UnlockImage();
568 return NS_OK;
571 NS_IMETHODIMP
572 imgRequestProxy::RequestDiscard() {
573 RefPtr<Image> image = GetImage();
574 if (image) {
575 return image->RequestDiscard();
577 return NS_OK;
580 NS_IMETHODIMP
581 imgRequestProxy::IncrementAnimationConsumers() {
582 mAnimationConsumers++;
583 RefPtr<Image> image =
584 GetOwner() && GetOwner()->ImageAvailable() ? GetImage() : nullptr;
585 if (image) {
586 image->IncrementAnimationConsumers();
588 return NS_OK;
591 NS_IMETHODIMP
592 imgRequestProxy::DecrementAnimationConsumers() {
593 // We may get here if some responsible code called Increment,
594 // then called us, but we have meanwhile called ClearAnimationConsumers
595 // because we needed to get rid of them earlier (see
596 // imgRequest::RemoveProxy), and hence have nothing left to
597 // decrement. (In such a case we got rid of the animation consumers
598 // early, but not the observer.)
599 if (mAnimationConsumers > 0) {
600 mAnimationConsumers--;
601 RefPtr<Image> image =
602 GetOwner() && GetOwner()->ImageAvailable() ? GetImage() : nullptr;
603 if (image) {
604 image->DecrementAnimationConsumers();
607 return NS_OK;
610 void imgRequestProxy::ClearAnimationConsumers() {
611 while (mAnimationConsumers > 0) {
612 DecrementAnimationConsumers();
616 NS_IMETHODIMP
617 imgRequestProxy::Suspend() { return NS_ERROR_NOT_IMPLEMENTED; }
619 NS_IMETHODIMP
620 imgRequestProxy::Resume() { return NS_ERROR_NOT_IMPLEMENTED; }
622 NS_IMETHODIMP
623 imgRequestProxy::GetLoadGroup(nsILoadGroup** loadGroup) {
624 NS_IF_ADDREF(*loadGroup = mLoadGroup.get());
625 return NS_OK;
627 NS_IMETHODIMP
628 imgRequestProxy::SetLoadGroup(nsILoadGroup* loadGroup) {
629 if (loadGroup != mLoadGroup) {
630 MOZ_ASSERT_UNREACHABLE("Switching load groups is unsupported!");
631 return NS_ERROR_NOT_IMPLEMENTED;
633 return NS_OK;
636 NS_IMETHODIMP
637 imgRequestProxy::GetLoadFlags(nsLoadFlags* flags) {
638 *flags = mLoadFlags;
639 return NS_OK;
641 NS_IMETHODIMP
642 imgRequestProxy::SetLoadFlags(nsLoadFlags flags) {
643 mLoadFlags = flags;
644 return NS_OK;
647 /** imgIRequest methods **/
649 NS_IMETHODIMP
650 imgRequestProxy::GetImage(imgIContainer** aImage) {
651 NS_ENSURE_TRUE(aImage, NS_ERROR_NULL_POINTER);
652 // It's possible that our owner has an image but hasn't notified us of it -
653 // that'll happen if we get Canceled before the owner instantiates its image
654 // (because Canceling unregisters us as a listener on mOwner). If we're
655 // in that situation, just grab the image off of mOwner.
656 RefPtr<Image> image = GetImage();
657 nsCOMPtr<imgIContainer> imageToReturn;
658 if (image) {
659 imageToReturn = image;
661 if (!imageToReturn && GetOwner()) {
662 imageToReturn = GetOwner()->GetImage();
664 if (!imageToReturn) {
665 return NS_ERROR_FAILURE;
668 imageToReturn.swap(*aImage);
670 return NS_OK;
673 NS_IMETHODIMP
674 imgRequestProxy::GetProducerId(uint32_t* aId) {
675 NS_ENSURE_TRUE(aId, NS_ERROR_NULL_POINTER);
677 nsCOMPtr<imgIContainer> image;
678 nsresult rv = GetImage(getter_AddRefs(image));
679 if (NS_SUCCEEDED(rv)) {
680 *aId = image->GetProducerId();
681 } else {
682 *aId = layers::kContainerProducerID_Invalid;
685 return NS_OK;
688 NS_IMETHODIMP
689 imgRequestProxy::GetImageStatus(uint32_t* aStatus) {
690 if (IsValidating()) {
691 // We are currently validating the image, and so our status could revert if
692 // we discard the cache. We should also be deferring notifications, such
693 // that the caller will be notified when validation completes. Rather than
694 // risk misleading the caller, return nothing.
695 *aStatus = imgIRequest::STATUS_NONE;
696 } else {
697 RefPtr<ProgressTracker> progressTracker = GetProgressTracker();
698 *aStatus = progressTracker->GetImageStatus();
701 return NS_OK;
704 NS_IMETHODIMP
705 imgRequestProxy::GetImageErrorCode(nsresult* aStatus) {
706 if (!GetOwner()) {
707 return NS_ERROR_FAILURE;
710 *aStatus = GetOwner()->GetImageErrorCode();
712 return NS_OK;
715 NS_IMETHODIMP
716 imgRequestProxy::GetURI(nsIURI** aURI) {
717 MOZ_ASSERT(NS_IsMainThread(), "Must be on main thread to convert URI");
718 nsCOMPtr<nsIURI> uri = mURI;
719 uri.forget(aURI);
720 return NS_OK;
723 nsresult imgRequestProxy::GetFinalURI(nsIURI** aURI) {
724 if (!GetOwner()) {
725 return NS_ERROR_FAILURE;
728 return GetOwner()->GetFinalURI(aURI);
731 NS_IMETHODIMP
732 imgRequestProxy::GetNotificationObserver(imgINotificationObserver** aObserver) {
733 *aObserver = mListener;
734 NS_IF_ADDREF(*aObserver);
735 return NS_OK;
738 NS_IMETHODIMP
739 imgRequestProxy::GetMimeType(char** aMimeType) {
740 if (!GetOwner()) {
741 return NS_ERROR_FAILURE;
744 const char* type = GetOwner()->GetMimeType();
745 if (!type) {
746 return NS_ERROR_FAILURE;
749 *aMimeType = NS_xstrdup(type);
751 return NS_OK;
754 imgRequestProxy* imgRequestProxy::NewClonedProxy() {
755 return new imgRequestProxy();
758 NS_IMETHODIMP
759 imgRequestProxy::Clone(imgINotificationObserver* aObserver,
760 imgIRequest** aClone) {
761 nsresult result;
762 imgRequestProxy* proxy;
763 result = PerformClone(aObserver, nullptr, /* aSyncNotify */ true, &proxy);
764 *aClone = proxy;
765 return result;
768 nsresult imgRequestProxy::SyncClone(imgINotificationObserver* aObserver,
769 Document* aLoadingDocument,
770 imgRequestProxy** aClone) {
771 return PerformClone(aObserver, aLoadingDocument,
772 /* aSyncNotify */ true, aClone);
775 nsresult imgRequestProxy::Clone(imgINotificationObserver* aObserver,
776 Document* aLoadingDocument,
777 imgRequestProxy** aClone) {
778 return PerformClone(aObserver, aLoadingDocument,
779 /* aSyncNotify */ false, aClone);
782 nsresult imgRequestProxy::PerformClone(imgINotificationObserver* aObserver,
783 Document* aLoadingDocument,
784 bool aSyncNotify,
785 imgRequestProxy** aClone) {
786 MOZ_ASSERT(aClone, "Null out param");
788 LOG_SCOPE(gImgLog, "imgRequestProxy::Clone");
790 *aClone = nullptr;
791 RefPtr<imgRequestProxy> clone = NewClonedProxy();
793 nsCOMPtr<nsILoadGroup> loadGroup;
794 if (aLoadingDocument) {
795 loadGroup = aLoadingDocument->GetDocumentLoadGroup();
798 // It is important to call |SetLoadFlags()| before calling |Init()| because
799 // |Init()| adds the request to the loadgroup.
800 // When a request is added to a loadgroup, its load flags are merged
801 // with the load flags of the loadgroup.
802 // XXXldb That's not true anymore. Stuff from imgLoader adds the
803 // request to the loadgroup.
804 clone->SetLoadFlags(mLoadFlags);
805 nsresult rv = clone->Init(mBehaviour->GetOwner(), loadGroup, aLoadingDocument,
806 mURI, aObserver);
807 if (NS_FAILED(rv)) {
808 return rv;
811 // Assign to *aClone before calling Notify so that if the caller expects to
812 // only be notified for requests it's already holding pointers to it won't be
813 // surprised.
814 NS_ADDREF(*aClone = clone);
816 imgCacheValidator* validator = GetValidator();
817 if (validator) {
818 // Note that if we have a validator, we don't want to issue notifications at
819 // here because we want to defer until that completes. AddProxy will add us
820 // to the load group; we cannot avoid that in this case, because we don't
821 // know when the validation will complete, and if it will cause us to
822 // discard our cached state anyways. We are probably already blocked by the
823 // original LoadImage(WithChannel) request in any event.
824 clone->MarkValidating();
825 validator->AddProxy(clone);
826 } else {
827 // We only want to add the request to the load group of the owning document
828 // if it is still in progress. Some callers cannot handle a supurious load
829 // group removal (e.g. print preview) so we must be careful. On the other
830 // hand, if after cloning, the original request proxy is cancelled /
831 // destroyed, we need to ensure that any clones still block the load group
832 // if it is incomplete.
833 bool addToLoadGroup = mIsInLoadGroup;
834 if (!addToLoadGroup) {
835 RefPtr<ProgressTracker> tracker = clone->GetProgressTracker();
836 addToLoadGroup =
837 tracker && !(tracker->GetProgress() & FLAG_LOAD_COMPLETE);
840 if (addToLoadGroup) {
841 clone->AddToLoadGroup();
844 if (aSyncNotify) {
845 // This is wrong!!! We need to notify asynchronously, but there's code
846 // that assumes that we don't. This will be fixed in bug 580466. Note that
847 // if we have a validator, we won't issue notifications anyways because
848 // they are deferred, so there is no point in requesting.
849 clone->mForceDispatchLoadGroup = true;
850 clone->SyncNotifyListener();
851 clone->mForceDispatchLoadGroup = false;
852 } else {
853 // Without a validator, we can request asynchronous notifications
854 // immediately. If there was a validator, this would override the deferral
855 // and that would be incorrect.
856 clone->NotifyListener();
860 return NS_OK;
863 NS_IMETHODIMP
864 imgRequestProxy::GetImagePrincipal(nsIPrincipal** aPrincipal) {
865 if (!GetOwner()) {
866 return NS_ERROR_FAILURE;
869 nsCOMPtr<nsIPrincipal> principal = GetOwner()->GetPrincipal();
870 principal.forget(aPrincipal);
871 return NS_OK;
874 NS_IMETHODIMP
875 imgRequestProxy::GetHadCrossOriginRedirects(bool* aHadCrossOriginRedirects) {
876 *aHadCrossOriginRedirects = false;
878 nsCOMPtr<nsITimedChannel> timedChannel = TimedChannel();
879 if (timedChannel) {
880 bool allRedirectsSameOrigin = false;
881 *aHadCrossOriginRedirects =
882 NS_SUCCEEDED(
883 timedChannel->GetAllRedirectsSameOrigin(&allRedirectsSameOrigin)) &&
884 !allRedirectsSameOrigin;
887 return NS_OK;
890 NS_IMETHODIMP
891 imgRequestProxy::GetMultipart(bool* aMultipart) {
892 if (!GetOwner()) {
893 return NS_ERROR_FAILURE;
896 *aMultipart = GetOwner()->GetMultipart();
898 return NS_OK;
901 NS_IMETHODIMP
902 imgRequestProxy::GetCORSMode(int32_t* aCorsMode) {
903 if (!GetOwner()) {
904 return NS_ERROR_FAILURE;
907 *aCorsMode = GetOwner()->GetCORSMode();
909 return NS_OK;
912 NS_IMETHODIMP
913 imgRequestProxy::BoostPriority(uint32_t aCategory) {
914 NS_ENSURE_STATE(GetOwner() && !mCanceled);
915 GetOwner()->BoostPriority(aCategory);
916 return NS_OK;
919 /** nsISupportsPriority methods **/
921 NS_IMETHODIMP
922 imgRequestProxy::GetPriority(int32_t* priority) {
923 NS_ENSURE_STATE(GetOwner());
924 *priority = GetOwner()->Priority();
925 return NS_OK;
928 NS_IMETHODIMP
929 imgRequestProxy::SetPriority(int32_t priority) {
930 NS_ENSURE_STATE(GetOwner() && !mCanceled);
931 GetOwner()->AdjustPriority(this, priority - GetOwner()->Priority());
932 return NS_OK;
935 NS_IMETHODIMP
936 imgRequestProxy::AdjustPriority(int32_t priority) {
937 // We don't require |!mCanceled| here. This may be called even if we're
938 // cancelled, because it's invoked as part of the process of removing an image
939 // from the load group.
940 NS_ENSURE_STATE(GetOwner());
941 GetOwner()->AdjustPriority(this, priority);
942 return NS_OK;
945 static const char* NotificationTypeToString(int32_t aType) {
946 switch (aType) {
947 case imgINotificationObserver::SIZE_AVAILABLE:
948 return "SIZE_AVAILABLE";
949 case imgINotificationObserver::FRAME_UPDATE:
950 return "FRAME_UPDATE";
951 case imgINotificationObserver::FRAME_COMPLETE:
952 return "FRAME_COMPLETE";
953 case imgINotificationObserver::LOAD_COMPLETE:
954 return "LOAD_COMPLETE";
955 case imgINotificationObserver::DECODE_COMPLETE:
956 return "DECODE_COMPLETE";
957 case imgINotificationObserver::DISCARD:
958 return "DISCARD";
959 case imgINotificationObserver::UNLOCKED_DRAW:
960 return "UNLOCKED_DRAW";
961 case imgINotificationObserver::IS_ANIMATED:
962 return "IS_ANIMATED";
963 case imgINotificationObserver::HAS_TRANSPARENCY:
964 return "HAS_TRANSPARENCY";
965 default:
966 MOZ_ASSERT_UNREACHABLE("Notification list should be exhaustive");
967 return "(unknown notification)";
971 void imgRequestProxy::Notify(int32_t aType,
972 const mozilla::gfx::IntRect* aRect) {
973 MOZ_ASSERT(aType != imgINotificationObserver::LOAD_COMPLETE,
974 "Should call OnLoadComplete");
976 LOG_FUNC_WITH_PARAM(gImgLog, "imgRequestProxy::Notify", "type",
977 NotificationTypeToString(aType));
979 if (!mListener || mCanceled) {
980 return;
983 if (!IsOnEventTarget()) {
984 RefPtr<imgRequestProxy> self(this);
985 if (aRect) {
986 const mozilla::gfx::IntRect rect = *aRect;
987 DispatchWithTarget(NS_NewRunnableFunction(
988 "imgRequestProxy::Notify",
989 [self, rect, aType]() -> void { self->Notify(aType, &rect); }));
990 } else {
991 DispatchWithTarget(NS_NewRunnableFunction(
992 "imgRequestProxy::Notify",
993 [self, aType]() -> void { self->Notify(aType, nullptr); }));
995 return;
998 // Make sure the listener stays alive while we notify.
999 nsCOMPtr<imgINotificationObserver> listener(mListener);
1001 listener->Notify(this, aType, aRect);
1004 void imgRequestProxy::OnLoadComplete(bool aLastPart) {
1005 LOG_FUNC_WITH_PARAM(gImgLog, "imgRequestProxy::OnLoadComplete", "uri", mURI);
1007 // There's all sorts of stuff here that could kill us (the OnStopRequest call
1008 // on the listener, the removal from the loadgroup, the release of the
1009 // listener, etc). Don't let them do it.
1010 RefPtr<imgRequestProxy> self(this);
1012 if (!IsOnEventTarget()) {
1013 DispatchWithTarget(NS_NewRunnableFunction(
1014 "imgRequestProxy::OnLoadComplete",
1015 [self, aLastPart]() -> void { self->OnLoadComplete(aLastPart); }));
1016 return;
1019 if (mListener && !mCanceled) {
1020 // Hold a ref to the listener while we call it, just in case.
1021 nsCOMPtr<imgINotificationObserver> listener(mListener);
1022 listener->Notify(this, imgINotificationObserver::LOAD_COMPLETE, nullptr);
1025 // If we're expecting more data from a multipart channel, re-add ourself
1026 // to the loadgroup so that the document doesn't lose track of the load.
1027 // If the request is already a background request and there's more data
1028 // coming, we can just leave the request in the loadgroup as-is.
1029 if (aLastPart || (mLoadFlags & nsIRequest::LOAD_BACKGROUND) == 0) {
1030 if (aLastPart) {
1031 RemoveFromLoadGroup();
1032 } else {
1033 // More data is coming, so change the request to be a background request
1034 // and put it back in the loadgroup.
1035 MoveToBackgroundInLoadGroup();
1039 if (mListenerIsStrongRef && aLastPart) {
1040 MOZ_ASSERT(mListener, "How did that happen?");
1041 // Drop our strong ref to the listener now that we're done with
1042 // everything. Note that this can cancel us and other fun things
1043 // like that. Don't add anything in this method after this point.
1044 imgINotificationObserver* obs = mListener;
1045 mListenerIsStrongRef = false;
1046 NS_RELEASE(obs);
1050 void imgRequestProxy::NullOutListener() {
1051 // If we have animation consumers, then they don't matter anymore
1052 if (mListener) {
1053 ClearAnimationConsumers();
1056 if (mListenerIsStrongRef) {
1057 // Releasing could do weird reentery stuff, so just play it super-safe
1058 nsCOMPtr<imgINotificationObserver> obs;
1059 obs.swap(mListener);
1060 mListenerIsStrongRef = false;
1061 } else {
1062 mListener = nullptr;
1065 // Note that we don't free the event target. We actually need that to ensure
1066 // we get removed from the ProgressTracker properly. No harm in keeping it
1067 // however.
1068 mTabGroup = nullptr;
1071 NS_IMETHODIMP
1072 imgRequestProxy::GetStaticRequest(imgIRequest** aReturn) {
1073 imgRequestProxy* proxy;
1074 nsresult result = GetStaticRequest(nullptr, &proxy);
1075 *aReturn = proxy;
1076 return result;
1079 nsresult imgRequestProxy::GetStaticRequest(Document* aLoadingDocument,
1080 imgRequestProxy** aReturn) {
1081 *aReturn = nullptr;
1082 RefPtr<Image> image = GetImage();
1084 bool animated;
1085 if (!image || (NS_SUCCEEDED(image->GetAnimated(&animated)) && !animated)) {
1086 // Early exit - we're not animated, so we don't have to do anything.
1087 NS_ADDREF(*aReturn = this);
1088 return NS_OK;
1091 // Check for errors in the image. Callers code rely on GetStaticRequest
1092 // failing in this case, though with FrozenImage there's no technical reason
1093 // for it anymore.
1094 if (image->HasError()) {
1095 return NS_ERROR_FAILURE;
1098 // We are animated. We need to create a frozen version of this image.
1099 RefPtr<Image> frozenImage = ImageOps::Freeze(image);
1101 // Create a static imgRequestProxy with our new extracted frame.
1102 nsCOMPtr<nsIPrincipal> currentPrincipal;
1103 GetImagePrincipal(getter_AddRefs(currentPrincipal));
1104 bool hadCrossOriginRedirects = true;
1105 GetHadCrossOriginRedirects(&hadCrossOriginRedirects);
1106 RefPtr<imgRequestProxy> req = new imgRequestProxyStatic(
1107 frozenImage, currentPrincipal, hadCrossOriginRedirects);
1108 req->Init(nullptr, nullptr, aLoadingDocument, mURI, nullptr);
1110 NS_ADDREF(*aReturn = req);
1112 return NS_OK;
1115 void imgRequestProxy::NotifyListener() {
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 if (GetOwner()) {
1123 // Send the notifications to our listener asynchronously.
1124 progressTracker->Notify(this);
1125 } else {
1126 // We don't have an imgRequest, so we can only notify the clone of our
1127 // current state, but we still have to do that asynchronously.
1128 MOZ_ASSERT(HasImage(), "if we have no imgRequest, we should have an Image");
1129 progressTracker->NotifyCurrentState(this);
1133 void imgRequestProxy::SyncNotifyListener() {
1134 // It would be nice to notify the observer directly in the status tracker
1135 // instead of through the proxy, but there are several places we do extra
1136 // processing when we receive notifications (like OnStopRequest()), and we
1137 // need to check mCanceled everywhere too.
1139 RefPtr<ProgressTracker> progressTracker = GetProgressTracker();
1140 progressTracker->SyncNotify(this);
1143 void imgRequestProxy::SetHasImage() {
1144 RefPtr<ProgressTracker> progressTracker = GetProgressTracker();
1145 MOZ_ASSERT(progressTracker);
1146 RefPtr<Image> image = progressTracker->GetImage();
1147 MOZ_ASSERT(image);
1149 // Force any private status related to the owner to reflect
1150 // the presence of an image;
1151 mBehaviour->SetOwner(mBehaviour->GetOwner());
1153 // Apply any locks we have
1154 for (uint32_t i = 0; i < mLockCount; ++i) {
1155 image->LockImage();
1158 // Apply any animation consumers we have
1159 for (uint32_t i = 0; i < mAnimationConsumers; i++) {
1160 image->IncrementAnimationConsumers();
1164 already_AddRefed<ProgressTracker> imgRequestProxy::GetProgressTracker() const {
1165 return mBehaviour->GetProgressTracker();
1168 already_AddRefed<mozilla::image::Image> imgRequestProxy::GetImage() const {
1169 return mBehaviour->GetImage();
1172 bool RequestBehaviour::HasImage() const {
1173 if (!mOwnerHasImage) {
1174 return false;
1176 RefPtr<ProgressTracker> progressTracker = GetProgressTracker();
1177 return progressTracker ? progressTracker->HasImage() : false;
1180 bool imgRequestProxy::HasImage() const { return mBehaviour->HasImage(); }
1182 imgRequest* imgRequestProxy::GetOwner() const { return mBehaviour->GetOwner(); }
1184 imgCacheValidator* imgRequestProxy::GetValidator() const {
1185 imgRequest* owner = GetOwner();
1186 if (!owner) {
1187 return nullptr;
1189 return owner->GetValidator();
1192 ////////////////// imgRequestProxyStatic methods
1194 class StaticBehaviour : public ProxyBehaviour {
1195 public:
1196 explicit StaticBehaviour(mozilla::image::Image* aImage) : mImage(aImage) {}
1198 already_AddRefed<mozilla::image::Image> GetImage() const override {
1199 RefPtr<mozilla::image::Image> image = mImage;
1200 return image.forget();
1203 bool HasImage() const override { return mImage; }
1205 already_AddRefed<ProgressTracker> GetProgressTracker() const override {
1206 return mImage->GetProgressTracker();
1209 imgRequest* GetOwner() const override { return nullptr; }
1211 void SetOwner(imgRequest* aOwner) override {
1212 MOZ_ASSERT(!aOwner,
1213 "We shouldn't be giving static requests a non-null owner.");
1216 private:
1217 // Our image. We have to hold a strong reference here, because that's normally
1218 // the job of the underlying request.
1219 RefPtr<mozilla::image::Image> mImage;
1222 imgRequestProxyStatic::imgRequestProxyStatic(mozilla::image::Image* aImage,
1223 nsIPrincipal* aPrincipal,
1224 bool aHadCrossOriginRedirects)
1225 : mPrincipal(aPrincipal),
1226 mHadCrossOriginRedirects(aHadCrossOriginRedirects) {
1227 mBehaviour = mozilla::MakeUnique<StaticBehaviour>(aImage);
1230 NS_IMETHODIMP
1231 imgRequestProxyStatic::GetImagePrincipal(nsIPrincipal** aPrincipal) {
1232 if (!mPrincipal) {
1233 return NS_ERROR_FAILURE;
1236 NS_ADDREF(*aPrincipal = mPrincipal);
1238 return NS_OK;
1241 NS_IMETHODIMP
1242 imgRequestProxyStatic::GetHadCrossOriginRedirects(
1243 bool* aHadCrossOriginRedirects) {
1244 *aHadCrossOriginRedirects = mHadCrossOriginRedirects;
1245 return NS_OK;
1248 imgRequestProxy* imgRequestProxyStatic::NewClonedProxy() {
1249 nsCOMPtr<nsIPrincipal> currentPrincipal;
1250 GetImagePrincipal(getter_AddRefs(currentPrincipal));
1251 bool hadCrossOriginRedirects = true;
1252 GetHadCrossOriginRedirects(&hadCrossOriginRedirects);
1253 RefPtr<mozilla::image::Image> image = GetImage();
1254 return new imgRequestProxyStatic(image, currentPrincipal,
1255 hadCrossOriginRedirects);