diff -r 000000000000 -r 6474c204b198 image/src/imgRequestProxy.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/image/src/imgRequestProxy.cpp Wed Dec 31 06:09:35 2014 +0100 @@ -0,0 +1,1094 @@ +/* -*- 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 "imgIOnloadBlocker.h" + +#include "Image.h" +#include "ImageOps.h" +#include "nsError.h" +#include "ImageLogging.h" +#include "nsCRTGlue.h" +#include "imgINotificationObserver.h" +#include "nsNetUtil.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 GetImage() const = 0; + virtual bool HasImage() const = 0; + virtual already_AddRefed GetStatusTracker() 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 GetImage() const MOZ_OVERRIDE; + virtual bool HasImage() const MOZ_OVERRIDE; + virtual already_AddRefed GetStatusTracker() const MOZ_OVERRIDE; + + virtual imgRequest* GetOwner() const MOZ_OVERRIDE { + return mOwner; + } + + virtual void SetOwner(imgRequest* aOwner) MOZ_OVERRIDE { + mOwner = aOwner; + + if (mOwner) { + nsRefPtr ownerStatusTracker = GetStatusTracker(); + mOwnerHasImage = ownerStatusTracker && ownerStatusTracker->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. + nsRefPtr mOwner; + + bool mOwnerHasImage; +}; + +already_AddRefed +RequestBehaviour::GetImage() const +{ + if (!mOwnerHasImage) + return nullptr; + nsRefPtr statusTracker = GetStatusTracker(); + return statusTracker->GetImage(); +} + +already_AddRefed +RequestBehaviour::GetStatusTracker() 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->GetStatusTracker() instead of just + // mOwner->mStatusTracker -- we might have a null mImage and yet have an + // mOwner with a non-null mImage (and a null mStatusTracker pointer). + return mOwner->GetStatusTracker(); +} + +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), + mSentStartContainer(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(GetImgLog(), "imgRequestProxy::Init", "request", aOwner); + + NS_ABORT_IF_FALSE(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(); + + // Were we decoded before? + bool wasDecoded = false; + nsRefPtr statusTracker = GetStatusTracker(); + if (statusTracker->HasImage() && + statusTracker->GetImageStatus() & imgIRequest::STATUS_FRAME_COMPLETE) { + wasDecoded = true; + } + + 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 were decoded, or if we'd previously requested a decode, request a + // decode on the new image + if (wasDecoded || mDecodeRequested) + GetOwner()->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 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 **/ + +/* readonly attribute wstring name; */ +NS_IMETHODIMP imgRequestProxy::GetName(nsACString &aName) +{ + aName.Truncate(); + + if (mURI) + mURI->GetSpec(aName); + + return NS_OK; +} + +/* boolean isPending (); */ +NS_IMETHODIMP imgRequestProxy::IsPending(bool *_retval) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +/* readonly attribute nsresult status; */ +NS_IMETHODIMP imgRequestProxy::GetStatus(nsresult *aStatus) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +/* void cancel (in nsresult status); */ +NS_IMETHODIMP imgRequestProxy::Cancel(nsresult status) +{ + if (mCanceled) + return NS_ERROR_FAILURE; + + LOG_SCOPE(GetImgLog(), "imgRequestProxy::Cancel"); + + mCanceled = true; + + nsCOMPtr ev = new imgCancelRunnable(this, status); + return NS_DispatchToCurrentThread(ev); +} + +void +imgRequestProxy::DoCancel(nsresult status) +{ + if (GetOwner()) { + GetOwner()->RemoveProxy(this, status); + } + + NullOutListener(); +} + +/* void cancelAndForgetObserver (in nsresult aStatus); */ +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(GetImgLog(), "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) { + nsCOMPtr ev = + NS_NewRunnableMethod(this, &imgRequestProxy::DoRemoveFromLoadGroup); + NS_DispatchToCurrentThread(ev); + } + + NullOutListener(); + + return NS_OK; +} + +/* void startDecode (); */ +NS_IMETHODIMP +imgRequestProxy::StartDecoding() +{ + if (!GetOwner()) + return NS_ERROR_FAILURE; + + // Flag this, so we know to transfer the request if our owner changes + mDecodeRequested = true; + + // Forward the request + return GetOwner()->StartDecoding(); +} + +/* void requestDecode (); */ +NS_IMETHODIMP +imgRequestProxy::RequestDecode() +{ + if (!GetOwner()) + return NS_ERROR_FAILURE; + + // Flag this, so we know to transfer the request if our owner changes + mDecodeRequested = true; + + // Forward the request + return GetOwner()->RequestDecode(); +} + + +/* void lockImage (); */ +NS_IMETHODIMP +imgRequestProxy::LockImage() +{ + mLockCount++; + nsRefPtr image = GetImage(); + if (image) + return image->LockImage(); + return NS_OK; +} + +/* void unlockImage (); */ +NS_IMETHODIMP +imgRequestProxy::UnlockImage() +{ + NS_ABORT_IF_FALSE(mLockCount > 0, "calling unlock but no locks!"); + + mLockCount--; + nsRefPtr image = GetImage(); + if (image) + return image->UnlockImage(); + return NS_OK; +} + +/* void requestDiscard (); */ +NS_IMETHODIMP +imgRequestProxy::RequestDiscard() +{ + nsRefPtr image = GetImage(); + if (image) + return image->RequestDiscard(); + return NS_OK; +} + +NS_IMETHODIMP +imgRequestProxy::IncrementAnimationConsumers() +{ + mAnimationConsumers++; + nsRefPtr 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--; + nsRefPtr image = GetImage(); + if (image) + image->DecrementAnimationConsumers(); + } + return NS_OK; +} + +void +imgRequestProxy::ClearAnimationConsumers() +{ + while (mAnimationConsumers > 0) + DecrementAnimationConsumers(); +} + +/* void suspend (); */ +NS_IMETHODIMP imgRequestProxy::Suspend() +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +/* void resume (); */ +NS_IMETHODIMP imgRequestProxy::Resume() +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +/* attribute nsILoadGroup loadGroup */ +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; +} + +/* attribute nsLoadFlags loadFlags */ +NS_IMETHODIMP imgRequestProxy::GetLoadFlags(nsLoadFlags *flags) +{ + *flags = mLoadFlags; + return NS_OK; +} +NS_IMETHODIMP imgRequestProxy::SetLoadFlags(nsLoadFlags flags) +{ + mLoadFlags = flags; + return NS_OK; +} + +/** imgIRequest methods **/ + +/* attribute imgIContainer image; */ +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. + nsRefPtr image = GetImage(); + nsCOMPtr imageToReturn; + if (image) + imageToReturn = do_QueryInterface(image); + if (!imageToReturn && GetOwner()) + imageToReturn = GetOwner()->mImage; + + if (!imageToReturn) + return NS_ERROR_FAILURE; + + imageToReturn.swap(*aImage); + + return NS_OK; +} + +/* readonly attribute unsigned long imageStatus; */ +NS_IMETHODIMP imgRequestProxy::GetImageStatus(uint32_t *aStatus) +{ + nsRefPtr statusTracker = GetStatusTracker(); + *aStatus = statusTracker->GetImageStatus(); + + return NS_OK; +} + +/* readonly attribute nsIURI URI; */ +NS_IMETHODIMP imgRequestProxy::GetURI(nsIURI **aURI) +{ + MOZ_ASSERT(NS_IsMainThread(), "Must be on main thread to convert URI"); + nsCOMPtr uri = mURI->ToIURI(); + uri.forget(aURI); + return NS_OK; +} + +nsresult imgRequestProxy::GetURI(ImageURL **aURI) +{ + if (!mURI) + return NS_ERROR_FAILURE; + + NS_ADDREF(*aURI = mURI); + + return NS_OK; +} + +/* readonly attribute imgINotificationObserver notificationObserver; */ +NS_IMETHODIMP imgRequestProxy::GetNotificationObserver(imgINotificationObserver **aObserver) +{ + *aObserver = mListener; + NS_IF_ADDREF(*aObserver); + return NS_OK; +} + +/* readonly attribute string mimeType; */ +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 currentPrincipal; + aThis->GetImagePrincipal(getter_AddRefs(currentPrincipal)); + nsRefPtr 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(GetImgLog(), "imgRequestProxy::Clone"); + + *aClone = nullptr; + nsRefPtr 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; + + // 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; +} + +/* readonly attribute nsIPrincipal imagePrincipal; */ +NS_IMETHODIMP imgRequestProxy::GetImagePrincipal(nsIPrincipal **aPrincipal) +{ + if (!GetOwner()) + return NS_ERROR_FAILURE; + + NS_ADDREF(*aPrincipal = GetOwner()->GetPrincipal()); + return NS_OK; +} + +/* readonly attribute bool multipart; */ +NS_IMETHODIMP imgRequestProxy::GetMultipart(bool *aMultipart) +{ + if (!GetOwner()) + return NS_ERROR_FAILURE; + + *aMultipart = GetOwner()->GetMultipart(); + + return NS_OK; +} + +/* readonly attribute int32_t CORSMode; */ +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; +} + +/** imgDecoderObserver methods **/ + +void imgRequestProxy::OnStartDecode() +{ + // This notification is deliberately not propagated since there are no + // listeners who care about it. + if (GetOwner()) { + // In the case of streaming jpegs, it is possible to get multiple + // OnStartDecodes which indicates the beginning of a new decode. The cache + // entry's size therefore needs to be reset to 0 here. If we do not do + // this, the code in imgStatusTrackerObserver::OnStopFrame will continue to + // increase the data size cumulatively. + GetOwner()->ResetCacheEntry(); + } +} + +void imgRequestProxy::OnStartContainer() +{ + LOG_FUNC(GetImgLog(), "imgRequestProxy::OnStartContainer"); + + if (mListener && !mCanceled && !mSentStartContainer) { + // Hold a ref to the listener while we call it, just in case. + nsCOMPtr kungFuDeathGrip(mListener); + mListener->Notify(this, imgINotificationObserver::SIZE_AVAILABLE, nullptr); + mSentStartContainer = true; + } +} + +void imgRequestProxy::OnFrameUpdate(const nsIntRect * rect) +{ + LOG_FUNC(GetImgLog(), "imgRequestProxy::OnDataAvailable"); + + if (mListener && !mCanceled) { + // Hold a ref to the listener while we call it, just in case. + nsCOMPtr kungFuDeathGrip(mListener); + mListener->Notify(this, imgINotificationObserver::FRAME_UPDATE, rect); + } +} + +void imgRequestProxy::OnStopFrame() +{ + LOG_FUNC(GetImgLog(), "imgRequestProxy::OnStopFrame"); + + if (mListener && !mCanceled) { + // Hold a ref to the listener while we call it, just in case. + nsCOMPtr kungFuDeathGrip(mListener); + mListener->Notify(this, imgINotificationObserver::FRAME_COMPLETE, nullptr); + } +} + +void imgRequestProxy::OnStopDecode() +{ + LOG_FUNC(GetImgLog(), "imgRequestProxy::OnStopDecode"); + + if (mListener && !mCanceled) { + // Hold a ref to the listener while we call it, just in case. + nsCOMPtr kungFuDeathGrip(mListener); + mListener->Notify(this, imgINotificationObserver::DECODE_COMPLETE, nullptr); + } + + if (GetOwner()) { + // We finished the decode, and thus have the decoded frames. Update the cache + // entry size to take this into account. + GetOwner()->UpdateCacheEntrySize(); + + // Multipart needs reset for next OnStartContainer. + if (GetOwner()->GetMultipart()) + mSentStartContainer = false; + } +} + +void imgRequestProxy::OnDiscard() +{ + LOG_FUNC(GetImgLog(), "imgRequestProxy::OnDiscard"); + + if (mListener && !mCanceled) { + // Hold a ref to the listener while we call it, just in case. + nsCOMPtr kungFuDeathGrip(mListener); + mListener->Notify(this, imgINotificationObserver::DISCARD, nullptr); + } + if (GetOwner()) { + // Update the cache entry size, since we just got rid of frame data. + GetOwner()->UpdateCacheEntrySize(); + } +} + +void imgRequestProxy::OnUnlockedDraw() +{ + LOG_FUNC(GetImgLog(), "imgRequestProxy::OnUnlockedDraw"); + + if (mListener && !mCanceled) { + // Hold a ref to the listener while we call it, just in case. + nsCOMPtr kungFuDeathGrip(mListener); + mListener->Notify(this, imgINotificationObserver::UNLOCKED_DRAW, nullptr); + } +} + +void imgRequestProxy::OnImageIsAnimated() +{ + LOG_FUNC(GetImgLog(), "imgRequestProxy::OnImageIsAnimated"); + if (mListener && !mCanceled) { + // Hold a ref to the listener while we call it, just in case. + nsCOMPtr kungFuDeathGrip(mListener); + mListener->Notify(this, imgINotificationObserver::IS_ANIMATED, nullptr); + } +} + +void imgRequestProxy::OnStartRequest() +{ +#ifdef PR_LOGGING + nsAutoCString name; + GetName(name); + LOG_FUNC_WITH_PARAM(GetImgLog(), "imgRequestProxy::OnStartRequest", "name", name.get()); +#endif +} + +void imgRequestProxy::OnStopRequest(bool lastPart) +{ +#ifdef PR_LOGGING + nsAutoCString name; + GetName(name); + LOG_FUNC_WITH_PARAM(GetImgLog(), "imgRequestProxy::OnStopRequest", "name", name.get()); +#endif + // 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 kungFuDeathGrip(this); + + if (mListener && !mCanceled) { + // Hold a ref to the listener while we call it, just in case. + nsCOMPtr kungFuDeathGrip(mListener); + mListener->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 (lastPart || (mLoadFlags & nsIRequest::LOAD_BACKGROUND) == 0) { + RemoveFromLoadGroup(lastPart); + // More data is coming, so change the request to be a background request + // and put it back in the loadgroup. + if (!lastPart) { + mLoadFlags |= nsIRequest::LOAD_BACKGROUND; + AddToLoadGroup(); + } + } + + if (mListenerIsStrongRef && lastPart) { + 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() +{ +#ifdef PR_LOGGING + nsAutoCString name; + GetName(name); + LOG_FUNC_WITH_PARAM(GetImgLog(), "imgRequestProxy::BlockOnload", "name", name.get()); +#endif + + nsCOMPtr blocker = do_QueryInterface(mListener); + if (blocker) { + blocker->BlockOnload(this); + } +} + +void imgRequestProxy::UnblockOnload() +{ +#ifdef PR_LOGGING + nsAutoCString name; + GetName(name); + LOG_FUNC_WITH_PARAM(GetImgLog(), "imgRequestProxy::UnblockOnload", "name", name.get()); +#endif + + nsCOMPtr 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 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; + nsRefPtr 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. + nsRefPtr frozenImage = ImageOps::Freeze(image); + + // Create a static imgRequestProxy with our new extracted frame. + nsCOMPtr currentPrincipal; + GetImagePrincipal(getter_AddRefs(currentPrincipal)); + nsRefPtr 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. + + nsRefPtr statusTracker = GetStatusTracker(); + if (GetOwner()) { + // Send the notifications to our listener asynchronously. + statusTracker->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. + NS_ABORT_IF_FALSE(HasImage(), + "if we have no imgRequest, we should have an Image"); + statusTracker->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. + + nsRefPtr statusTracker = GetStatusTracker(); + statusTracker->SyncNotify(this); +} + +void +imgRequestProxy::SetHasImage() +{ + nsRefPtr statusTracker = GetStatusTracker(); + MOZ_ASSERT(statusTracker); + nsRefPtr image = statusTracker->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 +imgRequestProxy::GetStatusTracker() const +{ + return mBehaviour->GetStatusTracker(); +} + +already_AddRefed +imgRequestProxy::GetImage() const +{ + return mBehaviour->GetImage(); +} + +bool +RequestBehaviour::HasImage() const +{ + if (!mOwnerHasImage) + return false; + nsRefPtr statusTracker = GetStatusTracker(); + return statusTracker ? statusTracker->HasImage() : false; +} + +bool +imgRequestProxy::HasImage() const +{ + return mBehaviour->HasImage(); +} + +imgRequest* +imgRequestProxy::GetOwner() const +{ + return mBehaviour->GetOwner(); +} + +////////////////// imgRequestProxyStatic methods + +class StaticBehaviour : public ProxyBehaviour +{ +public: + StaticBehaviour(mozilla::image::Image* aImage) : mImage(aImage) {} + + virtual already_AddRefed + GetImage() const MOZ_OVERRIDE { + nsRefPtr image = mImage; + return image.forget(); + } + + virtual bool HasImage() const MOZ_OVERRIDE { + return mImage; + } + + virtual already_AddRefed GetStatusTracker() const MOZ_OVERRIDE { + return mImage->GetStatusTracker(); + } + + virtual imgRequest* GetOwner() const MOZ_OVERRIDE { + return nullptr; + } + + virtual void SetOwner(imgRequest* aOwner) MOZ_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. + nsRefPtr mImage; +}; + +imgRequestProxyStatic::imgRequestProxyStatic(mozilla::image::Image* aImage, + nsIPrincipal* aPrincipal) +: mPrincipal(aPrincipal) +{ + mBehaviour = new 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); +}