michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- michael@0: * michael@0: * This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "imgRequestProxy.h" michael@0: #include "imgIOnloadBlocker.h" michael@0: michael@0: #include "Image.h" michael@0: #include "ImageOps.h" michael@0: #include "nsError.h" michael@0: #include "ImageLogging.h" michael@0: #include "nsCRTGlue.h" michael@0: #include "imgINotificationObserver.h" michael@0: #include "nsNetUtil.h" michael@0: michael@0: using namespace mozilla::image; michael@0: michael@0: // The split of imgRequestProxy and imgRequestProxyStatic means that michael@0: // certain overridden functions need to be usable in the destructor. michael@0: // Since virtual functions can't be used in that way, this class michael@0: // provides a behavioural trait for each class to use instead. michael@0: class ProxyBehaviour michael@0: { michael@0: public: michael@0: virtual ~ProxyBehaviour() {} michael@0: michael@0: virtual already_AddRefed GetImage() const = 0; michael@0: virtual bool HasImage() const = 0; michael@0: virtual already_AddRefed GetStatusTracker() const = 0; michael@0: virtual imgRequest* GetOwner() const = 0; michael@0: virtual void SetOwner(imgRequest* aOwner) = 0; michael@0: }; michael@0: michael@0: class RequestBehaviour : public ProxyBehaviour michael@0: { michael@0: public: michael@0: RequestBehaviour() : mOwner(nullptr), mOwnerHasImage(false) {} michael@0: michael@0: virtual already_AddRefed GetImage() const MOZ_OVERRIDE; michael@0: virtual bool HasImage() const MOZ_OVERRIDE; michael@0: virtual already_AddRefed GetStatusTracker() const MOZ_OVERRIDE; michael@0: michael@0: virtual imgRequest* GetOwner() const MOZ_OVERRIDE { michael@0: return mOwner; michael@0: } michael@0: michael@0: virtual void SetOwner(imgRequest* aOwner) MOZ_OVERRIDE { michael@0: mOwner = aOwner; michael@0: michael@0: if (mOwner) { michael@0: nsRefPtr ownerStatusTracker = GetStatusTracker(); michael@0: mOwnerHasImage = ownerStatusTracker && ownerStatusTracker->HasImage(); michael@0: } else { michael@0: mOwnerHasImage = false; michael@0: } michael@0: } michael@0: michael@0: private: michael@0: // We maintain the following invariant: michael@0: // The proxy is registered at most with a single imgRequest as an observer, michael@0: // and whenever it is, mOwner points to that object. This helps ensure that michael@0: // imgRequestProxy::~imgRequestProxy unregisters the proxy as an observer michael@0: // from whatever request it was registered with (if any). This, in turn, michael@0: // means that imgRequest::mObservers will not have any stale pointers in it. michael@0: nsRefPtr mOwner; michael@0: michael@0: bool mOwnerHasImage; michael@0: }; michael@0: michael@0: already_AddRefed michael@0: RequestBehaviour::GetImage() const michael@0: { michael@0: if (!mOwnerHasImage) michael@0: return nullptr; michael@0: nsRefPtr statusTracker = GetStatusTracker(); michael@0: return statusTracker->GetImage(); michael@0: } michael@0: michael@0: already_AddRefed michael@0: RequestBehaviour::GetStatusTracker() const michael@0: { michael@0: // NOTE: It's possible that our mOwner has an Image that it didn't notify michael@0: // us about, if we were Canceled before its Image was constructed. michael@0: // (Canceling removes us as an observer, so mOwner has no way to notify us). michael@0: // That's why this method uses mOwner->GetStatusTracker() instead of just michael@0: // mOwner->mStatusTracker -- we might have a null mImage and yet have an michael@0: // mOwner with a non-null mImage (and a null mStatusTracker pointer). michael@0: return mOwner->GetStatusTracker(); michael@0: } michael@0: michael@0: NS_IMPL_ADDREF(imgRequestProxy) michael@0: NS_IMPL_RELEASE(imgRequestProxy) michael@0: michael@0: NS_INTERFACE_MAP_BEGIN(imgRequestProxy) michael@0: NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, imgIRequest) michael@0: NS_INTERFACE_MAP_ENTRY(imgIRequest) michael@0: NS_INTERFACE_MAP_ENTRY(nsIRequest) michael@0: NS_INTERFACE_MAP_ENTRY(nsISupportsPriority) michael@0: NS_INTERFACE_MAP_ENTRY(nsISecurityInfoProvider) michael@0: NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsITimedChannel, TimedChannel() != nullptr) michael@0: NS_INTERFACE_MAP_END michael@0: michael@0: imgRequestProxy::imgRequestProxy() : michael@0: mBehaviour(new RequestBehaviour), michael@0: mURI(nullptr), michael@0: mListener(nullptr), michael@0: mLoadFlags(nsIRequest::LOAD_NORMAL), michael@0: mLockCount(0), michael@0: mAnimationConsumers(0), michael@0: mCanceled(false), michael@0: mIsInLoadGroup(false), michael@0: mListenerIsStrongRef(false), michael@0: mDecodeRequested(false), michael@0: mDeferNotifications(false), michael@0: mSentStartContainer(false) michael@0: { michael@0: /* member initializers and constructor code */ michael@0: michael@0: } michael@0: michael@0: imgRequestProxy::~imgRequestProxy() michael@0: { michael@0: /* destructor code */ michael@0: NS_PRECONDITION(!mListener, "Someone forgot to properly cancel this request!"); michael@0: michael@0: // Unlock the image the proper number of times if we're holding locks on it. michael@0: // Note that UnlockImage() decrements mLockCount each time it's called. michael@0: while (mLockCount) michael@0: UnlockImage(); michael@0: michael@0: ClearAnimationConsumers(); michael@0: michael@0: // Explicitly set mListener to null to ensure that the RemoveProxy michael@0: // call below can't send |this| to an arbitrary listener while |this| michael@0: // is being destroyed. This is all belt-and-suspenders in view of the michael@0: // above assert. michael@0: NullOutListener(); michael@0: michael@0: if (GetOwner()) { michael@0: /* Call RemoveProxy with a successful status. This will keep the michael@0: channel, if still downloading data, from being canceled if 'this' is michael@0: the last observer. This allows the image to continue to download and michael@0: be cached even if no one is using it currently. michael@0: */ michael@0: mCanceled = true; michael@0: GetOwner()->RemoveProxy(this, NS_OK); michael@0: } michael@0: } michael@0: michael@0: nsresult imgRequestProxy::Init(imgRequest* aOwner, michael@0: nsILoadGroup* aLoadGroup, michael@0: ImageURL* aURI, michael@0: imgINotificationObserver* aObserver) michael@0: { michael@0: NS_PRECONDITION(!GetOwner() && !mListener, "imgRequestProxy is already initialized"); michael@0: michael@0: LOG_SCOPE_WITH_PARAM(GetImgLog(), "imgRequestProxy::Init", "request", aOwner); michael@0: michael@0: NS_ABORT_IF_FALSE(mAnimationConsumers == 0, "Cannot have animation before Init"); michael@0: michael@0: mBehaviour->SetOwner(aOwner); michael@0: mListener = aObserver; michael@0: // Make sure to addref mListener before the AddProxy call below, since michael@0: // that call might well want to release it if the imgRequest has michael@0: // already seen OnStopRequest. michael@0: if (mListener) { michael@0: mListenerIsStrongRef = true; michael@0: NS_ADDREF(mListener); michael@0: } michael@0: mLoadGroup = aLoadGroup; michael@0: mURI = aURI; michael@0: michael@0: // Note: AddProxy won't send all the On* notifications immediately michael@0: if (GetOwner()) michael@0: GetOwner()->AddProxy(this); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult imgRequestProxy::ChangeOwner(imgRequest *aNewOwner) michael@0: { michael@0: NS_PRECONDITION(GetOwner(), "Cannot ChangeOwner on a proxy without an owner!"); michael@0: michael@0: if (mCanceled) { michael@0: // Ensure that this proxy has received all notifications to date before michael@0: // we clean it up when removing it from the old owner below. michael@0: SyncNotifyListener(); michael@0: } michael@0: michael@0: // If we're holding locks, unlock the old image. michael@0: // Note that UnlockImage decrements mLockCount each time it's called. michael@0: uint32_t oldLockCount = mLockCount; michael@0: while (mLockCount) michael@0: UnlockImage(); michael@0: michael@0: // If we're holding animation requests, undo them. michael@0: uint32_t oldAnimationConsumers = mAnimationConsumers; michael@0: ClearAnimationConsumers(); michael@0: michael@0: // Were we decoded before? michael@0: bool wasDecoded = false; michael@0: nsRefPtr statusTracker = GetStatusTracker(); michael@0: if (statusTracker->HasImage() && michael@0: statusTracker->GetImageStatus() & imgIRequest::STATUS_FRAME_COMPLETE) { michael@0: wasDecoded = true; michael@0: } michael@0: michael@0: GetOwner()->RemoveProxy(this, NS_IMAGELIB_CHANGING_OWNER); michael@0: michael@0: mBehaviour->SetOwner(aNewOwner); michael@0: michael@0: // If we were locked, apply the locks here michael@0: for (uint32_t i = 0; i < oldLockCount; i++) michael@0: LockImage(); michael@0: michael@0: // If we had animation requests, restore them here. Note that we michael@0: // do this *after* RemoveProxy, which clears out animation consumers michael@0: // (see bug 601723). michael@0: for (uint32_t i = 0; i < oldAnimationConsumers; i++) michael@0: IncrementAnimationConsumers(); michael@0: michael@0: GetOwner()->AddProxy(this); michael@0: michael@0: // If we were decoded, or if we'd previously requested a decode, request a michael@0: // decode on the new image michael@0: if (wasDecoded || mDecodeRequested) michael@0: GetOwner()->StartDecoding(); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: void imgRequestProxy::AddToLoadGroup() michael@0: { michael@0: NS_ASSERTION(!mIsInLoadGroup, "Whaa, we're already in the loadgroup!"); michael@0: michael@0: if (!mIsInLoadGroup && mLoadGroup) { michael@0: mLoadGroup->AddRequest(this, nullptr); michael@0: mIsInLoadGroup = true; michael@0: } michael@0: } michael@0: michael@0: void imgRequestProxy::RemoveFromLoadGroup(bool releaseLoadGroup) michael@0: { michael@0: if (!mIsInLoadGroup) michael@0: return; michael@0: michael@0: /* calling RemoveFromLoadGroup may cause the document to finish michael@0: loading, which could result in our death. We need to make sure michael@0: that we stay alive long enough to fight another battle... at michael@0: least until we exit this function. michael@0: */ michael@0: nsCOMPtr kungFuDeathGrip(this); michael@0: michael@0: mLoadGroup->RemoveRequest(this, nullptr, NS_OK); michael@0: mIsInLoadGroup = false; michael@0: michael@0: if (releaseLoadGroup) { michael@0: // We're done with the loadgroup, release it. michael@0: mLoadGroup = nullptr; michael@0: } michael@0: } michael@0: michael@0: michael@0: /** nsIRequest / imgIRequest methods **/ michael@0: michael@0: /* readonly attribute wstring name; */ michael@0: NS_IMETHODIMP imgRequestProxy::GetName(nsACString &aName) michael@0: { michael@0: aName.Truncate(); michael@0: michael@0: if (mURI) michael@0: mURI->GetSpec(aName); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: /* boolean isPending (); */ michael@0: NS_IMETHODIMP imgRequestProxy::IsPending(bool *_retval) michael@0: { michael@0: return NS_ERROR_NOT_IMPLEMENTED; michael@0: } michael@0: michael@0: /* readonly attribute nsresult status; */ michael@0: NS_IMETHODIMP imgRequestProxy::GetStatus(nsresult *aStatus) michael@0: { michael@0: return NS_ERROR_NOT_IMPLEMENTED; michael@0: } michael@0: michael@0: /* void cancel (in nsresult status); */ michael@0: NS_IMETHODIMP imgRequestProxy::Cancel(nsresult status) michael@0: { michael@0: if (mCanceled) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: LOG_SCOPE(GetImgLog(), "imgRequestProxy::Cancel"); michael@0: michael@0: mCanceled = true; michael@0: michael@0: nsCOMPtr ev = new imgCancelRunnable(this, status); michael@0: return NS_DispatchToCurrentThread(ev); michael@0: } michael@0: michael@0: void michael@0: imgRequestProxy::DoCancel(nsresult status) michael@0: { michael@0: if (GetOwner()) { michael@0: GetOwner()->RemoveProxy(this, status); michael@0: } michael@0: michael@0: NullOutListener(); michael@0: } michael@0: michael@0: /* void cancelAndForgetObserver (in nsresult aStatus); */ michael@0: NS_IMETHODIMP imgRequestProxy::CancelAndForgetObserver(nsresult aStatus) michael@0: { michael@0: // If mCanceled is true but mListener is non-null, that means michael@0: // someone called Cancel() on us but the imgCancelRunnable is still michael@0: // pending. We still need to null out mListener before returning michael@0: // from this function in this case. That means we want to do the michael@0: // RemoveProxy call right now, because we need to deliver the michael@0: // onStopRequest. michael@0: if (mCanceled && !mListener) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: LOG_SCOPE(GetImgLog(), "imgRequestProxy::CancelAndForgetObserver"); michael@0: michael@0: mCanceled = true; michael@0: michael@0: // Now cheat and make sure our removal from loadgroup happens async michael@0: bool oldIsInLoadGroup = mIsInLoadGroup; michael@0: mIsInLoadGroup = false; michael@0: michael@0: if (GetOwner()) { michael@0: GetOwner()->RemoveProxy(this, aStatus); michael@0: } michael@0: michael@0: mIsInLoadGroup = oldIsInLoadGroup; michael@0: michael@0: if (mIsInLoadGroup) { michael@0: nsCOMPtr ev = michael@0: NS_NewRunnableMethod(this, &imgRequestProxy::DoRemoveFromLoadGroup); michael@0: NS_DispatchToCurrentThread(ev); michael@0: } michael@0: michael@0: NullOutListener(); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: /* void startDecode (); */ michael@0: NS_IMETHODIMP michael@0: imgRequestProxy::StartDecoding() michael@0: { michael@0: if (!GetOwner()) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: // Flag this, so we know to transfer the request if our owner changes michael@0: mDecodeRequested = true; michael@0: michael@0: // Forward the request michael@0: return GetOwner()->StartDecoding(); michael@0: } michael@0: michael@0: /* void requestDecode (); */ michael@0: NS_IMETHODIMP michael@0: imgRequestProxy::RequestDecode() michael@0: { michael@0: if (!GetOwner()) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: // Flag this, so we know to transfer the request if our owner changes michael@0: mDecodeRequested = true; michael@0: michael@0: // Forward the request michael@0: return GetOwner()->RequestDecode(); michael@0: } michael@0: michael@0: michael@0: /* void lockImage (); */ michael@0: NS_IMETHODIMP michael@0: imgRequestProxy::LockImage() michael@0: { michael@0: mLockCount++; michael@0: nsRefPtr image = GetImage(); michael@0: if (image) michael@0: return image->LockImage(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: /* void unlockImage (); */ michael@0: NS_IMETHODIMP michael@0: imgRequestProxy::UnlockImage() michael@0: { michael@0: NS_ABORT_IF_FALSE(mLockCount > 0, "calling unlock but no locks!"); michael@0: michael@0: mLockCount--; michael@0: nsRefPtr image = GetImage(); michael@0: if (image) michael@0: return image->UnlockImage(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: /* void requestDiscard (); */ michael@0: NS_IMETHODIMP michael@0: imgRequestProxy::RequestDiscard() michael@0: { michael@0: nsRefPtr image = GetImage(); michael@0: if (image) michael@0: return image->RequestDiscard(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: imgRequestProxy::IncrementAnimationConsumers() michael@0: { michael@0: mAnimationConsumers++; michael@0: nsRefPtr image = GetImage(); michael@0: if (image) michael@0: image->IncrementAnimationConsumers(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: imgRequestProxy::DecrementAnimationConsumers() michael@0: { michael@0: // We may get here if some responsible code called Increment, michael@0: // then called us, but we have meanwhile called ClearAnimationConsumers michael@0: // because we needed to get rid of them earlier (see michael@0: // imgRequest::RemoveProxy), and hence have nothing left to michael@0: // decrement. (In such a case we got rid of the animation consumers michael@0: // early, but not the observer.) michael@0: if (mAnimationConsumers > 0) { michael@0: mAnimationConsumers--; michael@0: nsRefPtr image = GetImage(); michael@0: if (image) michael@0: image->DecrementAnimationConsumers(); michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: void michael@0: imgRequestProxy::ClearAnimationConsumers() michael@0: { michael@0: while (mAnimationConsumers > 0) michael@0: DecrementAnimationConsumers(); michael@0: } michael@0: michael@0: /* void suspend (); */ michael@0: NS_IMETHODIMP imgRequestProxy::Suspend() michael@0: { michael@0: return NS_ERROR_NOT_IMPLEMENTED; michael@0: } michael@0: michael@0: /* void resume (); */ michael@0: NS_IMETHODIMP imgRequestProxy::Resume() michael@0: { michael@0: return NS_ERROR_NOT_IMPLEMENTED; michael@0: } michael@0: michael@0: /* attribute nsILoadGroup loadGroup */ michael@0: NS_IMETHODIMP imgRequestProxy::GetLoadGroup(nsILoadGroup **loadGroup) michael@0: { michael@0: NS_IF_ADDREF(*loadGroup = mLoadGroup.get()); michael@0: return NS_OK; michael@0: } michael@0: NS_IMETHODIMP imgRequestProxy::SetLoadGroup(nsILoadGroup *loadGroup) michael@0: { michael@0: mLoadGroup = loadGroup; michael@0: return NS_OK; michael@0: } michael@0: michael@0: /* attribute nsLoadFlags loadFlags */ michael@0: NS_IMETHODIMP imgRequestProxy::GetLoadFlags(nsLoadFlags *flags) michael@0: { michael@0: *flags = mLoadFlags; michael@0: return NS_OK; michael@0: } michael@0: NS_IMETHODIMP imgRequestProxy::SetLoadFlags(nsLoadFlags flags) michael@0: { michael@0: mLoadFlags = flags; michael@0: return NS_OK; michael@0: } michael@0: michael@0: /** imgIRequest methods **/ michael@0: michael@0: /* attribute imgIContainer image; */ michael@0: NS_IMETHODIMP imgRequestProxy::GetImage(imgIContainer **aImage) michael@0: { michael@0: NS_ENSURE_TRUE(aImage, NS_ERROR_NULL_POINTER); michael@0: // It's possible that our owner has an image but hasn't notified us of it - michael@0: // that'll happen if we get Canceled before the owner instantiates its image michael@0: // (because Canceling unregisters us as a listener on mOwner). If we're michael@0: // in that situation, just grab the image off of mOwner. michael@0: nsRefPtr image = GetImage(); michael@0: nsCOMPtr imageToReturn; michael@0: if (image) michael@0: imageToReturn = do_QueryInterface(image); michael@0: if (!imageToReturn && GetOwner()) michael@0: imageToReturn = GetOwner()->mImage; michael@0: michael@0: if (!imageToReturn) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: imageToReturn.swap(*aImage); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: /* readonly attribute unsigned long imageStatus; */ michael@0: NS_IMETHODIMP imgRequestProxy::GetImageStatus(uint32_t *aStatus) michael@0: { michael@0: nsRefPtr statusTracker = GetStatusTracker(); michael@0: *aStatus = statusTracker->GetImageStatus(); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: /* readonly attribute nsIURI URI; */ michael@0: NS_IMETHODIMP imgRequestProxy::GetURI(nsIURI **aURI) michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread(), "Must be on main thread to convert URI"); michael@0: nsCOMPtr uri = mURI->ToIURI(); michael@0: uri.forget(aURI); michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult imgRequestProxy::GetURI(ImageURL **aURI) michael@0: { michael@0: if (!mURI) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: NS_ADDREF(*aURI = mURI); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: /* readonly attribute imgINotificationObserver notificationObserver; */ michael@0: NS_IMETHODIMP imgRequestProxy::GetNotificationObserver(imgINotificationObserver **aObserver) michael@0: { michael@0: *aObserver = mListener; michael@0: NS_IF_ADDREF(*aObserver); michael@0: return NS_OK; michael@0: } michael@0: michael@0: /* readonly attribute string mimeType; */ michael@0: NS_IMETHODIMP imgRequestProxy::GetMimeType(char **aMimeType) michael@0: { michael@0: if (!GetOwner()) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: const char *type = GetOwner()->GetMimeType(); michael@0: if (!type) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: *aMimeType = NS_strdup(type); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: static imgRequestProxy* NewProxy(imgRequestProxy* /*aThis*/) michael@0: { michael@0: return new imgRequestProxy(); michael@0: } michael@0: michael@0: imgRequestProxy* NewStaticProxy(imgRequestProxy* aThis) michael@0: { michael@0: nsCOMPtr currentPrincipal; michael@0: aThis->GetImagePrincipal(getter_AddRefs(currentPrincipal)); michael@0: nsRefPtr image = aThis->GetImage(); michael@0: return new imgRequestProxyStatic(image, currentPrincipal); michael@0: } michael@0: michael@0: NS_IMETHODIMP imgRequestProxy::Clone(imgINotificationObserver* aObserver, michael@0: imgIRequest** aClone) michael@0: { michael@0: nsresult result; michael@0: imgRequestProxy* proxy; michael@0: result = Clone(aObserver, &proxy); michael@0: *aClone = proxy; michael@0: return result; michael@0: } michael@0: michael@0: nsresult imgRequestProxy::Clone(imgINotificationObserver* aObserver, michael@0: imgRequestProxy** aClone) michael@0: { michael@0: return PerformClone(aObserver, NewProxy, aClone); michael@0: } michael@0: michael@0: nsresult imgRequestProxy::PerformClone(imgINotificationObserver* aObserver, michael@0: imgRequestProxy* (aAllocFn)(imgRequestProxy*), michael@0: imgRequestProxy** aClone) michael@0: { michael@0: NS_PRECONDITION(aClone, "Null out param"); michael@0: michael@0: LOG_SCOPE(GetImgLog(), "imgRequestProxy::Clone"); michael@0: michael@0: *aClone = nullptr; michael@0: nsRefPtr clone = aAllocFn(this); michael@0: michael@0: // It is important to call |SetLoadFlags()| before calling |Init()| because michael@0: // |Init()| adds the request to the loadgroup. michael@0: // When a request is added to a loadgroup, its load flags are merged michael@0: // with the load flags of the loadgroup. michael@0: // XXXldb That's not true anymore. Stuff from imgLoader adds the michael@0: // request to the loadgroup. michael@0: clone->SetLoadFlags(mLoadFlags); michael@0: nsresult rv = clone->Init(mBehaviour->GetOwner(), mLoadGroup, mURI, aObserver); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: michael@0: // Assign to *aClone before calling Notify so that if the caller expects to michael@0: // only be notified for requests it's already holding pointers to it won't be michael@0: // surprised. michael@0: NS_ADDREF(*aClone = clone); michael@0: michael@0: // This is wrong!!! We need to notify asynchronously, but there's code that michael@0: // assumes that we don't. This will be fixed in bug 580466. michael@0: clone->SyncNotifyListener(); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: /* readonly attribute nsIPrincipal imagePrincipal; */ michael@0: NS_IMETHODIMP imgRequestProxy::GetImagePrincipal(nsIPrincipal **aPrincipal) michael@0: { michael@0: if (!GetOwner()) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: NS_ADDREF(*aPrincipal = GetOwner()->GetPrincipal()); michael@0: return NS_OK; michael@0: } michael@0: michael@0: /* readonly attribute bool multipart; */ michael@0: NS_IMETHODIMP imgRequestProxy::GetMultipart(bool *aMultipart) michael@0: { michael@0: if (!GetOwner()) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: *aMultipart = GetOwner()->GetMultipart(); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: /* readonly attribute int32_t CORSMode; */ michael@0: NS_IMETHODIMP imgRequestProxy::GetCORSMode(int32_t* aCorsMode) michael@0: { michael@0: if (!GetOwner()) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: *aCorsMode = GetOwner()->GetCORSMode(); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: /** nsISupportsPriority methods **/ michael@0: michael@0: NS_IMETHODIMP imgRequestProxy::GetPriority(int32_t *priority) michael@0: { michael@0: NS_ENSURE_STATE(GetOwner()); michael@0: *priority = GetOwner()->Priority(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP imgRequestProxy::SetPriority(int32_t priority) michael@0: { michael@0: NS_ENSURE_STATE(GetOwner() && !mCanceled); michael@0: GetOwner()->AdjustPriority(this, priority - GetOwner()->Priority()); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP imgRequestProxy::AdjustPriority(int32_t priority) michael@0: { michael@0: // We don't require |!mCanceled| here. This may be called even if we're michael@0: // cancelled, because it's invoked as part of the process of removing an image michael@0: // from the load group. michael@0: NS_ENSURE_STATE(GetOwner()); michael@0: GetOwner()->AdjustPriority(this, priority); michael@0: return NS_OK; michael@0: } michael@0: michael@0: /** nsISecurityInfoProvider methods **/ michael@0: michael@0: NS_IMETHODIMP imgRequestProxy::GetSecurityInfo(nsISupports** _retval) michael@0: { michael@0: if (GetOwner()) michael@0: return GetOwner()->GetSecurityInfo(_retval); michael@0: michael@0: *_retval = nullptr; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP imgRequestProxy::GetHasTransferredData(bool* hasData) michael@0: { michael@0: if (GetOwner()) { michael@0: *hasData = GetOwner()->HasTransferredData(); michael@0: } else { michael@0: // The safe thing to do is to claim we have data michael@0: *hasData = true; michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: /** imgDecoderObserver methods **/ michael@0: michael@0: void imgRequestProxy::OnStartDecode() michael@0: { michael@0: // This notification is deliberately not propagated since there are no michael@0: // listeners who care about it. michael@0: if (GetOwner()) { michael@0: // In the case of streaming jpegs, it is possible to get multiple michael@0: // OnStartDecodes which indicates the beginning of a new decode. The cache michael@0: // entry's size therefore needs to be reset to 0 here. If we do not do michael@0: // this, the code in imgStatusTrackerObserver::OnStopFrame will continue to michael@0: // increase the data size cumulatively. michael@0: GetOwner()->ResetCacheEntry(); michael@0: } michael@0: } michael@0: michael@0: void imgRequestProxy::OnStartContainer() michael@0: { michael@0: LOG_FUNC(GetImgLog(), "imgRequestProxy::OnStartContainer"); michael@0: michael@0: if (mListener && !mCanceled && !mSentStartContainer) { michael@0: // Hold a ref to the listener while we call it, just in case. michael@0: nsCOMPtr kungFuDeathGrip(mListener); michael@0: mListener->Notify(this, imgINotificationObserver::SIZE_AVAILABLE, nullptr); michael@0: mSentStartContainer = true; michael@0: } michael@0: } michael@0: michael@0: void imgRequestProxy::OnFrameUpdate(const nsIntRect * rect) michael@0: { michael@0: LOG_FUNC(GetImgLog(), "imgRequestProxy::OnDataAvailable"); michael@0: michael@0: if (mListener && !mCanceled) { michael@0: // Hold a ref to the listener while we call it, just in case. michael@0: nsCOMPtr kungFuDeathGrip(mListener); michael@0: mListener->Notify(this, imgINotificationObserver::FRAME_UPDATE, rect); michael@0: } michael@0: } michael@0: michael@0: void imgRequestProxy::OnStopFrame() michael@0: { michael@0: LOG_FUNC(GetImgLog(), "imgRequestProxy::OnStopFrame"); michael@0: michael@0: if (mListener && !mCanceled) { michael@0: // Hold a ref to the listener while we call it, just in case. michael@0: nsCOMPtr kungFuDeathGrip(mListener); michael@0: mListener->Notify(this, imgINotificationObserver::FRAME_COMPLETE, nullptr); michael@0: } michael@0: } michael@0: michael@0: void imgRequestProxy::OnStopDecode() michael@0: { michael@0: LOG_FUNC(GetImgLog(), "imgRequestProxy::OnStopDecode"); michael@0: michael@0: if (mListener && !mCanceled) { michael@0: // Hold a ref to the listener while we call it, just in case. michael@0: nsCOMPtr kungFuDeathGrip(mListener); michael@0: mListener->Notify(this, imgINotificationObserver::DECODE_COMPLETE, nullptr); michael@0: } michael@0: michael@0: if (GetOwner()) { michael@0: // We finished the decode, and thus have the decoded frames. Update the cache michael@0: // entry size to take this into account. michael@0: GetOwner()->UpdateCacheEntrySize(); michael@0: michael@0: // Multipart needs reset for next OnStartContainer. michael@0: if (GetOwner()->GetMultipart()) michael@0: mSentStartContainer = false; michael@0: } michael@0: } michael@0: michael@0: void imgRequestProxy::OnDiscard() michael@0: { michael@0: LOG_FUNC(GetImgLog(), "imgRequestProxy::OnDiscard"); michael@0: michael@0: if (mListener && !mCanceled) { michael@0: // Hold a ref to the listener while we call it, just in case. michael@0: nsCOMPtr kungFuDeathGrip(mListener); michael@0: mListener->Notify(this, imgINotificationObserver::DISCARD, nullptr); michael@0: } michael@0: if (GetOwner()) { michael@0: // Update the cache entry size, since we just got rid of frame data. michael@0: GetOwner()->UpdateCacheEntrySize(); michael@0: } michael@0: } michael@0: michael@0: void imgRequestProxy::OnUnlockedDraw() michael@0: { michael@0: LOG_FUNC(GetImgLog(), "imgRequestProxy::OnUnlockedDraw"); michael@0: michael@0: if (mListener && !mCanceled) { michael@0: // Hold a ref to the listener while we call it, just in case. michael@0: nsCOMPtr kungFuDeathGrip(mListener); michael@0: mListener->Notify(this, imgINotificationObserver::UNLOCKED_DRAW, nullptr); michael@0: } michael@0: } michael@0: michael@0: void imgRequestProxy::OnImageIsAnimated() michael@0: { michael@0: LOG_FUNC(GetImgLog(), "imgRequestProxy::OnImageIsAnimated"); michael@0: if (mListener && !mCanceled) { michael@0: // Hold a ref to the listener while we call it, just in case. michael@0: nsCOMPtr kungFuDeathGrip(mListener); michael@0: mListener->Notify(this, imgINotificationObserver::IS_ANIMATED, nullptr); michael@0: } michael@0: } michael@0: michael@0: void imgRequestProxy::OnStartRequest() michael@0: { michael@0: #ifdef PR_LOGGING michael@0: nsAutoCString name; michael@0: GetName(name); michael@0: LOG_FUNC_WITH_PARAM(GetImgLog(), "imgRequestProxy::OnStartRequest", "name", name.get()); michael@0: #endif michael@0: } michael@0: michael@0: void imgRequestProxy::OnStopRequest(bool lastPart) michael@0: { michael@0: #ifdef PR_LOGGING michael@0: nsAutoCString name; michael@0: GetName(name); michael@0: LOG_FUNC_WITH_PARAM(GetImgLog(), "imgRequestProxy::OnStopRequest", "name", name.get()); michael@0: #endif michael@0: // There's all sorts of stuff here that could kill us (the OnStopRequest call michael@0: // on the listener, the removal from the loadgroup, the release of the michael@0: // listener, etc). Don't let them do it. michael@0: nsCOMPtr kungFuDeathGrip(this); michael@0: michael@0: if (mListener && !mCanceled) { michael@0: // Hold a ref to the listener while we call it, just in case. michael@0: nsCOMPtr kungFuDeathGrip(mListener); michael@0: mListener->Notify(this, imgINotificationObserver::LOAD_COMPLETE, nullptr); michael@0: } michael@0: michael@0: // If we're expecting more data from a multipart channel, re-add ourself michael@0: // to the loadgroup so that the document doesn't lose track of the load. michael@0: // If the request is already a background request and there's more data michael@0: // coming, we can just leave the request in the loadgroup as-is. michael@0: if (lastPart || (mLoadFlags & nsIRequest::LOAD_BACKGROUND) == 0) { michael@0: RemoveFromLoadGroup(lastPart); michael@0: // More data is coming, so change the request to be a background request michael@0: // and put it back in the loadgroup. michael@0: if (!lastPart) { michael@0: mLoadFlags |= nsIRequest::LOAD_BACKGROUND; michael@0: AddToLoadGroup(); michael@0: } michael@0: } michael@0: michael@0: if (mListenerIsStrongRef && lastPart) { michael@0: NS_PRECONDITION(mListener, "How did that happen?"); michael@0: // Drop our strong ref to the listener now that we're done with michael@0: // everything. Note that this can cancel us and other fun things michael@0: // like that. Don't add anything in this method after this point. michael@0: imgINotificationObserver* obs = mListener; michael@0: mListenerIsStrongRef = false; michael@0: NS_RELEASE(obs); michael@0: } michael@0: } michael@0: michael@0: void imgRequestProxy::BlockOnload() michael@0: { michael@0: #ifdef PR_LOGGING michael@0: nsAutoCString name; michael@0: GetName(name); michael@0: LOG_FUNC_WITH_PARAM(GetImgLog(), "imgRequestProxy::BlockOnload", "name", name.get()); michael@0: #endif michael@0: michael@0: nsCOMPtr blocker = do_QueryInterface(mListener); michael@0: if (blocker) { michael@0: blocker->BlockOnload(this); michael@0: } michael@0: } michael@0: michael@0: void imgRequestProxy::UnblockOnload() michael@0: { michael@0: #ifdef PR_LOGGING michael@0: nsAutoCString name; michael@0: GetName(name); michael@0: LOG_FUNC_WITH_PARAM(GetImgLog(), "imgRequestProxy::UnblockOnload", "name", name.get()); michael@0: #endif michael@0: michael@0: nsCOMPtr blocker = do_QueryInterface(mListener); michael@0: if (blocker) { michael@0: blocker->UnblockOnload(this); michael@0: } michael@0: } michael@0: michael@0: void imgRequestProxy::NullOutListener() michael@0: { michael@0: // If we have animation consumers, then they don't matter anymore michael@0: if (mListener) michael@0: ClearAnimationConsumers(); michael@0: michael@0: if (mListenerIsStrongRef) { michael@0: // Releasing could do weird reentery stuff, so just play it super-safe michael@0: nsCOMPtr obs; michael@0: obs.swap(mListener); michael@0: mListenerIsStrongRef = false; michael@0: } else { michael@0: mListener = nullptr; michael@0: } michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: imgRequestProxy::GetStaticRequest(imgIRequest** aReturn) michael@0: { michael@0: imgRequestProxy *proxy; michael@0: nsresult result = GetStaticRequest(&proxy); michael@0: *aReturn = proxy; michael@0: return result; michael@0: } michael@0: michael@0: nsresult michael@0: imgRequestProxy::GetStaticRequest(imgRequestProxy** aReturn) michael@0: { michael@0: *aReturn = nullptr; michael@0: nsRefPtr image = GetImage(); michael@0: michael@0: bool animated; michael@0: if (!image || (NS_SUCCEEDED(image->GetAnimated(&animated)) && !animated)) { michael@0: // Early exit - we're not animated, so we don't have to do anything. michael@0: NS_ADDREF(*aReturn = this); michael@0: return NS_OK; michael@0: } michael@0: michael@0: // Check for errors in the image. Callers code rely on GetStaticRequest michael@0: // failing in this case, though with FrozenImage there's no technical reason michael@0: // for it anymore. michael@0: if (image->HasError()) { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: // We are animated. We need to create a frozen version of this image. michael@0: nsRefPtr frozenImage = ImageOps::Freeze(image); michael@0: michael@0: // Create a static imgRequestProxy with our new extracted frame. michael@0: nsCOMPtr currentPrincipal; michael@0: GetImagePrincipal(getter_AddRefs(currentPrincipal)); michael@0: nsRefPtr req = new imgRequestProxyStatic(frozenImage, michael@0: currentPrincipal); michael@0: req->Init(nullptr, nullptr, mURI, nullptr); michael@0: michael@0: NS_ADDREF(*aReturn = req); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: void imgRequestProxy::NotifyListener() michael@0: { michael@0: // It would be nice to notify the observer directly in the status tracker michael@0: // instead of through the proxy, but there are several places we do extra michael@0: // processing when we receive notifications (like OnStopRequest()), and we michael@0: // need to check mCanceled everywhere too. michael@0: michael@0: nsRefPtr statusTracker = GetStatusTracker(); michael@0: if (GetOwner()) { michael@0: // Send the notifications to our listener asynchronously. michael@0: statusTracker->Notify(this); michael@0: } else { michael@0: // We don't have an imgRequest, so we can only notify the clone of our michael@0: // current state, but we still have to do that asynchronously. michael@0: NS_ABORT_IF_FALSE(HasImage(), michael@0: "if we have no imgRequest, we should have an Image"); michael@0: statusTracker->NotifyCurrentState(this); michael@0: } michael@0: } michael@0: michael@0: void imgRequestProxy::SyncNotifyListener() michael@0: { michael@0: // It would be nice to notify the observer directly in the status tracker michael@0: // instead of through the proxy, but there are several places we do extra michael@0: // processing when we receive notifications (like OnStopRequest()), and we michael@0: // need to check mCanceled everywhere too. michael@0: michael@0: nsRefPtr statusTracker = GetStatusTracker(); michael@0: statusTracker->SyncNotify(this); michael@0: } michael@0: michael@0: void michael@0: imgRequestProxy::SetHasImage() michael@0: { michael@0: nsRefPtr statusTracker = GetStatusTracker(); michael@0: MOZ_ASSERT(statusTracker); michael@0: nsRefPtr image = statusTracker->GetImage(); michael@0: MOZ_ASSERT(image); michael@0: michael@0: // Force any private status related to the owner to reflect michael@0: // the presence of an image; michael@0: mBehaviour->SetOwner(mBehaviour->GetOwner()); michael@0: michael@0: // Apply any locks we have michael@0: for (uint32_t i = 0; i < mLockCount; ++i) michael@0: image->LockImage(); michael@0: michael@0: // Apply any animation consumers we have michael@0: for (uint32_t i = 0; i < mAnimationConsumers; i++) michael@0: image->IncrementAnimationConsumers(); michael@0: } michael@0: michael@0: already_AddRefed michael@0: imgRequestProxy::GetStatusTracker() const michael@0: { michael@0: return mBehaviour->GetStatusTracker(); michael@0: } michael@0: michael@0: already_AddRefed michael@0: imgRequestProxy::GetImage() const michael@0: { michael@0: return mBehaviour->GetImage(); michael@0: } michael@0: michael@0: bool michael@0: RequestBehaviour::HasImage() const michael@0: { michael@0: if (!mOwnerHasImage) michael@0: return false; michael@0: nsRefPtr statusTracker = GetStatusTracker(); michael@0: return statusTracker ? statusTracker->HasImage() : false; michael@0: } michael@0: michael@0: bool michael@0: imgRequestProxy::HasImage() const michael@0: { michael@0: return mBehaviour->HasImage(); michael@0: } michael@0: michael@0: imgRequest* michael@0: imgRequestProxy::GetOwner() const michael@0: { michael@0: return mBehaviour->GetOwner(); michael@0: } michael@0: michael@0: ////////////////// imgRequestProxyStatic methods michael@0: michael@0: class StaticBehaviour : public ProxyBehaviour michael@0: { michael@0: public: michael@0: StaticBehaviour(mozilla::image::Image* aImage) : mImage(aImage) {} michael@0: michael@0: virtual already_AddRefed michael@0: GetImage() const MOZ_OVERRIDE { michael@0: nsRefPtr image = mImage; michael@0: return image.forget(); michael@0: } michael@0: michael@0: virtual bool HasImage() const MOZ_OVERRIDE { michael@0: return mImage; michael@0: } michael@0: michael@0: virtual already_AddRefed GetStatusTracker() const MOZ_OVERRIDE { michael@0: return mImage->GetStatusTracker(); michael@0: } michael@0: michael@0: virtual imgRequest* GetOwner() const MOZ_OVERRIDE { michael@0: return nullptr; michael@0: } michael@0: michael@0: virtual void SetOwner(imgRequest* aOwner) MOZ_OVERRIDE { michael@0: MOZ_ASSERT(!aOwner, "We shouldn't be giving static requests a non-null owner."); michael@0: } michael@0: michael@0: private: michael@0: // Our image. We have to hold a strong reference here, because that's normally michael@0: // the job of the underlying request. michael@0: nsRefPtr mImage; michael@0: }; michael@0: michael@0: imgRequestProxyStatic::imgRequestProxyStatic(mozilla::image::Image* aImage, michael@0: nsIPrincipal* aPrincipal) michael@0: : mPrincipal(aPrincipal) michael@0: { michael@0: mBehaviour = new StaticBehaviour(aImage); michael@0: } michael@0: michael@0: NS_IMETHODIMP imgRequestProxyStatic::GetImagePrincipal(nsIPrincipal **aPrincipal) michael@0: { michael@0: if (!mPrincipal) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: NS_ADDREF(*aPrincipal = mPrincipal); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: imgRequestProxyStatic::Clone(imgINotificationObserver* aObserver, michael@0: imgRequestProxy** aClone) michael@0: { michael@0: return PerformClone(aObserver, NewStaticProxy, aClone); michael@0: }