summaryrefslogtreecommitdiffstats
path: root/image/imgRequestProxy.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'image/imgRequestProxy.cpp')
-rw-r--r--image/imgRequestProxy.cpp1268
1 files changed, 1268 insertions, 0 deletions
diff --git a/image/imgRequestProxy.cpp b/image/imgRequestProxy.cpp
new file mode 100644
index 0000000000..45140edd3e
--- /dev/null
+++ b/image/imgRequestProxy.cpp
@@ -0,0 +1,1268 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "imgRequestProxy.h"
+
+#include <utility>
+
+#include "Image.h"
+#include "ImageLogging.h"
+#include "ImageOps.h"
+#include "ImageTypes.h"
+#include "imgINotificationObserver.h"
+#include "imgLoader.h"
+#include "mozilla/dom/Document.h"
+#include "mozilla/Telemetry.h" // for Telemetry
+#include "mozilla/dom/DocGroup.h" // for DocGroup
+#include "nsCRTGlue.h"
+#include "nsError.h"
+
+using namespace mozilla;
+using namespace mozilla::image;
+
+// The split of imgRequestProxy and imgRequestProxyStatic means that
+// certain overridden functions need to be usable in the destructor.
+// Since virtual functions can't be used in that way, this class
+// provides a behavioural trait for each class to use instead.
+class ProxyBehaviour {
+ public:
+ virtual ~ProxyBehaviour() = default;
+
+ virtual already_AddRefed<mozilla::image::Image> GetImage() const = 0;
+ virtual bool HasImage() const = 0;
+ virtual already_AddRefed<ProgressTracker> GetProgressTracker() const = 0;
+ virtual imgRequest* GetOwner() const = 0;
+ virtual void SetOwner(imgRequest* aOwner) = 0;
+};
+
+class RequestBehaviour : public ProxyBehaviour {
+ public:
+ RequestBehaviour() : mOwner(nullptr), mOwnerHasImage(false) {}
+
+ already_AddRefed<mozilla::image::Image> GetImage() const override;
+ bool HasImage() const override;
+ already_AddRefed<ProgressTracker> GetProgressTracker() const override;
+
+ imgRequest* GetOwner() const override { return mOwner; }
+
+ void SetOwner(imgRequest* aOwner) override {
+ mOwner = aOwner;
+
+ if (mOwner) {
+ RefPtr<ProgressTracker> ownerProgressTracker = GetProgressTracker();
+ mOwnerHasImage = ownerProgressTracker && ownerProgressTracker->HasImage();
+ } else {
+ mOwnerHasImage = false;
+ }
+ }
+
+ private:
+ // We maintain the following invariant:
+ // The proxy is registered at most with a single imgRequest as an observer,
+ // and whenever it is, mOwner points to that object. This helps ensure that
+ // imgRequestProxy::~imgRequestProxy unregisters the proxy as an observer
+ // from whatever request it was registered with (if any). This, in turn,
+ // means that imgRequest::mObservers will not have any stale pointers in it.
+ RefPtr<imgRequest> mOwner;
+
+ bool mOwnerHasImage;
+};
+
+already_AddRefed<mozilla::image::Image> RequestBehaviour::GetImage() const {
+ if (!mOwnerHasImage) {
+ return nullptr;
+ }
+ RefPtr<ProgressTracker> progressTracker = GetProgressTracker();
+ return progressTracker->GetImage();
+}
+
+already_AddRefed<ProgressTracker> RequestBehaviour::GetProgressTracker() const {
+ // NOTE: It's possible that our mOwner has an Image that it didn't notify
+ // us about, if we were Canceled before its Image was constructed.
+ // (Canceling removes us as an observer, so mOwner has no way to notify us).
+ // That's why this method uses mOwner->GetProgressTracker() instead of just
+ // mOwner->mProgressTracker -- we might have a null mImage and yet have an
+ // mOwner with a non-null mImage (and a null mProgressTracker pointer).
+ return mOwner->GetProgressTracker();
+}
+
+NS_IMPL_ADDREF(imgRequestProxy)
+NS_IMPL_RELEASE(imgRequestProxy)
+
+NS_INTERFACE_MAP_BEGIN(imgRequestProxy)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, PreloaderBase)
+ NS_INTERFACE_MAP_ENTRY(imgIRequest)
+ NS_INTERFACE_MAP_ENTRY(nsIRequest)
+ NS_INTERFACE_MAP_ENTRY(nsISupportsPriority)
+ NS_INTERFACE_MAP_ENTRY_CONCRETE(imgRequestProxy)
+ NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsITimedChannel, TimedChannel() != nullptr)
+NS_INTERFACE_MAP_END
+
+imgRequestProxy::imgRequestProxy()
+ : mBehaviour(new RequestBehaviour),
+ mURI(nullptr),
+ mListener(nullptr),
+ mLoadFlags(nsIRequest::LOAD_NORMAL),
+ mLockCount(0),
+ mAnimationConsumers(0),
+ mCancelable(true),
+ mCanceled(false),
+ mIsInLoadGroup(false),
+ mForceDispatchLoadGroup(false),
+ mListenerIsStrongRef(false),
+ mDecodeRequested(false),
+ mPendingNotify(false),
+ mValidating(false),
+ mHadListener(false) {
+ /* member initializers and constructor code */
+ LOG_FUNC(gImgLog, "imgRequestProxy::imgRequestProxy");
+}
+
+imgRequestProxy::~imgRequestProxy() {
+ /* destructor code */
+ MOZ_ASSERT(!mListener, "Someone forgot to properly cancel this request!");
+ MOZ_RELEASE_ASSERT(!mLockCount, "Someone forgot to unlock on time?");
+
+ ClearAnimationConsumers();
+
+ // Explicitly set mListener to null to ensure that the RemoveProxy
+ // call below can't send |this| to an arbitrary listener while |this|
+ // is being destroyed. This is all belt-and-suspenders in view of the
+ // above assert.
+ NullOutListener();
+
+ /* Call RemoveProxy with a successful status. This will keep the
+ channel, if still downloading data, from being canceled if 'this' is
+ the last observer. This allows the image to continue to download and
+ be cached even if no one is using it currently.
+ */
+ mCanceled = true;
+ RemoveFromOwner(NS_OK);
+
+ RemoveFromLoadGroup();
+ LOG_FUNC(gImgLog, "imgRequestProxy::~imgRequestProxy");
+}
+
+nsresult imgRequestProxy::Init(imgRequest* aOwner, nsILoadGroup* aLoadGroup,
+ nsIURI* aURI,
+ imgINotificationObserver* aObserver) {
+ MOZ_ASSERT(!GetOwner() && !mListener,
+ "imgRequestProxy is already initialized");
+
+ LOG_SCOPE_WITH_PARAM(gImgLog, "imgRequestProxy::Init", "request", aOwner);
+
+ MOZ_ASSERT(mAnimationConsumers == 0, "Cannot have animation before Init");
+
+ mBehaviour->SetOwner(aOwner);
+ mListener = aObserver;
+ // Make sure to addref mListener before the AddToOwner call below, since
+ // that call might well want to release it if the imgRequest has
+ // already seen OnStopRequest.
+ if (mListener) {
+ mHadListener = true;
+ mListenerIsStrongRef = true;
+ NS_ADDREF(mListener);
+ }
+ mLoadGroup = aLoadGroup;
+ mURI = aURI;
+
+ // Note: AddToOwner won't send all the On* notifications immediately
+ AddToOwner();
+
+ return NS_OK;
+}
+
+nsresult imgRequestProxy::ChangeOwner(imgRequest* aNewOwner) {
+ MOZ_ASSERT(GetOwner(), "Cannot ChangeOwner on a proxy without an owner!");
+
+ if (mCanceled) {
+ // Ensure that this proxy has received all notifications to date
+ // before we clean it up when removing it from the old owner below.
+ SyncNotifyListener();
+ }
+
+ // If we're holding locks, unlock the old image.
+ // Note that UnlockImage decrements mLockCount each time it's called.
+ uint32_t oldLockCount = mLockCount;
+ while (mLockCount) {
+ UnlockImage();
+ }
+
+ // If we're holding animation requests, undo them.
+ uint32_t oldAnimationConsumers = mAnimationConsumers;
+ ClearAnimationConsumers();
+
+ GetOwner()->RemoveProxy(this, NS_OK);
+
+ mBehaviour->SetOwner(aNewOwner);
+ MOZ_ASSERT(!GetValidator(), "New owner cannot be validating!");
+
+ // If we were locked, apply the locks here
+ for (uint32_t i = 0; i < oldLockCount; i++) {
+ LockImage();
+ }
+
+ // If we had animation requests, restore them here. Note that we
+ // do this *after* RemoveProxy, which clears out animation consumers
+ // (see bug 601723).
+ for (uint32_t i = 0; i < oldAnimationConsumers; i++) {
+ IncrementAnimationConsumers();
+ }
+
+ AddToOwner();
+ return NS_OK;
+}
+
+NS_IMETHODIMP imgRequestProxy::GetTriggeringPrincipal(
+ nsIPrincipal** aTriggeringPrincipal) {
+ MOZ_ASSERT(GetOwner());
+ nsCOMPtr<nsIPrincipal> triggeringPrincipal =
+ GetOwner()->GetTriggeringPrincipal();
+ triggeringPrincipal.forget(aTriggeringPrincipal);
+ return NS_OK;
+}
+
+void imgRequestProxy::MarkValidating() {
+ MOZ_ASSERT(GetValidator());
+ mValidating = true;
+}
+
+void imgRequestProxy::ClearValidating() {
+ MOZ_ASSERT(mValidating);
+ MOZ_ASSERT(!GetValidator());
+ mValidating = false;
+
+ // If we'd previously requested a synchronous decode, request a decode on the
+ // new image.
+ if (mDecodeRequested) {
+ mDecodeRequested = false;
+ StartDecoding(imgIContainer::FLAG_NONE);
+ }
+}
+
+bool imgRequestProxy::HasDecodedPixels() {
+ if (IsValidating()) {
+ return false;
+ }
+
+ RefPtr<Image> image = GetImage();
+ if (image) {
+ return image->HasDecodedPixels();
+ }
+
+ return false;
+}
+
+nsresult imgRequestProxy::DispatchWithTargetIfAvailable(
+ already_AddRefed<nsIRunnable> aEvent) {
+ LOG_FUNC(gImgLog, "imgRequestProxy::DispatchWithTargetIfAvailable");
+ return NS_DispatchToMainThread(
+ CreateRenderBlockingRunnable(std::move(aEvent)));
+}
+
+void imgRequestProxy::AddToOwner() {
+ imgRequest* owner = GetOwner();
+ if (!owner) {
+ return;
+ }
+
+ owner->AddProxy(this);
+}
+
+void imgRequestProxy::RemoveFromOwner(nsresult aStatus) {
+ imgRequest* owner = GetOwner();
+ if (owner) {
+ if (mValidating) {
+ imgCacheValidator* validator = owner->GetValidator();
+ MOZ_ASSERT(validator);
+ validator->RemoveProxy(this);
+ mValidating = false;
+ }
+
+ owner->RemoveProxy(this, aStatus);
+ }
+}
+
+void imgRequestProxy::AddToLoadGroup() {
+ NS_ASSERTION(!mIsInLoadGroup, "Whaa, we're already in the loadgroup!");
+ MOZ_ASSERT(!mForceDispatchLoadGroup);
+
+ /* While in theory there could be a dispatch outstanding to remove this
+ request from the load group, in practice we only add to the load group
+ (when previously not in a load group) at initialization. */
+ if (!mIsInLoadGroup && mLoadGroup) {
+ LOG_FUNC(gImgLog, "imgRequestProxy::AddToLoadGroup");
+ mLoadGroup->AddRequest(this, nullptr);
+ mIsInLoadGroup = true;
+ }
+}
+
+void imgRequestProxy::RemoveFromLoadGroup() {
+ if (!mIsInLoadGroup || !mLoadGroup) {
+ return;
+ }
+
+ /* Sometimes we may not be able to remove ourselves from the load group in
+ the current context. This is because our listeners are not re-entrant (e.g.
+ we are in the middle of CancelAndForgetObserver or SyncClone). */
+ if (mForceDispatchLoadGroup) {
+ LOG_FUNC(gImgLog, "imgRequestProxy::RemoveFromLoadGroup -- dispatch");
+
+ /* We take away the load group from the request temporarily; this prevents
+ additional dispatches via RemoveFromLoadGroup occurring, as well as
+ MoveToBackgroundInLoadGroup from removing and readding. This is safe
+ because we know that once we get here, blocking the load group at all is
+ unnecessary. */
+ mIsInLoadGroup = false;
+ nsCOMPtr<nsILoadGroup> loadGroup = std::move(mLoadGroup);
+ RefPtr<imgRequestProxy> self(this);
+ DispatchWithTargetIfAvailable(NS_NewRunnableFunction(
+ "imgRequestProxy::RemoveFromLoadGroup", [self, loadGroup]() -> void {
+ loadGroup->RemoveRequest(self, nullptr, NS_OK);
+ }));
+ return;
+ }
+
+ LOG_FUNC(gImgLog, "imgRequestProxy::RemoveFromLoadGroup");
+
+ /* calling RemoveFromLoadGroup may cause the document to finish
+ loading, which could result in our death. We need to make sure
+ that we stay alive long enough to fight another battle... at
+ least until we exit this function. */
+ nsCOMPtr<imgIRequest> kungFuDeathGrip(this);
+ mLoadGroup->RemoveRequest(this, nullptr, NS_OK);
+ mLoadGroup = nullptr;
+ mIsInLoadGroup = false;
+}
+
+void imgRequestProxy::MoveToBackgroundInLoadGroup() {
+ /* Even if we are still in the load group, we may have taken away the load
+ group reference itself because we are in the process of leaving the group.
+ In that case, there is no need to background the request. */
+ if (!mLoadGroup) {
+ return;
+ }
+
+ /* There is no need to dispatch if we only need to add ourselves to the load
+ group without removal. It is the removal which causes the problematic
+ callbacks (see RemoveFromLoadGroup). */
+ if (mIsInLoadGroup && mForceDispatchLoadGroup) {
+ LOG_FUNC(gImgLog,
+ "imgRequestProxy::MoveToBackgroundInLoadGroup -- dispatch");
+
+ RefPtr<imgRequestProxy> self(this);
+ DispatchWithTargetIfAvailable(NS_NewRunnableFunction(
+ "imgRequestProxy::MoveToBackgroundInLoadGroup",
+ [self]() -> void { self->MoveToBackgroundInLoadGroup(); }));
+ return;
+ }
+
+ LOG_FUNC(gImgLog, "imgRequestProxy::MoveToBackgroundInLoadGroup");
+ nsCOMPtr<imgIRequest> kungFuDeathGrip(this);
+ if (mIsInLoadGroup) {
+ mLoadGroup->RemoveRequest(this, nullptr, NS_OK);
+ }
+
+ mLoadFlags |= nsIRequest::LOAD_BACKGROUND;
+ mLoadGroup->AddRequest(this, nullptr);
+}
+
+/** nsIRequest / imgIRequest methods **/
+
+NS_IMETHODIMP
+imgRequestProxy::GetName(nsACString& aName) {
+ aName.Truncate();
+
+ if (mURI) {
+ mURI->GetSpec(aName);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+imgRequestProxy::IsPending(bool* _retval) { return NS_ERROR_NOT_IMPLEMENTED; }
+
+NS_IMETHODIMP
+imgRequestProxy::GetStatus(nsresult* aStatus) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP imgRequestProxy::SetCanceledReason(const nsACString& aReason) {
+ return SetCanceledReasonImpl(aReason);
+}
+
+NS_IMETHODIMP imgRequestProxy::GetCanceledReason(nsACString& aReason) {
+ return GetCanceledReasonImpl(aReason);
+}
+
+NS_IMETHODIMP imgRequestProxy::CancelWithReason(nsresult aStatus,
+ const nsACString& aReason) {
+ return CancelWithReasonImpl(aStatus, aReason);
+}
+
+void imgRequestProxy::SetCancelable(bool aCancelable) {
+ MOZ_ASSERT(NS_IsMainThread());
+ mCancelable = aCancelable;
+}
+
+NS_IMETHODIMP
+imgRequestProxy::Cancel(nsresult status) {
+ if (mCanceled) {
+ return NS_ERROR_FAILURE;
+ }
+
+ if (NS_WARN_IF(!mCancelable)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ LOG_SCOPE(gImgLog, "imgRequestProxy::Cancel");
+
+ mCanceled = true;
+
+ nsCOMPtr<nsIRunnable> ev = new imgCancelRunnable(this, status);
+ return DispatchWithTargetIfAvailable(ev.forget());
+}
+
+void imgRequestProxy::DoCancel(nsresult status) {
+ RemoveFromOwner(status);
+ RemoveFromLoadGroup();
+ NullOutListener();
+}
+
+NS_IMETHODIMP
+imgRequestProxy::CancelAndForgetObserver(nsresult aStatus) {
+ // If mCanceled is true but mListener is non-null, that means
+ // someone called Cancel() on us but the imgCancelRunnable is still
+ // pending. We still need to null out mListener before returning
+ // from this function in this case. That means we want to do the
+ // RemoveProxy call right now, because we need to deliver the
+ // onStopRequest.
+ if (mCanceled && !mListener) {
+ return NS_ERROR_FAILURE;
+ }
+
+ if (NS_WARN_IF(!mCancelable)) {
+ MOZ_ASSERT(mCancelable,
+ "Shouldn't try to cancel non-cancelable requests via "
+ "CancelAndForgetObserver");
+ return NS_ERROR_FAILURE;
+ }
+
+ LOG_SCOPE(gImgLog, "imgRequestProxy::CancelAndForgetObserver");
+
+ mCanceled = true;
+ mForceDispatchLoadGroup = true;
+ RemoveFromOwner(aStatus);
+ RemoveFromLoadGroup();
+ mForceDispatchLoadGroup = false;
+
+ NullOutListener();
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+imgRequestProxy::StartDecoding(uint32_t aFlags) {
+ // Flag this, so we know to request after validation if pending.
+ if (IsValidating()) {
+ mDecodeRequested = true;
+ return NS_OK;
+ }
+
+ RefPtr<Image> image = GetImage();
+ if (image) {
+ return image->StartDecoding(aFlags);
+ }
+
+ if (GetOwner()) {
+ GetOwner()->StartDecoding();
+ }
+
+ return NS_OK;
+}
+
+bool imgRequestProxy::StartDecodingWithResult(uint32_t aFlags) {
+ // Flag this, so we know to request after validation if pending.
+ if (IsValidating()) {
+ mDecodeRequested = true;
+ return false;
+ }
+
+ RefPtr<Image> image = GetImage();
+ if (image) {
+ return image->StartDecodingWithResult(aFlags);
+ }
+
+ if (GetOwner()) {
+ GetOwner()->StartDecoding();
+ }
+
+ return false;
+}
+
+imgIContainer::DecodeResult imgRequestProxy::RequestDecodeWithResult(
+ uint32_t aFlags) {
+ if (IsValidating()) {
+ mDecodeRequested = true;
+ return imgIContainer::DECODE_REQUESTED;
+ }
+
+ RefPtr<Image> image = GetImage();
+ if (image) {
+ return image->RequestDecodeWithResult(aFlags);
+ }
+
+ if (GetOwner()) {
+ GetOwner()->StartDecoding();
+ }
+
+ return imgIContainer::DECODE_REQUESTED;
+}
+
+NS_IMETHODIMP
+imgRequestProxy::LockImage() {
+ mLockCount++;
+ RefPtr<Image> image =
+ GetOwner() && GetOwner()->ImageAvailable() ? GetImage() : nullptr;
+ if (image) {
+ return image->LockImage();
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+imgRequestProxy::UnlockImage() {
+ MOZ_ASSERT(mLockCount > 0, "calling unlock but no locks!");
+
+ mLockCount--;
+ RefPtr<Image> image =
+ GetOwner() && GetOwner()->ImageAvailable() ? GetImage() : nullptr;
+ if (image) {
+ return image->UnlockImage();
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+imgRequestProxy::RequestDiscard() {
+ RefPtr<Image> image = GetImage();
+ if (image) {
+ return image->RequestDiscard();
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+imgRequestProxy::IncrementAnimationConsumers() {
+ mAnimationConsumers++;
+ RefPtr<Image> image =
+ GetOwner() && GetOwner()->ImageAvailable() ? GetImage() : nullptr;
+ if (image) {
+ image->IncrementAnimationConsumers();
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+imgRequestProxy::DecrementAnimationConsumers() {
+ // We may get here if some responsible code called Increment,
+ // then called us, but we have meanwhile called ClearAnimationConsumers
+ // because we needed to get rid of them earlier (see
+ // imgRequest::RemoveProxy), and hence have nothing left to
+ // decrement. (In such a case we got rid of the animation consumers
+ // early, but not the observer.)
+ if (mAnimationConsumers > 0) {
+ mAnimationConsumers--;
+ RefPtr<Image> image =
+ GetOwner() && GetOwner()->ImageAvailable() ? GetImage() : nullptr;
+ if (image) {
+ image->DecrementAnimationConsumers();
+ }
+ }
+ return NS_OK;
+}
+
+void imgRequestProxy::ClearAnimationConsumers() {
+ while (mAnimationConsumers > 0) {
+ DecrementAnimationConsumers();
+ }
+}
+
+NS_IMETHODIMP
+imgRequestProxy::Suspend() { return NS_ERROR_NOT_IMPLEMENTED; }
+
+NS_IMETHODIMP
+imgRequestProxy::Resume() { return NS_ERROR_NOT_IMPLEMENTED; }
+
+NS_IMETHODIMP
+imgRequestProxy::GetLoadGroup(nsILoadGroup** loadGroup) {
+ NS_IF_ADDREF(*loadGroup = mLoadGroup.get());
+ return NS_OK;
+}
+NS_IMETHODIMP
+imgRequestProxy::SetLoadGroup(nsILoadGroup* loadGroup) {
+ if (loadGroup != mLoadGroup) {
+ MOZ_ASSERT_UNREACHABLE("Switching load groups is unsupported!");
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+imgRequestProxy::GetLoadFlags(nsLoadFlags* flags) {
+ *flags = mLoadFlags;
+ return NS_OK;
+}
+NS_IMETHODIMP
+imgRequestProxy::SetLoadFlags(nsLoadFlags flags) {
+ mLoadFlags = flags;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+imgRequestProxy::GetTRRMode(nsIRequest::TRRMode* aTRRMode) {
+ return GetTRRModeImpl(aTRRMode);
+}
+
+NS_IMETHODIMP
+imgRequestProxy::SetTRRMode(nsIRequest::TRRMode aTRRMode) {
+ return SetTRRModeImpl(aTRRMode);
+}
+
+/** imgIRequest methods **/
+
+NS_IMETHODIMP
+imgRequestProxy::GetImage(imgIContainer** aImage) {
+ NS_ENSURE_TRUE(aImage, NS_ERROR_NULL_POINTER);
+ // It's possible that our owner has an image but hasn't notified us of it -
+ // that'll happen if we get Canceled before the owner instantiates its image
+ // (because Canceling unregisters us as a listener on mOwner). If we're
+ // in that situation, just grab the image off of mOwner.
+ RefPtr<Image> image = GetImage();
+ nsCOMPtr<imgIContainer> imageToReturn;
+ if (image) {
+ imageToReturn = image;
+ }
+ if (!imageToReturn && GetOwner()) {
+ imageToReturn = GetOwner()->GetImage();
+ }
+ if (!imageToReturn) {
+ return NS_ERROR_FAILURE;
+ }
+
+ imageToReturn.swap(*aImage);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+imgRequestProxy::GetProviderId(uint32_t* aId) {
+ NS_ENSURE_TRUE(aId, NS_ERROR_NULL_POINTER);
+
+ nsCOMPtr<imgIContainer> image;
+ nsresult rv = GetImage(getter_AddRefs(image));
+ if (NS_SUCCEEDED(rv)) {
+ *aId = image->GetProviderId();
+ } else {
+ *aId = 0;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+imgRequestProxy::GetImageStatus(uint32_t* aStatus) {
+ if (IsValidating()) {
+ // We are currently validating the image, and so our status could revert if
+ // we discard the cache. We should also be deferring notifications, such
+ // that the caller will be notified when validation completes. Rather than
+ // risk misleading the caller, return nothing.
+ *aStatus = imgIRequest::STATUS_NONE;
+ } else {
+ RefPtr<ProgressTracker> progressTracker = GetProgressTracker();
+ *aStatus = progressTracker->GetImageStatus();
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+imgRequestProxy::GetImageErrorCode(nsresult* aStatus) {
+ if (!GetOwner()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ *aStatus = GetOwner()->GetImageErrorCode();
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+imgRequestProxy::GetURI(nsIURI** aURI) {
+ MOZ_ASSERT(NS_IsMainThread(), "Must be on main thread to convert URI");
+ nsCOMPtr<nsIURI> uri = mURI;
+ uri.forget(aURI);
+ return NS_OK;
+}
+
+nsresult imgRequestProxy::GetFinalURI(nsIURI** aURI) {
+ if (!GetOwner()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return GetOwner()->GetFinalURI(aURI);
+}
+
+NS_IMETHODIMP
+imgRequestProxy::GetNotificationObserver(imgINotificationObserver** aObserver) {
+ *aObserver = mListener;
+ NS_IF_ADDREF(*aObserver);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+imgRequestProxy::GetMimeType(char** aMimeType) {
+ if (!GetOwner()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ const char* type = GetOwner()->GetMimeType();
+ if (!type) {
+ return NS_ERROR_FAILURE;
+ }
+
+ *aMimeType = NS_xstrdup(type);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+imgRequestProxy::GetFileName(nsACString& aFileName) {
+ if (!GetOwner()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ GetOwner()->GetFileName(aFileName);
+ return NS_OK;
+}
+
+imgRequestProxy* imgRequestProxy::NewClonedProxy() {
+ return new imgRequestProxy();
+}
+
+NS_IMETHODIMP
+imgRequestProxy::Clone(imgINotificationObserver* aObserver,
+ imgIRequest** aClone) {
+ nsresult result;
+ imgRequestProxy* proxy;
+ result = PerformClone(aObserver, nullptr, /* aSyncNotify */ true, &proxy);
+ *aClone = proxy;
+ return result;
+}
+
+nsresult imgRequestProxy::SyncClone(imgINotificationObserver* aObserver,
+ Document* aLoadingDocument,
+ imgRequestProxy** aClone) {
+ return PerformClone(aObserver, aLoadingDocument,
+ /* aSyncNotify */ true, aClone);
+}
+
+nsresult imgRequestProxy::Clone(imgINotificationObserver* aObserver,
+ Document* aLoadingDocument,
+ imgRequestProxy** aClone) {
+ return PerformClone(aObserver, aLoadingDocument,
+ /* aSyncNotify */ false, aClone);
+}
+
+nsresult imgRequestProxy::PerformClone(imgINotificationObserver* aObserver,
+ Document* aLoadingDocument,
+ bool aSyncNotify,
+ imgRequestProxy** aClone) {
+ MOZ_ASSERT(aClone, "Null out param");
+
+ LOG_SCOPE(gImgLog, "imgRequestProxy::Clone");
+
+ *aClone = nullptr;
+ RefPtr<imgRequestProxy> clone = NewClonedProxy();
+
+ nsCOMPtr<nsILoadGroup> loadGroup;
+ if (aLoadingDocument) {
+ loadGroup = aLoadingDocument->GetDocumentLoadGroup();
+ }
+
+ // It is important to call |SetLoadFlags()| before calling |Init()| because
+ // |Init()| adds the request to the loadgroup.
+ // When a request is added to a loadgroup, its load flags are merged
+ // with the load flags of the loadgroup.
+ // XXXldb That's not true anymore. Stuff from imgLoader adds the
+ // request to the loadgroup.
+ clone->SetLoadFlags(mLoadFlags);
+ nsresult rv = clone->Init(mBehaviour->GetOwner(), loadGroup, mURI, aObserver);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ // Assign to *aClone before calling Notify so that if the caller expects to
+ // only be notified for requests it's already holding pointers to it won't be
+ // surprised.
+ NS_ADDREF(*aClone = clone);
+
+ imgCacheValidator* validator = GetValidator();
+ if (validator) {
+ // Note that if we have a validator, we don't want to issue notifications at
+ // here because we want to defer until that completes. AddProxy will add us
+ // to the load group; we cannot avoid that in this case, because we don't
+ // know when the validation will complete, and if it will cause us to
+ // discard our cached state anyways. We are probably already blocked by the
+ // original LoadImage(WithChannel) request in any event.
+ clone->MarkValidating();
+ validator->AddProxy(clone);
+ } else {
+ // We only want to add the request to the load group of the owning document
+ // if it is still in progress. Some callers cannot handle a supurious load
+ // group removal (e.g. print preview) so we must be careful. On the other
+ // hand, if after cloning, the original request proxy is cancelled /
+ // destroyed, we need to ensure that any clones still block the load group
+ // if it is incomplete.
+ bool addToLoadGroup = mIsInLoadGroup;
+ if (!addToLoadGroup) {
+ RefPtr<ProgressTracker> tracker = clone->GetProgressTracker();
+ addToLoadGroup =
+ tracker && !(tracker->GetProgress() & FLAG_LOAD_COMPLETE);
+ }
+
+ if (addToLoadGroup) {
+ clone->AddToLoadGroup();
+ }
+
+ if (aSyncNotify) {
+ // This is wrong!!! We need to notify asynchronously, but there's code
+ // that assumes that we don't. This will be fixed in bug 580466. Note that
+ // if we have a validator, we won't issue notifications anyways because
+ // they are deferred, so there is no point in requesting.
+ clone->mForceDispatchLoadGroup = true;
+ clone->SyncNotifyListener();
+ clone->mForceDispatchLoadGroup = false;
+ } else {
+ // Without a validator, we can request asynchronous notifications
+ // immediately. If there was a validator, this would override the deferral
+ // and that would be incorrect.
+ clone->NotifyListener();
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+imgRequestProxy::GetImagePrincipal(nsIPrincipal** aPrincipal) {
+ if (!GetOwner()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsCOMPtr<nsIPrincipal> principal = GetOwner()->GetPrincipal();
+ principal.forget(aPrincipal);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+imgRequestProxy::GetHadCrossOriginRedirects(bool* aHadCrossOriginRedirects) {
+ *aHadCrossOriginRedirects = false;
+
+ nsCOMPtr<nsITimedChannel> timedChannel = TimedChannel();
+ if (timedChannel) {
+ bool allRedirectsSameOrigin = false;
+ *aHadCrossOriginRedirects =
+ NS_SUCCEEDED(
+ timedChannel->GetAllRedirectsSameOrigin(&allRedirectsSameOrigin)) &&
+ !allRedirectsSameOrigin;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+imgRequestProxy::GetMultipart(bool* aMultipart) {
+ if (!GetOwner()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ *aMultipart = GetOwner()->GetMultipart();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+imgRequestProxy::GetCORSMode(int32_t* aCorsMode) {
+ if (!GetOwner()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ *aCorsMode = GetOwner()->GetCORSMode();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+imgRequestProxy::GetReferrerInfo(nsIReferrerInfo** aReferrerInfo) {
+ if (!GetOwner()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsCOMPtr<nsIReferrerInfo> referrerInfo = GetOwner()->GetReferrerInfo();
+ referrerInfo.forget(aReferrerInfo);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+imgRequestProxy::BoostPriority(uint32_t aCategory) {
+ NS_ENSURE_STATE(GetOwner() && !mCanceled);
+ GetOwner()->BoostPriority(aCategory);
+ return NS_OK;
+}
+
+/** nsISupportsPriority methods **/
+
+NS_IMETHODIMP
+imgRequestProxy::GetPriority(int32_t* priority) {
+ NS_ENSURE_STATE(GetOwner());
+ *priority = GetOwner()->Priority();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+imgRequestProxy::SetPriority(int32_t priority) {
+ NS_ENSURE_STATE(GetOwner() && !mCanceled);
+ GetOwner()->AdjustPriority(this, priority - GetOwner()->Priority());
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+imgRequestProxy::AdjustPriority(int32_t priority) {
+ // We don't require |!mCanceled| here. This may be called even if we're
+ // cancelled, because it's invoked as part of the process of removing an image
+ // from the load group.
+ NS_ENSURE_STATE(GetOwner());
+ GetOwner()->AdjustPriority(this, priority);
+ return NS_OK;
+}
+
+static const char* NotificationTypeToString(int32_t aType) {
+ switch (aType) {
+ case imgINotificationObserver::SIZE_AVAILABLE:
+ return "SIZE_AVAILABLE";
+ case imgINotificationObserver::FRAME_UPDATE:
+ return "FRAME_UPDATE";
+ case imgINotificationObserver::FRAME_COMPLETE:
+ return "FRAME_COMPLETE";
+ case imgINotificationObserver::LOAD_COMPLETE:
+ return "LOAD_COMPLETE";
+ case imgINotificationObserver::DECODE_COMPLETE:
+ return "DECODE_COMPLETE";
+ case imgINotificationObserver::DISCARD:
+ return "DISCARD";
+ case imgINotificationObserver::UNLOCKED_DRAW:
+ return "UNLOCKED_DRAW";
+ case imgINotificationObserver::IS_ANIMATED:
+ return "IS_ANIMATED";
+ case imgINotificationObserver::HAS_TRANSPARENCY:
+ return "HAS_TRANSPARENCY";
+ default:
+ MOZ_ASSERT_UNREACHABLE("Notification list should be exhaustive");
+ return "(unknown notification)";
+ }
+}
+
+void imgRequestProxy::Notify(int32_t aType,
+ const mozilla::gfx::IntRect* aRect) {
+ MOZ_ASSERT(aType != imgINotificationObserver::LOAD_COMPLETE,
+ "Should call OnLoadComplete");
+
+ LOG_FUNC_WITH_PARAM(gImgLog, "imgRequestProxy::Notify", "type",
+ NotificationTypeToString(aType));
+
+ if (!mListener || mCanceled) {
+ return;
+ }
+
+ // Make sure the listener stays alive while we notify.
+ nsCOMPtr<imgINotificationObserver> listener(mListener);
+
+ listener->Notify(this, aType, aRect);
+}
+
+void imgRequestProxy::OnLoadComplete(bool aLastPart) {
+ LOG_FUNC_WITH_PARAM(gImgLog, "imgRequestProxy::OnLoadComplete", "uri", mURI);
+
+ // There's all sorts of stuff here that could kill us (the OnStopRequest call
+ // on the listener, the removal from the loadgroup, the release of the
+ // listener, etc). Don't let them do it.
+ RefPtr<imgRequestProxy> self(this);
+
+ if (mListener && !mCanceled) {
+ // Hold a ref to the listener while we call it, just in case.
+ nsCOMPtr<imgINotificationObserver> listener(mListener);
+ listener->Notify(this, imgINotificationObserver::LOAD_COMPLETE, nullptr);
+ }
+
+ // If we're expecting more data from a multipart channel, re-add ourself
+ // to the loadgroup so that the document doesn't lose track of the load.
+ // If the request is already a background request and there's more data
+ // coming, we can just leave the request in the loadgroup as-is.
+ if (aLastPart || (mLoadFlags & nsIRequest::LOAD_BACKGROUND) == 0) {
+ if (aLastPart) {
+ RemoveFromLoadGroup();
+
+ nsresult errorCode = NS_OK;
+ // if the load is cross origin without CORS, or the CORS access is
+ // rejected, always fire load event to avoid leaking site information for
+ // <link rel=preload>.
+ // XXXedgar, currently we don't do the same thing for <img>.
+ imgRequest* request = GetOwner();
+ if (!request || !(request->IsDeniedCrossSiteCORSRequest() ||
+ request->IsCrossSiteNoCORSRequest())) {
+ uint32_t status = imgIRequest::STATUS_NONE;
+ GetImageStatus(&status);
+ if (status & imgIRequest::STATUS_ERROR) {
+ errorCode = NS_ERROR_FAILURE;
+ }
+ }
+ NotifyStop(errorCode);
+ } else {
+ // More data is coming, so change the request to be a background request
+ // and put it back in the loadgroup.
+ MoveToBackgroundInLoadGroup();
+ }
+ }
+
+ if (mListenerIsStrongRef && aLastPart) {
+ MOZ_ASSERT(mListener, "How did that happen?");
+ // Drop our strong ref to the listener now that we're done with
+ // everything. Note that this can cancel us and other fun things
+ // like that. Don't add anything in this method after this point.
+ imgINotificationObserver* obs = mListener;
+ mListenerIsStrongRef = false;
+ NS_RELEASE(obs);
+ }
+}
+
+void imgRequestProxy::NullOutListener() {
+ // If we have animation consumers, then they don't matter anymore
+ if (mListener) {
+ ClearAnimationConsumers();
+ }
+
+ if (mListenerIsStrongRef) {
+ // Releasing could do weird reentery stuff, so just play it super-safe
+ nsCOMPtr<imgINotificationObserver> obs;
+ obs.swap(mListener);
+ mListenerIsStrongRef = false;
+ } else {
+ mListener = nullptr;
+ }
+}
+
+NS_IMETHODIMP
+imgRequestProxy::GetStaticRequest(imgIRequest** aReturn) {
+ RefPtr<imgRequestProxy> proxy =
+ GetStaticRequest(static_cast<Document*>(nullptr));
+ if (proxy != this) {
+ RefPtr<Image> image = GetImage();
+ if (image && image->HasError()) {
+ // image/test/unit/test_async_notification_404.js needs this, but ideally
+ // this special case can be removed from the scripted codepath.
+ return NS_ERROR_FAILURE;
+ }
+ }
+ proxy.forget(aReturn);
+ return NS_OK;
+}
+
+already_AddRefed<imgRequestProxy> imgRequestProxy::GetStaticRequest(
+ Document* aLoadingDocument) {
+ MOZ_DIAGNOSTIC_ASSERT(!aLoadingDocument ||
+ aLoadingDocument->IsStaticDocument());
+ RefPtr<Image> image = GetImage();
+
+ bool animated;
+ if (!image || (NS_SUCCEEDED(image->GetAnimated(&animated)) && !animated)) {
+ // Early exit - we're not animated, so we don't have to do anything.
+ return do_AddRef(this);
+ }
+
+ // We are animated. We need to create a frozen version of this image.
+ RefPtr<Image> frozenImage = ImageOps::Freeze(image);
+
+ // Create a static imgRequestProxy with our new extracted frame.
+ nsCOMPtr<nsIPrincipal> currentPrincipal;
+ GetImagePrincipal(getter_AddRefs(currentPrincipal));
+ bool hadCrossOriginRedirects = true;
+ GetHadCrossOriginRedirects(&hadCrossOriginRedirects);
+ nsCOMPtr<nsIPrincipal> triggeringPrincipal = GetTriggeringPrincipal();
+ RefPtr<imgRequestProxy> req =
+ new imgRequestProxyStatic(frozenImage, currentPrincipal,
+ triggeringPrincipal, hadCrossOriginRedirects);
+ req->Init(nullptr, nullptr, mURI, nullptr);
+
+ return req.forget();
+}
+
+void imgRequestProxy::NotifyListener() {
+ // It would be nice to notify the observer directly in the status tracker
+ // instead of through the proxy, but there are several places we do extra
+ // processing when we receive notifications (like OnStopRequest()), and we
+ // need to check mCanceled everywhere too.
+
+ RefPtr<ProgressTracker> progressTracker = GetProgressTracker();
+ if (GetOwner()) {
+ // Send the notifications to our listener asynchronously.
+ progressTracker->Notify(this);
+ } else {
+ // We don't have an imgRequest, so we can only notify the clone of our
+ // current state, but we still have to do that asynchronously.
+ MOZ_ASSERT(HasImage(), "if we have no imgRequest, we should have an Image");
+ progressTracker->NotifyCurrentState(this);
+ }
+}
+
+void imgRequestProxy::SyncNotifyListener() {
+ // It would be nice to notify the observer directly in the status tracker
+ // instead of through the proxy, but there are several places we do extra
+ // processing when we receive notifications (like OnStopRequest()), and we
+ // need to check mCanceled everywhere too.
+
+ RefPtr<ProgressTracker> progressTracker = GetProgressTracker();
+ progressTracker->SyncNotify(this);
+}
+
+void imgRequestProxy::SetHasImage() {
+ RefPtr<ProgressTracker> progressTracker = GetProgressTracker();
+ MOZ_ASSERT(progressTracker);
+ RefPtr<Image> image = progressTracker->GetImage();
+ MOZ_ASSERT(image);
+
+ // Force any private status related to the owner to reflect
+ // the presence of an image;
+ mBehaviour->SetOwner(mBehaviour->GetOwner());
+
+ // Apply any locks we have
+ for (uint32_t i = 0; i < mLockCount; ++i) {
+ image->LockImage();
+ }
+
+ // Apply any animation consumers we have
+ for (uint32_t i = 0; i < mAnimationConsumers; i++) {
+ image->IncrementAnimationConsumers();
+ }
+}
+
+already_AddRefed<ProgressTracker> imgRequestProxy::GetProgressTracker() const {
+ return mBehaviour->GetProgressTracker();
+}
+
+already_AddRefed<mozilla::image::Image> imgRequestProxy::GetImage() const {
+ return mBehaviour->GetImage();
+}
+
+bool RequestBehaviour::HasImage() const {
+ if (!mOwnerHasImage) {
+ return false;
+ }
+ RefPtr<ProgressTracker> progressTracker = GetProgressTracker();
+ return progressTracker ? progressTracker->HasImage() : false;
+}
+
+bool imgRequestProxy::HasImage() const { return mBehaviour->HasImage(); }
+
+imgRequest* imgRequestProxy::GetOwner() const { return mBehaviour->GetOwner(); }
+
+imgCacheValidator* imgRequestProxy::GetValidator() const {
+ imgRequest* owner = GetOwner();
+ if (!owner) {
+ return nullptr;
+ }
+ return owner->GetValidator();
+}
+
+nsITimedChannel* imgRequestProxy::TimedChannel() {
+ if (!GetOwner()) {
+ return nullptr;
+ }
+ return GetOwner()->GetTimedChannel();
+}
+
+////////////////// imgRequestProxyStatic methods
+
+class StaticBehaviour : public ProxyBehaviour {
+ public:
+ explicit StaticBehaviour(mozilla::image::Image* aImage) : mImage(aImage) {}
+
+ already_AddRefed<mozilla::image::Image> GetImage() const override {
+ RefPtr<mozilla::image::Image> image = mImage;
+ return image.forget();
+ }
+
+ bool HasImage() const override { return mImage; }
+
+ already_AddRefed<ProgressTracker> GetProgressTracker() const override {
+ return mImage->GetProgressTracker();
+ }
+
+ imgRequest* GetOwner() const override { return nullptr; }
+
+ void SetOwner(imgRequest* aOwner) override {
+ MOZ_ASSERT(!aOwner,
+ "We shouldn't be giving static requests a non-null owner.");
+ }
+
+ private:
+ // Our image. We have to hold a strong reference here, because that's normally
+ // the job of the underlying request.
+ RefPtr<mozilla::image::Image> mImage;
+};
+
+imgRequestProxyStatic::imgRequestProxyStatic(mozilla::image::Image* aImage,
+ nsIPrincipal* aImagePrincipal,
+ nsIPrincipal* aTriggeringPrincipal,
+ bool aHadCrossOriginRedirects)
+ : mImagePrincipal(aImagePrincipal),
+ mTriggeringPrincipal(aTriggeringPrincipal),
+ mHadCrossOriginRedirects(aHadCrossOriginRedirects) {
+ mBehaviour = mozilla::MakeUnique<StaticBehaviour>(aImage);
+}
+
+NS_IMETHODIMP
+imgRequestProxyStatic::GetImagePrincipal(nsIPrincipal** aPrincipal) {
+ if (!mImagePrincipal) {
+ return NS_ERROR_FAILURE;
+ }
+ NS_ADDREF(*aPrincipal = mImagePrincipal);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+imgRequestProxyStatic::GetTriggeringPrincipal(nsIPrincipal** aPrincipal) {
+ NS_IF_ADDREF(*aPrincipal = mTriggeringPrincipal);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+imgRequestProxyStatic::GetHadCrossOriginRedirects(
+ bool* aHadCrossOriginRedirects) {
+ *aHadCrossOriginRedirects = mHadCrossOriginRedirects;
+ return NS_OK;
+}
+
+imgRequestProxy* imgRequestProxyStatic::NewClonedProxy() {
+ nsCOMPtr<nsIPrincipal> currentPrincipal;
+ GetImagePrincipal(getter_AddRefs(currentPrincipal));
+ nsCOMPtr<nsIPrincipal> triggeringPrincipal;
+ GetTriggeringPrincipal(getter_AddRefs(triggeringPrincipal));
+ bool hadCrossOriginRedirects = true;
+ GetHadCrossOriginRedirects(&hadCrossOriginRedirects);
+ RefPtr<mozilla::image::Image> image = GetImage();
+ return new imgRequestProxyStatic(image, currentPrincipal, triggeringPrincipal,
+ hadCrossOriginRedirects);
+}