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 "imgStatusTracker.h" michael@0: michael@0: #include "imgIContainer.h" michael@0: #include "imgRequestProxy.h" michael@0: #include "imgDecoderObserver.h" michael@0: #include "Image.h" michael@0: #include "ImageLogging.h" michael@0: #include "nsNetUtil.h" michael@0: #include "nsIObserverService.h" michael@0: michael@0: #include "mozilla/Assertions.h" michael@0: #include "mozilla/Services.h" michael@0: michael@0: using namespace mozilla::image; michael@0: using mozilla::WeakPtr; michael@0: michael@0: class imgStatusTrackerObserver : public imgDecoderObserver michael@0: { michael@0: public: michael@0: imgStatusTrackerObserver(imgStatusTracker* aTracker) michael@0: : mTracker(aTracker->asWeakPtr()) michael@0: { michael@0: MOZ_ASSERT(aTracker); michael@0: } michael@0: michael@0: void SetTracker(imgStatusTracker* aTracker) michael@0: { michael@0: MOZ_ASSERT(aTracker); michael@0: mTracker = aTracker->asWeakPtr(); michael@0: } michael@0: michael@0: /** imgDecoderObserver methods **/ michael@0: michael@0: virtual void OnStartDecode() MOZ_OVERRIDE michael@0: { michael@0: LOG_SCOPE(GetImgLog(), "imgStatusTrackerObserver::OnStartDecode"); michael@0: nsRefPtr tracker = mTracker.get(); michael@0: if (!tracker) { return; } michael@0: tracker->RecordStartDecode(); michael@0: if (!tracker->IsMultipart()) { michael@0: tracker->RecordBlockOnload(); michael@0: } michael@0: } michael@0: michael@0: virtual void OnStartRequest() MOZ_OVERRIDE michael@0: { michael@0: NS_NOTREACHED("imgStatusTrackerObserver(imgDecoderObserver)::OnStartRequest"); michael@0: } michael@0: michael@0: virtual void OnStartContainer() MOZ_OVERRIDE michael@0: { michael@0: LOG_SCOPE(GetImgLog(), "imgStatusTrackerObserver::OnStartContainer"); michael@0: nsRefPtr tracker = mTracker.get(); michael@0: if (!tracker) { return; } michael@0: nsRefPtr image = tracker->GetImage();; michael@0: tracker->RecordStartContainer(image); michael@0: } michael@0: michael@0: virtual void OnStartFrame() MOZ_OVERRIDE michael@0: { michael@0: LOG_SCOPE(GetImgLog(), "imgStatusTrackerObserver::OnStartFrame"); michael@0: nsRefPtr tracker = mTracker.get(); michael@0: if (!tracker) { return; } michael@0: tracker->RecordStartFrame(); michael@0: } michael@0: michael@0: virtual void FrameChanged(const nsIntRect* dirtyRect) MOZ_OVERRIDE michael@0: { michael@0: LOG_SCOPE(GetImgLog(), "imgStatusTrackerObserver::FrameChanged"); michael@0: nsRefPtr tracker = mTracker.get(); michael@0: if (!tracker) { return; } michael@0: tracker->RecordFrameChanged(dirtyRect); michael@0: } michael@0: michael@0: virtual void OnStopFrame() MOZ_OVERRIDE michael@0: { michael@0: LOG_SCOPE(GetImgLog(), "imgStatusTrackerObserver::OnStopFrame"); michael@0: nsRefPtr tracker = mTracker.get(); michael@0: if (!tracker) { return; } michael@0: tracker->RecordStopFrame(); michael@0: tracker->RecordUnblockOnload(); michael@0: } michael@0: michael@0: virtual void OnStopDecode(nsresult aStatus) MOZ_OVERRIDE michael@0: { michael@0: LOG_SCOPE(GetImgLog(), "imgStatusTrackerObserver::OnStopDecode"); michael@0: nsRefPtr tracker = mTracker.get(); michael@0: if (!tracker) { return; } michael@0: tracker->RecordStopDecode(aStatus); michael@0: michael@0: // This is really hacky. We need to handle the case where we start decoding, michael@0: // block onload, but then hit an error before we get to our first frame. michael@0: tracker->RecordUnblockOnload(); michael@0: } michael@0: michael@0: virtual void OnStopRequest(bool aLastPart, nsresult aStatus) MOZ_OVERRIDE michael@0: { michael@0: LOG_SCOPE(GetImgLog(), "imgStatusTrackerObserver::OnStopRequest"); michael@0: nsRefPtr tracker = mTracker.get(); michael@0: if (!tracker) { return; } michael@0: tracker->RecordStopRequest(aLastPart, aStatus); michael@0: } michael@0: michael@0: virtual void OnDiscard() MOZ_OVERRIDE michael@0: { michael@0: LOG_SCOPE(GetImgLog(), "imgStatusTrackerObserver::OnDiscard"); michael@0: nsRefPtr tracker = mTracker.get(); michael@0: if (!tracker) { return; } michael@0: tracker->RecordDiscard(); michael@0: } michael@0: michael@0: virtual void OnUnlockedDraw() MOZ_OVERRIDE michael@0: { michael@0: LOG_SCOPE(GetImgLog(), "imgStatusTrackerObserver::OnUnlockedDraw"); michael@0: nsRefPtr tracker = mTracker.get(); michael@0: if (!tracker) { return; } michael@0: NS_ABORT_IF_FALSE(tracker->HasImage(), michael@0: "OnUnlockedDraw callback before we've created our image"); michael@0: tracker->RecordUnlockedDraw(); michael@0: } michael@0: michael@0: virtual void OnImageIsAnimated() MOZ_OVERRIDE michael@0: { michael@0: LOG_SCOPE(GetImgLog(), "imgStatusTrackerObserver::OnImageIsAnimated"); michael@0: nsRefPtr tracker = mTracker.get(); michael@0: if (!tracker) { return; } michael@0: tracker->RecordImageIsAnimated(); michael@0: } michael@0: michael@0: virtual void OnError() MOZ_OVERRIDE michael@0: { michael@0: LOG_SCOPE(GetImgLog(), "imgStatusTrackerObserver::OnError"); michael@0: nsRefPtr tracker = mTracker.get(); michael@0: if (!tracker) { return; } michael@0: tracker->RecordError(); michael@0: } michael@0: michael@0: protected: michael@0: virtual ~imgStatusTrackerObserver() {} michael@0: michael@0: private: michael@0: WeakPtr mTracker; michael@0: }; michael@0: michael@0: // imgStatusTracker methods michael@0: michael@0: imgStatusTracker::imgStatusTracker(Image* aImage) michael@0: : mImage(aImage), michael@0: mState(0), michael@0: mImageStatus(imgIRequest::STATUS_NONE), michael@0: mIsMultipart(false), michael@0: mHadLastPart(false), michael@0: mHasBeenDecoded(false) michael@0: { michael@0: mTrackerObserver = new imgStatusTrackerObserver(this); michael@0: } michael@0: michael@0: // Private, used only by CloneForRecording. michael@0: imgStatusTracker::imgStatusTracker(const imgStatusTracker& aOther) michael@0: : mImage(aOther.mImage), michael@0: mState(aOther.mState), michael@0: mImageStatus(aOther.mImageStatus), michael@0: mIsMultipart(aOther.mIsMultipart), michael@0: mHadLastPart(aOther.mHadLastPart), michael@0: mHasBeenDecoded(aOther.mHasBeenDecoded) michael@0: // Note: we explicitly don't copy several fields: michael@0: // - mRequestRunnable, because it won't be nulled out when the michael@0: // mRequestRunnable's Run function eventually gets called. michael@0: // - mProperties, because we don't need it and it'd just point at the same michael@0: // object michael@0: // - mConsumers, because we don't need to talk to consumers michael@0: // - mInvalidRect, because the point of it is to be fired off and reset michael@0: { michael@0: mTrackerObserver = new imgStatusTrackerObserver(this); michael@0: } michael@0: michael@0: imgStatusTracker::~imgStatusTracker() michael@0: {} michael@0: michael@0: imgStatusTrackerInit::imgStatusTrackerInit(mozilla::image::Image* aImage, michael@0: imgStatusTracker* aTracker) michael@0: { michael@0: MOZ_ASSERT(aImage); michael@0: michael@0: if (aTracker) { michael@0: mTracker = aTracker; michael@0: mTracker->SetImage(aImage); michael@0: } else { michael@0: mTracker = new imgStatusTracker(aImage); michael@0: } michael@0: aImage->SetStatusTracker(mTracker); michael@0: MOZ_ASSERT(mTracker); michael@0: } michael@0: michael@0: imgStatusTrackerInit::~imgStatusTrackerInit() michael@0: { michael@0: mTracker->ResetImage(); michael@0: } michael@0: michael@0: void michael@0: imgStatusTracker::SetImage(Image* aImage) michael@0: { michael@0: NS_ABORT_IF_FALSE(aImage, "Setting null image"); michael@0: NS_ABORT_IF_FALSE(!mImage, "Setting image when we already have one"); michael@0: mImage = aImage; michael@0: } michael@0: michael@0: void michael@0: imgStatusTracker::ResetImage() michael@0: { michael@0: NS_ABORT_IF_FALSE(mImage, "Resetting image when it's already null!"); michael@0: mImage = nullptr; michael@0: } michael@0: michael@0: bool michael@0: imgStatusTracker::IsLoading() const michael@0: { michael@0: // Checking for whether OnStopRequest has fired allows us to say we're michael@0: // loading before OnStartRequest gets called, letting the request properly michael@0: // get removed from the cache in certain cases. michael@0: return !(mState & stateRequestStopped); michael@0: } michael@0: michael@0: uint32_t michael@0: imgStatusTracker::GetImageStatus() const michael@0: { michael@0: return mImageStatus; michael@0: } michael@0: michael@0: // A helper class to allow us to call SyncNotify asynchronously. michael@0: class imgRequestNotifyRunnable : public nsRunnable michael@0: { michael@0: public: michael@0: imgRequestNotifyRunnable(imgStatusTracker* aTracker, michael@0: imgRequestProxy* aRequestProxy) michael@0: : mTracker(aTracker) michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread(), "Should be created on the main thread"); michael@0: MOZ_ASSERT(aRequestProxy, "aRequestProxy should not be null"); michael@0: MOZ_ASSERT(aTracker, "aTracker should not be null"); michael@0: mProxies.AppendElement(aRequestProxy); michael@0: } michael@0: michael@0: NS_IMETHOD Run() michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread(), "Should be running on the main thread"); michael@0: MOZ_ASSERT(mTracker, "mTracker should not be null"); michael@0: for (uint32_t i = 0; i < mProxies.Length(); ++i) { michael@0: mProxies[i]->SetNotificationsDeferred(false); michael@0: mTracker->SyncNotify(mProxies[i]); michael@0: } michael@0: michael@0: mTracker->mRequestRunnable = nullptr; michael@0: return NS_OK; michael@0: } michael@0: michael@0: void AddProxy(imgRequestProxy* aRequestProxy) michael@0: { michael@0: mProxies.AppendElement(aRequestProxy); michael@0: } michael@0: michael@0: void RemoveProxy(imgRequestProxy* aRequestProxy) michael@0: { michael@0: mProxies.RemoveElement(aRequestProxy); michael@0: } michael@0: michael@0: private: michael@0: friend class imgStatusTracker; michael@0: michael@0: nsRefPtr mTracker; michael@0: nsTArray< nsRefPtr > mProxies; michael@0: }; michael@0: michael@0: void michael@0: imgStatusTracker::Notify(imgRequestProxy* proxy) michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread(), "imgRequestProxy is not threadsafe"); michael@0: #ifdef PR_LOGGING michael@0: if (mImage && mImage->GetURI()) { michael@0: nsRefPtr uri(mImage->GetURI()); michael@0: nsAutoCString spec; michael@0: uri->GetSpec(spec); michael@0: LOG_FUNC_WITH_PARAM(GetImgLog(), "imgStatusTracker::Notify async", "uri", spec.get()); michael@0: } else { michael@0: LOG_FUNC_WITH_PARAM(GetImgLog(), "imgStatusTracker::Notify async", "uri", ""); michael@0: } michael@0: #endif michael@0: michael@0: proxy->SetNotificationsDeferred(true); michael@0: michael@0: // If we have an existing runnable that we can use, we just append this proxy michael@0: // to its list of proxies to be notified. This ensures we don't unnecessarily michael@0: // delay onload. michael@0: imgRequestNotifyRunnable* runnable = static_cast(mRequestRunnable.get()); michael@0: if (runnable) { michael@0: runnable->AddProxy(proxy); michael@0: } else { michael@0: mRequestRunnable = new imgRequestNotifyRunnable(this, proxy); michael@0: NS_DispatchToCurrentThread(mRequestRunnable); michael@0: } michael@0: } michael@0: michael@0: // A helper class to allow us to call SyncNotify asynchronously for a given, michael@0: // fixed, state. michael@0: class imgStatusNotifyRunnable : public nsRunnable michael@0: { michael@0: public: michael@0: imgStatusNotifyRunnable(imgStatusTracker* statusTracker, michael@0: imgRequestProxy* requestproxy) michael@0: : mStatusTracker(statusTracker), mProxy(requestproxy) michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread(), "Should be created on the main thread"); michael@0: MOZ_ASSERT(requestproxy, "requestproxy cannot be null"); michael@0: MOZ_ASSERT(statusTracker, "status should not be null"); michael@0: mImage = statusTracker->GetImage(); michael@0: } michael@0: michael@0: NS_IMETHOD Run() michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread(), "Should be running on the main thread"); michael@0: mProxy->SetNotificationsDeferred(false); michael@0: michael@0: mStatusTracker->SyncNotify(mProxy); michael@0: return NS_OK; michael@0: } michael@0: michael@0: private: michael@0: nsRefPtr mStatusTracker; michael@0: // We have to hold on to a reference to the tracker's image, just in case michael@0: // it goes away while we're in the event queue. michael@0: nsRefPtr mImage; michael@0: nsRefPtr mProxy; michael@0: }; michael@0: michael@0: void michael@0: imgStatusTracker::NotifyCurrentState(imgRequestProxy* proxy) michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread(), "imgRequestProxy is not threadsafe"); michael@0: #ifdef PR_LOGGING michael@0: nsRefPtr uri; michael@0: proxy->GetURI(getter_AddRefs(uri)); michael@0: nsAutoCString spec; michael@0: uri->GetSpec(spec); michael@0: LOG_FUNC_WITH_PARAM(GetImgLog(), "imgStatusTracker::NotifyCurrentState", "uri", spec.get()); michael@0: #endif michael@0: michael@0: proxy->SetNotificationsDeferred(true); michael@0: michael@0: // We don't keep track of michael@0: nsCOMPtr ev = new imgStatusNotifyRunnable(this, proxy); michael@0: NS_DispatchToCurrentThread(ev); michael@0: } michael@0: michael@0: #define NOTIFY_IMAGE_OBSERVERS(func) \ michael@0: do { \ michael@0: ProxyArray::ForwardIterator iter(proxies); \ michael@0: while (iter.HasMore()) { \ michael@0: nsRefPtr proxy = iter.GetNext().get(); \ michael@0: if (proxy && !proxy->NotificationsDeferred()) { \ michael@0: proxy->func; \ michael@0: } \ michael@0: } \ michael@0: } while (false); michael@0: michael@0: /* static */ void michael@0: imgStatusTracker::SyncNotifyState(ProxyArray& proxies, michael@0: bool hasImage, uint32_t state, michael@0: nsIntRect& dirtyRect, bool hadLastPart) michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: // OnStartRequest michael@0: if (state & stateRequestStarted) michael@0: NOTIFY_IMAGE_OBSERVERS(OnStartRequest()); michael@0: michael@0: // OnStartContainer michael@0: if (state & stateHasSize) michael@0: NOTIFY_IMAGE_OBSERVERS(OnStartContainer()); michael@0: michael@0: // OnStartDecode michael@0: if (state & stateDecodeStarted) michael@0: NOTIFY_IMAGE_OBSERVERS(OnStartDecode()); michael@0: michael@0: // BlockOnload michael@0: if (state & stateBlockingOnload) michael@0: NOTIFY_IMAGE_OBSERVERS(BlockOnload()); michael@0: michael@0: if (hasImage) { michael@0: // OnFrameUpdate michael@0: // If there's any content in this frame at all (always true for michael@0: // vector images, true for raster images that have decoded at michael@0: // least one frame) then send OnFrameUpdate. michael@0: if (!dirtyRect.IsEmpty()) michael@0: NOTIFY_IMAGE_OBSERVERS(OnFrameUpdate(&dirtyRect)); michael@0: michael@0: if (state & stateFrameStopped) michael@0: NOTIFY_IMAGE_OBSERVERS(OnStopFrame()); michael@0: michael@0: // OnImageIsAnimated michael@0: if (state & stateImageIsAnimated) michael@0: NOTIFY_IMAGE_OBSERVERS(OnImageIsAnimated()); michael@0: } michael@0: michael@0: if (state & stateDecodeStopped) { michael@0: NS_ABORT_IF_FALSE(hasImage, "stopped decoding without ever having an image?"); michael@0: NOTIFY_IMAGE_OBSERVERS(OnStopDecode()); michael@0: } michael@0: michael@0: if (state & stateRequestStopped) { michael@0: NOTIFY_IMAGE_OBSERVERS(OnStopRequest(hadLastPart)); michael@0: } michael@0: } michael@0: michael@0: ImageStatusDiff michael@0: imgStatusTracker::Difference(imgStatusTracker* aOther) const michael@0: { michael@0: MOZ_ASSERT(aOther, "aOther cannot be null"); michael@0: ImageStatusDiff diff; michael@0: diff.diffState = ~mState & aOther->mState & ~stateRequestStarted; michael@0: diff.diffImageStatus = ~mImageStatus & aOther->mImageStatus; michael@0: diff.unblockedOnload = mState & stateBlockingOnload && !(aOther->mState & stateBlockingOnload); michael@0: diff.unsetDecodeStarted = mImageStatus & imgIRequest::STATUS_DECODE_STARTED michael@0: && !(aOther->mImageStatus & imgIRequest::STATUS_DECODE_STARTED); michael@0: diff.foundError = (mImageStatus != imgIRequest::STATUS_ERROR) michael@0: && (aOther->mImageStatus == imgIRequest::STATUS_ERROR); michael@0: michael@0: MOZ_ASSERT(!mIsMultipart || aOther->mIsMultipart, "mIsMultipart should be monotonic"); michael@0: diff.foundIsMultipart = !mIsMultipart && aOther->mIsMultipart; michael@0: diff.foundLastPart = !mHadLastPart && aOther->mHadLastPart; michael@0: michael@0: diff.gotDecoded = !mHasBeenDecoded && aOther->mHasBeenDecoded; michael@0: michael@0: // Only record partial invalidations if we haven't been decoded before. michael@0: // When images are re-decoded after discarding, we don't want to display michael@0: // partially decoded versions to the user. michael@0: const uint32_t combinedStatus = mImageStatus | aOther->mImageStatus; michael@0: const bool doInvalidations = !(mHasBeenDecoded || aOther->mHasBeenDecoded) michael@0: || combinedStatus & imgIRequest::STATUS_ERROR michael@0: || combinedStatus & imgIRequest::STATUS_DECODE_COMPLETE; michael@0: michael@0: // Record and reset the invalid rectangle. michael@0: // XXX(seth): We shouldn't be resetting anything here; see bug 910441. michael@0: if (doInvalidations) { michael@0: diff.invalidRect = aOther->mInvalidRect; michael@0: aOther->mInvalidRect.SetEmpty(); michael@0: } michael@0: michael@0: return diff; michael@0: } michael@0: michael@0: ImageStatusDiff michael@0: imgStatusTracker::DecodeStateAsDifference() const michael@0: { michael@0: ImageStatusDiff diff; michael@0: diff.diffState = mState & ~stateRequestStarted; michael@0: michael@0: // All other ImageStatusDiff fields are intentionally left at their default michael@0: // values; we only want to notify decode state changes. michael@0: michael@0: return diff; michael@0: } michael@0: michael@0: void michael@0: imgStatusTracker::ApplyDifference(const ImageStatusDiff& aDiff) michael@0: { michael@0: LOG_SCOPE(GetImgLog(), "imgStatusTracker::ApplyDifference"); michael@0: michael@0: // We must not modify or notify for the start-load state, which happens from Necko callbacks. michael@0: uint32_t loadState = mState & stateRequestStarted; michael@0: michael@0: // Synchronize our state. michael@0: mState |= aDiff.diffState | loadState; michael@0: if (aDiff.unblockedOnload) michael@0: mState &= ~stateBlockingOnload; michael@0: michael@0: mIsMultipart = mIsMultipart || aDiff.foundIsMultipart; michael@0: mHadLastPart = mHadLastPart || aDiff.foundLastPart; michael@0: mHasBeenDecoded = mHasBeenDecoded || aDiff.gotDecoded; michael@0: michael@0: // Update the image status. There are some subtle points which are handled below. michael@0: mImageStatus |= aDiff.diffImageStatus; michael@0: michael@0: // Unset bits which can get unset as part of the decoding process. michael@0: if (aDiff.unsetDecodeStarted) michael@0: mImageStatus &= ~imgIRequest::STATUS_DECODE_STARTED; michael@0: michael@0: // The error state is sticky and overrides all other bits. michael@0: if (mImageStatus & imgIRequest::STATUS_ERROR) michael@0: mImageStatus = imgIRequest::STATUS_ERROR; michael@0: } michael@0: michael@0: void michael@0: imgStatusTracker::SyncNotifyDifference(const ImageStatusDiff& diff) michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread(), "Use mConsumers on main thread only"); michael@0: LOG_SCOPE(GetImgLog(), "imgStatusTracker::SyncNotifyDifference"); michael@0: michael@0: nsIntRect invalidRect = mInvalidRect.Union(diff.invalidRect); michael@0: michael@0: SyncNotifyState(mConsumers, !!mImage, diff.diffState, invalidRect, mHadLastPart); michael@0: michael@0: mInvalidRect.SetEmpty(); michael@0: michael@0: if (diff.unblockedOnload) { michael@0: ProxyArray::ForwardIterator iter(mConsumers); michael@0: while (iter.HasMore()) { michael@0: // Hold on to a reference to this proxy, since notifying the state can michael@0: // cause it to disappear. michael@0: nsRefPtr proxy = iter.GetNext().get(); michael@0: michael@0: if (proxy && !proxy->NotificationsDeferred()) { michael@0: SendUnblockOnload(proxy); michael@0: } michael@0: } michael@0: } michael@0: michael@0: if (diff.foundError) { michael@0: FireFailureNotification(); michael@0: } michael@0: } michael@0: michael@0: already_AddRefed michael@0: imgStatusTracker::CloneForRecording() michael@0: { michael@0: // Grab a ref to this to ensure it isn't deleted. michael@0: nsRefPtr thisStatusTracker = this; michael@0: nsRefPtr clone = new imgStatusTracker(*thisStatusTracker); michael@0: return clone.forget(); michael@0: } michael@0: michael@0: void michael@0: imgStatusTracker::SyncNotify(imgRequestProxy* proxy) michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread(), "imgRequestProxy is not threadsafe"); michael@0: #ifdef PR_LOGGING michael@0: nsRefPtr uri; michael@0: proxy->GetURI(getter_AddRefs(uri)); michael@0: nsAutoCString spec; michael@0: uri->GetSpec(spec); michael@0: LOG_SCOPE_WITH_PARAM(GetImgLog(), "imgStatusTracker::SyncNotify", "uri", spec.get()); michael@0: #endif michael@0: michael@0: nsIntRect r; michael@0: if (mImage) { michael@0: // XXX - Should only send partial rects here, but that needs to michael@0: // wait until we fix up the observer interface michael@0: r = mImage->FrameRect(imgIContainer::FRAME_CURRENT); michael@0: } michael@0: michael@0: ProxyArray array; michael@0: array.AppendElement(proxy->asWeakPtr()); michael@0: SyncNotifyState(array, !!mImage, mState, r, mHadLastPart); michael@0: } michael@0: michael@0: void michael@0: imgStatusTracker::EmulateRequestFinished(imgRequestProxy* aProxy, michael@0: nsresult aStatus) michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread(), michael@0: "SyncNotifyState and mConsumers are not threadsafe"); michael@0: nsCOMPtr kungFuDeathGrip(aProxy); michael@0: michael@0: // In certain cases the request might not have started yet. michael@0: // We still need to fulfill the contract. michael@0: if (!(mState & stateRequestStarted)) { michael@0: aProxy->OnStartRequest(); michael@0: } michael@0: michael@0: if (mState & stateBlockingOnload) { michael@0: aProxy->UnblockOnload(); michael@0: } michael@0: michael@0: if (!(mState & stateRequestStopped)) { michael@0: aProxy->OnStopRequest(true); michael@0: } michael@0: } michael@0: michael@0: void michael@0: imgStatusTracker::AddConsumer(imgRequestProxy* aConsumer) michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: mConsumers.AppendElementUnlessExists(aConsumer->asWeakPtr()); michael@0: } michael@0: michael@0: // XXX - The last argument should go away. michael@0: bool michael@0: imgStatusTracker::RemoveConsumer(imgRequestProxy* aConsumer, nsresult aStatus) michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: // Remove the proxy from the list. michael@0: bool removed = mConsumers.RemoveElement(aConsumer); michael@0: michael@0: // Consumers can get confused if they don't get all the proper teardown michael@0: // notifications. Part ways on good terms. michael@0: if (removed && !aConsumer->NotificationsDeferred()) { michael@0: EmulateRequestFinished(aConsumer, aStatus); michael@0: } michael@0: michael@0: // Make sure we don't give callbacks to a consumer that isn't interested in michael@0: // them any more. michael@0: imgRequestNotifyRunnable* runnable = static_cast(mRequestRunnable.get()); michael@0: if (aConsumer->NotificationsDeferred() && runnable) { michael@0: runnable->RemoveProxy(aConsumer); michael@0: aConsumer->SetNotificationsDeferred(false); michael@0: } michael@0: michael@0: return removed; michael@0: } michael@0: michael@0: bool michael@0: imgStatusTracker::FirstConsumerIs(imgRequestProxy* aConsumer) michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread(), "Use mConsumers on main thread only"); michael@0: ProxyArray::ForwardIterator iter(mConsumers); michael@0: while (iter.HasMore()) { michael@0: nsRefPtr proxy = iter.GetNext().get(); michael@0: if (proxy) { michael@0: return proxy.get() == aConsumer; michael@0: } michael@0: } michael@0: return false; michael@0: } michael@0: michael@0: void michael@0: imgStatusTracker::RecordCancel() michael@0: { michael@0: if (!(mImageStatus & imgIRequest::STATUS_LOAD_PARTIAL)) michael@0: mImageStatus = imgIRequest::STATUS_ERROR; michael@0: } michael@0: michael@0: void michael@0: imgStatusTracker::RecordLoaded() michael@0: { michael@0: NS_ABORT_IF_FALSE(mImage, "RecordLoaded called before we have an Image"); michael@0: mState |= stateRequestStarted | stateHasSize | stateRequestStopped; michael@0: mImageStatus |= imgIRequest::STATUS_SIZE_AVAILABLE | imgIRequest::STATUS_LOAD_COMPLETE; michael@0: mHadLastPart = true; michael@0: } michael@0: michael@0: void michael@0: imgStatusTracker::RecordDecoded() michael@0: { michael@0: NS_ABORT_IF_FALSE(mImage, "RecordDecoded called before we have an Image"); michael@0: mState |= stateDecodeStarted | stateDecodeStopped | stateFrameStopped; michael@0: mImageStatus |= imgIRequest::STATUS_FRAME_COMPLETE | imgIRequest::STATUS_DECODE_COMPLETE; michael@0: mImageStatus &= ~imgIRequest::STATUS_DECODE_STARTED; michael@0: } michael@0: michael@0: void michael@0: imgStatusTracker::RecordStartDecode() michael@0: { michael@0: NS_ABORT_IF_FALSE(mImage, "RecordStartDecode without an Image"); michael@0: mState |= stateDecodeStarted; michael@0: mImageStatus |= imgIRequest::STATUS_DECODE_STARTED; michael@0: } michael@0: michael@0: void michael@0: imgStatusTracker::SendStartDecode(imgRequestProxy* aProxy) michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: if (!aProxy->NotificationsDeferred()) michael@0: aProxy->OnStartDecode(); michael@0: } michael@0: michael@0: void michael@0: imgStatusTracker::RecordStartContainer(imgIContainer* aContainer) michael@0: { michael@0: NS_ABORT_IF_FALSE(mImage, michael@0: "RecordStartContainer called before we have an Image"); michael@0: NS_ABORT_IF_FALSE(mImage == aContainer, michael@0: "RecordStartContainer called with wrong Image"); michael@0: mState |= stateHasSize; michael@0: mImageStatus |= imgIRequest::STATUS_SIZE_AVAILABLE; michael@0: } michael@0: michael@0: void michael@0: imgStatusTracker::SendStartContainer(imgRequestProxy* aProxy) michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: if (!aProxy->NotificationsDeferred()) michael@0: aProxy->OnStartContainer(); michael@0: } michael@0: michael@0: void michael@0: imgStatusTracker::RecordStartFrame() michael@0: { michael@0: mInvalidRect.SetEmpty(); michael@0: } michael@0: michael@0: // No SendStartFrame since it's not observed below us. michael@0: michael@0: void michael@0: imgStatusTracker::RecordStopFrame() michael@0: { michael@0: NS_ABORT_IF_FALSE(mImage, "RecordStopFrame called before we have an Image"); michael@0: mState |= stateFrameStopped; michael@0: mImageStatus |= imgIRequest::STATUS_FRAME_COMPLETE; michael@0: } michael@0: michael@0: void michael@0: imgStatusTracker::SendStopFrame(imgRequestProxy* aProxy) michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: if (!aProxy->NotificationsDeferred()) michael@0: aProxy->OnStopFrame(); michael@0: } michael@0: michael@0: void michael@0: imgStatusTracker::RecordStopDecode(nsresult aStatus) michael@0: { michael@0: NS_ABORT_IF_FALSE(mImage, michael@0: "RecordStopDecode called before we have an Image"); michael@0: mState |= stateDecodeStopped; michael@0: michael@0: if (NS_SUCCEEDED(aStatus) && mImageStatus != imgIRequest::STATUS_ERROR) { michael@0: mImageStatus |= imgIRequest::STATUS_DECODE_COMPLETE; michael@0: mImageStatus &= ~imgIRequest::STATUS_DECODE_STARTED; michael@0: mHasBeenDecoded = true; michael@0: // If we weren't successful, clear all success status bits and set error. michael@0: } else { michael@0: mImageStatus = imgIRequest::STATUS_ERROR; michael@0: } michael@0: } michael@0: michael@0: void michael@0: imgStatusTracker::SendStopDecode(imgRequestProxy* aProxy, michael@0: nsresult aStatus) michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: if (!aProxy->NotificationsDeferred()) michael@0: aProxy->OnStopDecode(); michael@0: } michael@0: michael@0: void michael@0: imgStatusTracker::RecordDiscard() michael@0: { michael@0: NS_ABORT_IF_FALSE(mImage, michael@0: "RecordDiscard called before we have an Image"); michael@0: // Clear the state bits we no longer deserve. michael@0: uint32_t stateBitsToClear = stateDecodeStopped; michael@0: mState &= ~stateBitsToClear; michael@0: michael@0: // Clear the status bits we no longer deserve. michael@0: uint32_t statusBitsToClear = imgIRequest::STATUS_DECODE_STARTED | michael@0: imgIRequest::STATUS_FRAME_COMPLETE | michael@0: imgIRequest::STATUS_DECODE_COMPLETE; michael@0: mImageStatus &= ~statusBitsToClear; michael@0: } michael@0: michael@0: void michael@0: imgStatusTracker::SendDiscard(imgRequestProxy* aProxy) michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: if (!aProxy->NotificationsDeferred()) michael@0: aProxy->OnDiscard(); michael@0: } michael@0: michael@0: michael@0: void michael@0: imgStatusTracker::RecordUnlockedDraw() michael@0: { michael@0: NS_ABORT_IF_FALSE(mImage, michael@0: "RecordUnlockedDraw called before we have an Image"); michael@0: } michael@0: michael@0: void michael@0: imgStatusTracker::RecordImageIsAnimated() michael@0: { michael@0: NS_ABORT_IF_FALSE(mImage, michael@0: "RecordImageIsAnimated called before we have an Image"); michael@0: mState |= stateImageIsAnimated; michael@0: } michael@0: michael@0: void michael@0: imgStatusTracker::SendImageIsAnimated(imgRequestProxy* aProxy) michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: if (!aProxy->NotificationsDeferred()) michael@0: aProxy->OnImageIsAnimated(); michael@0: } michael@0: michael@0: void michael@0: imgStatusTracker::SendUnlockedDraw(imgRequestProxy* aProxy) michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: if (!aProxy->NotificationsDeferred()) michael@0: aProxy->OnUnlockedDraw(); michael@0: } michael@0: michael@0: void michael@0: imgStatusTracker::OnUnlockedDraw() michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: RecordUnlockedDraw(); michael@0: ProxyArray::ForwardIterator iter(mConsumers); michael@0: while (iter.HasMore()) { michael@0: nsRefPtr proxy = iter.GetNext().get(); michael@0: if (proxy) { michael@0: SendUnlockedDraw(proxy); michael@0: } michael@0: } michael@0: } michael@0: michael@0: void michael@0: imgStatusTracker::RecordFrameChanged(const nsIntRect* aDirtyRect) michael@0: { michael@0: NS_ABORT_IF_FALSE(mImage, michael@0: "RecordFrameChanged called before we have an Image"); michael@0: mInvalidRect = mInvalidRect.Union(*aDirtyRect); michael@0: } michael@0: michael@0: void michael@0: imgStatusTracker::SendFrameChanged(imgRequestProxy* aProxy, michael@0: const nsIntRect* aDirtyRect) michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: if (!aProxy->NotificationsDeferred()) michael@0: aProxy->OnFrameUpdate(aDirtyRect); michael@0: } michael@0: michael@0: /* non-virtual sort-of-nsIRequestObserver methods */ michael@0: void michael@0: imgStatusTracker::RecordStartRequest() michael@0: { michael@0: // We're starting a new load, so clear any status and state bits indicating michael@0: // load/decode michael@0: mImageStatus &= ~imgIRequest::STATUS_LOAD_PARTIAL; michael@0: mImageStatus &= ~imgIRequest::STATUS_LOAD_COMPLETE; michael@0: mImageStatus &= ~imgIRequest::STATUS_FRAME_COMPLETE; michael@0: mImageStatus &= ~imgIRequest::STATUS_DECODE_STARTED; michael@0: mImageStatus &= ~imgIRequest::STATUS_DECODE_COMPLETE; michael@0: mState &= ~stateRequestStarted; michael@0: mState &= ~stateDecodeStarted; michael@0: mState &= ~stateDecodeStopped; michael@0: mState &= ~stateRequestStopped; michael@0: mState &= ~stateBlockingOnload; michael@0: mState &= ~stateImageIsAnimated; michael@0: michael@0: mState |= stateRequestStarted; michael@0: } michael@0: michael@0: void michael@0: imgStatusTracker::SendStartRequest(imgRequestProxy* aProxy) michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: if (!aProxy->NotificationsDeferred()) michael@0: aProxy->OnStartRequest(); michael@0: } michael@0: michael@0: void michael@0: imgStatusTracker::OnStartRequest() michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: RecordStartRequest(); michael@0: ProxyArray::ForwardIterator iter(mConsumers); michael@0: while (iter.HasMore()) { michael@0: nsRefPtr proxy = iter.GetNext().get(); michael@0: if (proxy) { michael@0: SendStartRequest(proxy); michael@0: } michael@0: } michael@0: } michael@0: michael@0: void michael@0: imgStatusTracker::RecordStopRequest(bool aLastPart, michael@0: nsresult aStatus) michael@0: { michael@0: mHadLastPart = aLastPart; michael@0: mState |= stateRequestStopped; michael@0: michael@0: // If we were successful in loading, note that the image is complete. michael@0: if (NS_SUCCEEDED(aStatus) && mImageStatus != imgIRequest::STATUS_ERROR) michael@0: mImageStatus |= imgIRequest::STATUS_LOAD_COMPLETE; michael@0: else michael@0: mImageStatus = imgIRequest::STATUS_ERROR; michael@0: } michael@0: michael@0: void michael@0: imgStatusTracker::SendStopRequest(imgRequestProxy* aProxy, michael@0: bool aLastPart, michael@0: nsresult aStatus) michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: if (!aProxy->NotificationsDeferred()) { michael@0: aProxy->OnStopRequest(aLastPart); michael@0: } michael@0: } michael@0: michael@0: class OnStopRequestEvent : public nsRunnable michael@0: { michael@0: public: michael@0: OnStopRequestEvent(imgStatusTracker* aTracker, michael@0: bool aLastPart, michael@0: nsresult aStatus) michael@0: : mTracker(aTracker) michael@0: , mLastPart(aLastPart) michael@0: , mStatus(aStatus) michael@0: { michael@0: MOZ_ASSERT(!NS_IsMainThread(), "Should be created off the main thread"); michael@0: MOZ_ASSERT(aTracker, "aTracker should not be null"); michael@0: } michael@0: michael@0: NS_IMETHOD Run() michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread(), "Should be running on the main thread"); michael@0: MOZ_ASSERT(mTracker, "mTracker should not be null"); michael@0: mTracker->OnStopRequest(mLastPart, mStatus); michael@0: return NS_OK; michael@0: } michael@0: private: michael@0: nsRefPtr mTracker; michael@0: bool mLastPart; michael@0: nsresult mStatus; michael@0: }; michael@0: michael@0: void michael@0: imgStatusTracker::OnStopRequest(bool aLastPart, michael@0: nsresult aStatus) michael@0: { michael@0: if (!NS_IsMainThread()) { michael@0: NS_DispatchToMainThread( michael@0: new OnStopRequestEvent(this, aLastPart, aStatus)); michael@0: return; michael@0: } michael@0: bool preexistingError = mImageStatus == imgIRequest::STATUS_ERROR; michael@0: michael@0: RecordStopRequest(aLastPart, aStatus); michael@0: /* notify the kids */ michael@0: ProxyArray::ForwardIterator srIter(mConsumers); michael@0: while (srIter.HasMore()) { michael@0: nsRefPtr proxy = srIter.GetNext().get(); michael@0: if (proxy) { michael@0: SendStopRequest(proxy, aLastPart, aStatus); michael@0: } michael@0: } michael@0: michael@0: if (NS_FAILED(aStatus) && !preexistingError) { michael@0: FireFailureNotification(); michael@0: } michael@0: } michael@0: michael@0: void michael@0: imgStatusTracker::OnDiscard() michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: RecordDiscard(); michael@0: michael@0: /* notify the kids */ michael@0: ProxyArray::ForwardIterator iter(mConsumers); michael@0: while (iter.HasMore()) { michael@0: nsRefPtr proxy = iter.GetNext().get(); michael@0: if (proxy) { michael@0: SendDiscard(proxy); michael@0: } michael@0: } michael@0: } michael@0: michael@0: void michael@0: imgStatusTracker::FrameChanged(const nsIntRect* aDirtyRect) michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: RecordFrameChanged(aDirtyRect); michael@0: michael@0: /* notify the kids */ michael@0: ProxyArray::ForwardIterator iter(mConsumers); michael@0: while (iter.HasMore()) { michael@0: nsRefPtr proxy = iter.GetNext().get(); michael@0: if (proxy) { michael@0: SendFrameChanged(proxy, aDirtyRect); michael@0: } michael@0: } michael@0: } michael@0: michael@0: void michael@0: imgStatusTracker::OnStopFrame() michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: RecordStopFrame(); michael@0: michael@0: /* notify the kids */ michael@0: ProxyArray::ForwardIterator iter(mConsumers); michael@0: while (iter.HasMore()) { michael@0: nsRefPtr proxy = iter.GetNext().get(); michael@0: if (proxy) { michael@0: SendStopFrame(proxy); michael@0: } michael@0: } michael@0: } michael@0: michael@0: void michael@0: imgStatusTracker::OnDataAvailable() michael@0: { michael@0: if (!NS_IsMainThread()) { michael@0: // Note: SetHasImage calls Image::Lock and Image::IncrementAnimationCounter michael@0: // so subsequent calls or dispatches which Unlock or Decrement~ should michael@0: // be issued after this to avoid race conditions. michael@0: NS_DispatchToMainThread( michael@0: NS_NewRunnableMethod(this, &imgStatusTracker::OnDataAvailable)); michael@0: return; michael@0: } michael@0: // Notify any imgRequestProxys that are observing us that we have an Image. michael@0: ProxyArray::ForwardIterator iter(mConsumers); michael@0: while (iter.HasMore()) { michael@0: nsRefPtr proxy = iter.GetNext().get(); michael@0: if (proxy) { michael@0: proxy->SetHasImage(); michael@0: } michael@0: } michael@0: } michael@0: michael@0: void michael@0: imgStatusTracker::RecordBlockOnload() michael@0: { michael@0: MOZ_ASSERT(!(mState & stateBlockingOnload)); michael@0: mState |= stateBlockingOnload; michael@0: } michael@0: michael@0: void michael@0: imgStatusTracker::SendBlockOnload(imgRequestProxy* aProxy) michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: if (!aProxy->NotificationsDeferred()) { michael@0: aProxy->BlockOnload(); michael@0: } michael@0: } michael@0: michael@0: void michael@0: imgStatusTracker::RecordUnblockOnload() michael@0: { michael@0: mState &= ~stateBlockingOnload; michael@0: } michael@0: michael@0: void michael@0: imgStatusTracker::SendUnblockOnload(imgRequestProxy* aProxy) michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: if (!aProxy->NotificationsDeferred()) { michael@0: aProxy->UnblockOnload(); michael@0: } michael@0: } michael@0: michael@0: void michael@0: imgStatusTracker::MaybeUnblockOnload() michael@0: { michael@0: if (!NS_IsMainThread()) { michael@0: NS_DispatchToMainThread( michael@0: NS_NewRunnableMethod(this, &imgStatusTracker::MaybeUnblockOnload)); michael@0: return; michael@0: } michael@0: if (!(mState & stateBlockingOnload)) { michael@0: return; michael@0: } michael@0: michael@0: RecordUnblockOnload(); michael@0: michael@0: ProxyArray::ForwardIterator iter(mConsumers); michael@0: while (iter.HasMore()) { michael@0: nsRefPtr proxy = iter.GetNext().get(); michael@0: if (proxy) { michael@0: SendUnblockOnload(proxy); michael@0: } michael@0: } michael@0: } michael@0: michael@0: void michael@0: imgStatusTracker::RecordError() michael@0: { michael@0: mImageStatus = imgIRequest::STATUS_ERROR; michael@0: } michael@0: michael@0: void michael@0: imgStatusTracker::FireFailureNotification() michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: michael@0: // Some kind of problem has happened with image decoding. michael@0: // Report the URI to net:failed-to-process-uri-conent observers. michael@0: if (mImage) { michael@0: // Should be on main thread, so ok to create a new nsIURI. michael@0: nsCOMPtr uri; michael@0: { michael@0: nsRefPtr threadsafeUriData = mImage->GetURI(); michael@0: uri = threadsafeUriData ? threadsafeUriData->ToIURI() : nullptr; michael@0: } michael@0: if (uri) { michael@0: nsCOMPtr os = mozilla::services::GetObserverService(); michael@0: if (os) { michael@0: os->NotifyObservers(uri, "net:failed-to-process-uri-content", nullptr); michael@0: } michael@0: } michael@0: } michael@0: }