summaryrefslogtreecommitdiffstats
path: root/image/imgRequestProxy.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'image/imgRequestProxy.cpp')
-rw-r--r--image/imgRequestProxy.cpp1084
1 files changed, 1084 insertions, 0 deletions
diff --git a/image/imgRequestProxy.cpp b/image/imgRequestProxy.cpp
new file mode 100644
index 000000000..4ccf1ff1d
--- /dev/null
+++ b/image/imgRequestProxy.cpp
@@ -0,0 +1,1084 @@
+/* -*- 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 "ImageLogging.h"
+#include "imgRequestProxy.h"
+#include "imgIOnloadBlocker.h"
+#include "imgLoader.h"
+#include "Image.h"
+#include "ImageOps.h"
+#include "nsError.h"
+#include "nsCRTGlue.h"
+#include "imgINotificationObserver.h"
+
+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() {}
+
+ 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) {}
+
+ virtual already_AddRefed<mozilla::image::Image>GetImage() const override;
+ virtual bool HasImage() const override;
+ virtual already_AddRefed<ProgressTracker> GetProgressTracker() const override;
+
+ virtual imgRequest* GetOwner() const override {
+ return mOwner;
+ }
+
+ virtual 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, imgIRequest)
+ NS_INTERFACE_MAP_ENTRY(imgIRequest)
+ NS_INTERFACE_MAP_ENTRY(nsIRequest)
+ NS_INTERFACE_MAP_ENTRY(nsISupportsPriority)
+ NS_INTERFACE_MAP_ENTRY(nsISecurityInfoProvider)
+ 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),
+ mCanceled(false),
+ mIsInLoadGroup(false),
+ mListenerIsStrongRef(false),
+ mDecodeRequested(false),
+ mDeferNotifications(false)
+{
+ /* member initializers and constructor code */
+
+}
+
+imgRequestProxy::~imgRequestProxy()
+{
+ /* destructor code */
+ NS_PRECONDITION(!mListener,
+ "Someone forgot to properly cancel this request!");
+
+ // Unlock the image the proper number of times if we're holding locks on
+ // it. Note that UnlockImage() decrements mLockCount each time it's called.
+ while (mLockCount) {
+ UnlockImage();
+ }
+
+ 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();
+
+ if (GetOwner()) {
+ /* 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;
+ GetOwner()->RemoveProxy(this, NS_OK);
+ }
+}
+
+nsresult
+imgRequestProxy::Init(imgRequest* aOwner,
+ nsILoadGroup* aLoadGroup,
+ ImageURL* aURI,
+ imgINotificationObserver* aObserver)
+{
+ NS_PRECONDITION(!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 AddProxy call below, since
+ // that call might well want to release it if the imgRequest has
+ // already seen OnStopRequest.
+ if (mListener) {
+ mListenerIsStrongRef = true;
+ NS_ADDREF(mListener);
+ }
+ mLoadGroup = aLoadGroup;
+ mURI = aURI;
+
+ // Note: AddProxy won't send all the On* notifications immediately
+ if (GetOwner()) {
+ GetOwner()->AddProxy(this);
+ }
+
+ return NS_OK;
+}
+
+nsresult
+imgRequestProxy::ChangeOwner(imgRequest* aNewOwner)
+{
+ NS_PRECONDITION(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_IMAGELIB_CHANGING_OWNER);
+
+ mBehaviour->SetOwner(aNewOwner);
+
+ // 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();
+ }
+
+ GetOwner()->AddProxy(this);
+
+ // If we'd previously requested a synchronous decode, request a decode on the
+ // new image.
+ if (mDecodeRequested) {
+ StartDecoding();
+ }
+
+ return NS_OK;
+}
+
+void
+imgRequestProxy::AddToLoadGroup()
+{
+ NS_ASSERTION(!mIsInLoadGroup, "Whaa, we're already in the loadgroup!");
+
+ if (!mIsInLoadGroup && mLoadGroup) {
+ mLoadGroup->AddRequest(this, nullptr);
+ mIsInLoadGroup = true;
+ }
+}
+
+void
+imgRequestProxy::RemoveFromLoadGroup(bool releaseLoadGroup)
+{
+ if (!mIsInLoadGroup) {
+ return;
+ }
+
+ /* 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);
+ mIsInLoadGroup = false;
+
+ if (releaseLoadGroup) {
+ // We're done with the loadgroup, release it.
+ mLoadGroup = 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::Cancel(nsresult status)
+{
+ if (mCanceled) {
+ return NS_ERROR_FAILURE;
+ }
+
+ LOG_SCOPE(gImgLog, "imgRequestProxy::Cancel");
+
+ mCanceled = true;
+
+ nsCOMPtr<nsIRunnable> ev = new imgCancelRunnable(this, status);
+ return NS_DispatchToCurrentThread(ev);
+}
+
+void
+imgRequestProxy::DoCancel(nsresult status)
+{
+ if (GetOwner()) {
+ GetOwner()->RemoveProxy(this, status);
+ }
+
+ 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;
+ }
+
+ LOG_SCOPE(gImgLog, "imgRequestProxy::CancelAndForgetObserver");
+
+ mCanceled = true;
+
+ // Now cheat and make sure our removal from loadgroup happens async
+ bool oldIsInLoadGroup = mIsInLoadGroup;
+ mIsInLoadGroup = false;
+
+ if (GetOwner()) {
+ GetOwner()->RemoveProxy(this, aStatus);
+ }
+
+ mIsInLoadGroup = oldIsInLoadGroup;
+
+ if (mIsInLoadGroup) {
+ NS_DispatchToCurrentThread(NewRunnableMethod(this, &imgRequestProxy::DoRemoveFromLoadGroup));
+ }
+
+ NullOutListener();
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+imgRequestProxy::StartDecoding()
+{
+ // Flag this, so we know to transfer the request if our owner changes
+ mDecodeRequested = true;
+
+ RefPtr<Image> image = GetImage();
+ if (image) {
+ return image->StartDecoding();
+ }
+
+ if (GetOwner()) {
+ GetOwner()->StartDecoding();
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+imgRequestProxy::LockImage()
+{
+ mLockCount++;
+ RefPtr<Image> image = GetImage();
+ 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 = GetImage();
+ 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 = GetImage();
+ 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 = GetImage();
+ 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)
+{
+ mLoadGroup = loadGroup;
+ 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;
+}
+
+/** 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 = do_QueryInterface(image);
+ }
+ if (!imageToReturn && GetOwner()) {
+ imageToReturn = GetOwner()->GetImage();
+ }
+ if (!imageToReturn) {
+ return NS_ERROR_FAILURE;
+ }
+
+ imageToReturn.swap(*aImage);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+imgRequestProxy::GetImageStatus(uint32_t* aStatus)
+{
+ 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->ToIURI();
+ uri.forget(aURI);
+ return NS_OK;
+}
+
+nsresult
+imgRequestProxy::GetCurrentURI(nsIURI** aURI)
+{
+ if (!GetOwner()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return GetOwner()->GetCurrentURI(aURI);
+}
+
+nsresult
+imgRequestProxy::GetURI(ImageURL** aURI)
+{
+ if (!mURI) {
+ return NS_ERROR_FAILURE;
+ }
+
+ NS_ADDREF(*aURI = mURI);
+
+ return NS_OK;
+}
+
+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_strdup(type);
+
+ return NS_OK;
+}
+
+static imgRequestProxy* NewProxy(imgRequestProxy* /*aThis*/)
+{
+ return new imgRequestProxy();
+}
+
+imgRequestProxy* NewStaticProxy(imgRequestProxy* aThis)
+{
+ nsCOMPtr<nsIPrincipal> currentPrincipal;
+ aThis->GetImagePrincipal(getter_AddRefs(currentPrincipal));
+ RefPtr<Image> image = aThis->GetImage();
+ return new imgRequestProxyStatic(image, currentPrincipal);
+}
+
+NS_IMETHODIMP
+imgRequestProxy::Clone(imgINotificationObserver* aObserver,
+ imgIRequest** aClone)
+{
+ nsresult result;
+ imgRequestProxy* proxy;
+ result = Clone(aObserver, &proxy);
+ *aClone = proxy;
+ return result;
+}
+
+nsresult imgRequestProxy::Clone(imgINotificationObserver* aObserver,
+ imgRequestProxy** aClone)
+{
+ return PerformClone(aObserver, NewProxy, aClone);
+}
+
+nsresult
+imgRequestProxy::PerformClone(imgINotificationObserver* aObserver,
+ imgRequestProxy* (aAllocFn)(imgRequestProxy*),
+ imgRequestProxy** aClone)
+{
+ NS_PRECONDITION(aClone, "Null out param");
+
+ LOG_SCOPE(gImgLog, "imgRequestProxy::Clone");
+
+ *aClone = nullptr;
+ RefPtr<imgRequestProxy> clone = aAllocFn(this);
+
+ // 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(), mLoadGroup,
+ mURI, aObserver);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ if (GetOwner() && GetOwner()->GetValidator()) {
+ clone->SetNotificationsDeferred(true);
+ GetOwner()->GetValidator()->AddProxy(clone);
+ }
+
+ // 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);
+
+ // 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.
+ clone->SyncNotifyListener();
+
+ 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::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;
+}
+
+/** 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;
+}
+
+/** nsISecurityInfoProvider methods **/
+
+NS_IMETHODIMP
+imgRequestProxy::GetSecurityInfo(nsISupports** _retval)
+{
+ if (GetOwner()) {
+ return GetOwner()->GetSecurityInfo(_retval);
+ }
+
+ *_retval = nullptr;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+imgRequestProxy::GetHasTransferredData(bool* hasData)
+{
+ if (GetOwner()) {
+ *hasData = GetOwner()->HasTransferredData();
+ } else {
+ // The safe thing to do is to claim we have data
+ *hasData = true;
+ }
+ 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:
+ NS_NOTREACHED("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)
+{
+ if (MOZ_LOG_TEST(gImgLog, LogLevel::Debug)) {
+ nsAutoCString name;
+ GetName(name);
+ LOG_FUNC_WITH_PARAM(gImgLog, "imgRequestProxy::OnLoadComplete",
+ "name", name.get());
+ }
+
+ // 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.
+ nsCOMPtr<imgIRequest> kungFuDeathGrip(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) {
+ RemoveFromLoadGroup(aLastPart);
+ // More data is coming, so change the request to be a background request
+ // and put it back in the loadgroup.
+ if (!aLastPart) {
+ mLoadFlags |= nsIRequest::LOAD_BACKGROUND;
+ AddToLoadGroup();
+ }
+ }
+
+ if (mListenerIsStrongRef && aLastPart) {
+ NS_PRECONDITION(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::BlockOnload()
+{
+ if (MOZ_LOG_TEST(gImgLog, LogLevel::Debug)) {
+ nsAutoCString name;
+ GetName(name);
+ LOG_FUNC_WITH_PARAM(gImgLog, "imgRequestProxy::BlockOnload",
+ "name", name.get());
+ }
+
+ nsCOMPtr<imgIOnloadBlocker> blocker = do_QueryInterface(mListener);
+ if (blocker) {
+ blocker->BlockOnload(this);
+ }
+}
+
+void
+imgRequestProxy::UnblockOnload()
+{
+ if (MOZ_LOG_TEST(gImgLog, LogLevel::Debug)) {
+ nsAutoCString name;
+ GetName(name);
+ LOG_FUNC_WITH_PARAM(gImgLog, "imgRequestProxy::UnblockOnload",
+ "name", name.get());
+ }
+
+ nsCOMPtr<imgIOnloadBlocker> blocker = do_QueryInterface(mListener);
+ if (blocker) {
+ blocker->UnblockOnload(this);
+ }
+}
+
+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)
+{
+ imgRequestProxy* proxy;
+ nsresult result = GetStaticRequest(&proxy);
+ *aReturn = proxy;
+ return result;
+}
+
+nsresult
+imgRequestProxy::GetStaticRequest(imgRequestProxy** aReturn)
+{
+ *aReturn = nullptr;
+ 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.
+ NS_ADDREF(*aReturn = this);
+ return NS_OK;
+ }
+
+ // Check for errors in the image. Callers code rely on GetStaticRequest
+ // failing in this case, though with FrozenImage there's no technical reason
+ // for it anymore.
+ if (image->HasError()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // 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));
+ RefPtr<imgRequestProxy> req = new imgRequestProxyStatic(frozenImage,
+ currentPrincipal);
+ req->Init(nullptr, nullptr, mURI, nullptr);
+
+ NS_ADDREF(*aReturn = req);
+
+ return NS_OK;
+}
+
+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();
+}
+
+////////////////// imgRequestProxyStatic methods
+
+class StaticBehaviour : public ProxyBehaviour
+{
+public:
+ explicit StaticBehaviour(mozilla::image::Image* aImage) : mImage(aImage) {}
+
+ virtual already_AddRefed<mozilla::image::Image>
+ GetImage() const override {
+ RefPtr<mozilla::image::Image> image = mImage;
+ return image.forget();
+ }
+
+ virtual bool HasImage() const override {
+ return mImage;
+ }
+
+ virtual already_AddRefed<ProgressTracker> GetProgressTracker()
+ const override {
+ return mImage->GetProgressTracker();
+ }
+
+ virtual imgRequest* GetOwner() const override {
+ return nullptr;
+ }
+
+ virtual 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* aPrincipal)
+: mPrincipal(aPrincipal)
+{
+ mBehaviour = mozilla::MakeUnique<StaticBehaviour>(aImage);
+}
+
+NS_IMETHODIMP
+imgRequestProxyStatic::GetImagePrincipal(nsIPrincipal** aPrincipal)
+{
+ if (!mPrincipal) {
+ return NS_ERROR_FAILURE;
+ }
+
+ NS_ADDREF(*aPrincipal = mPrincipal);
+
+ return NS_OK;
+}
+
+nsresult
+imgRequestProxyStatic::Clone(imgINotificationObserver* aObserver,
+ imgRequestProxy** aClone)
+{
+ return PerformClone(aObserver, NewStaticProxy, aClone);
+}