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: #ifndef imgStatusTracker_h__ michael@0: #define imgStatusTracker_h__ michael@0: michael@0: class imgDecoderObserver; michael@0: class imgIContainer; michael@0: class imgStatusNotifyRunnable; michael@0: class imgRequestNotifyRunnable; michael@0: class imgStatusTrackerObserver; michael@0: class nsIRunnable; michael@0: michael@0: #include "mozilla/RefPtr.h" michael@0: #include "mozilla/WeakPtr.h" michael@0: #include "nsCOMPtr.h" michael@0: #include "nsTObserverArray.h" michael@0: #include "nsThreadUtils.h" michael@0: #include "nsRect.h" michael@0: #include "imgRequestProxy.h" michael@0: michael@0: namespace mozilla { michael@0: namespace image { michael@0: michael@0: class Image; michael@0: michael@0: struct ImageStatusDiff michael@0: { michael@0: ImageStatusDiff() michael@0: : invalidRect() michael@0: , diffState(0) michael@0: , diffImageStatus(0) michael@0: , unblockedOnload(false) michael@0: , unsetDecodeStarted(false) michael@0: , foundError(false) michael@0: , foundIsMultipart(false) michael@0: , foundLastPart(false) michael@0: , gotDecoded(false) michael@0: { } michael@0: michael@0: static ImageStatusDiff NoChange() { return ImageStatusDiff(); } michael@0: bool IsNoChange() const { return *this == NoChange(); } michael@0: michael@0: bool operator!=(const ImageStatusDiff& aOther) const { return !(*this == aOther); } michael@0: bool operator==(const ImageStatusDiff& aOther) const { michael@0: return aOther.invalidRect == invalidRect michael@0: && aOther.diffState == diffState michael@0: && aOther.diffImageStatus == diffImageStatus michael@0: && aOther.unblockedOnload == unblockedOnload michael@0: && aOther.unsetDecodeStarted == unsetDecodeStarted michael@0: && aOther.foundError == foundError michael@0: && aOther.foundIsMultipart == foundIsMultipart michael@0: && aOther.foundLastPart == foundLastPart michael@0: && aOther.gotDecoded == gotDecoded; michael@0: } michael@0: michael@0: void Combine(const ImageStatusDiff& aOther) { michael@0: invalidRect = invalidRect.Union(aOther.invalidRect); michael@0: diffState |= aOther.diffState; michael@0: diffImageStatus |= aOther.diffImageStatus; michael@0: unblockedOnload = unblockedOnload || aOther.unblockedOnload; michael@0: unsetDecodeStarted = unsetDecodeStarted || aOther.unsetDecodeStarted; michael@0: foundError = foundError || aOther.foundError; michael@0: foundIsMultipart = foundIsMultipart || aOther.foundIsMultipart; michael@0: foundLastPart = foundLastPart || aOther.foundLastPart; michael@0: gotDecoded = gotDecoded || aOther.gotDecoded; michael@0: } michael@0: michael@0: nsIntRect invalidRect; michael@0: uint32_t diffState; michael@0: uint32_t diffImageStatus; michael@0: bool unblockedOnload : 1; michael@0: bool unsetDecodeStarted : 1; michael@0: bool foundError : 1; michael@0: bool foundIsMultipart : 1; michael@0: bool foundLastPart : 1; michael@0: bool gotDecoded : 1; michael@0: }; michael@0: michael@0: enum { michael@0: stateRequestStarted = 1u << 0, michael@0: stateHasSize = 1u << 1, michael@0: stateDecodeStarted = 1u << 2, michael@0: stateDecodeStopped = 1u << 3, michael@0: stateFrameStopped = 1u << 4, michael@0: stateRequestStopped = 1u << 5, michael@0: stateBlockingOnload = 1u << 6, michael@0: stateImageIsAnimated = 1u << 7 michael@0: }; michael@0: michael@0: } // namespace image michael@0: } // namespace mozilla michael@0: michael@0: /* michael@0: * The image status tracker is a class that encapsulates all the loading and michael@0: * decoding status about an Image, and makes it possible to send notifications michael@0: * to imgRequestProxys, both synchronously (i.e., the status now) and michael@0: * asynchronously (the status later). michael@0: * michael@0: * When a new proxy needs to be notified of the current state of an image, call michael@0: * the Notify() method on this class with the relevant proxy as its argument, michael@0: * and the notifications will be replayed to the proxy asynchronously. michael@0: */ michael@0: michael@0: michael@0: class imgStatusTracker : public mozilla::SupportsWeakPtr michael@0: { michael@0: public: michael@0: MOZ_DECLARE_REFCOUNTED_TYPENAME(imgStatusTracker) michael@0: NS_INLINE_DECL_THREADSAFE_REFCOUNTING(imgStatusTracker) michael@0: michael@0: // aImage is the image that this status tracker will pass to the michael@0: // imgRequestProxys in SyncNotify() and EmulateRequestFinished(), and must be michael@0: // alive as long as this instance is, because we hold a weak reference to it. michael@0: imgStatusTracker(mozilla::image::Image* aImage); michael@0: virtual ~imgStatusTracker(); michael@0: michael@0: // Image-setter, for imgStatusTrackers created by imgRequest::Init, which michael@0: // are created before their Image is created. This method should only michael@0: // be called once, and only on an imgStatusTracker that was initialized michael@0: // without an image. michael@0: void SetImage(mozilla::image::Image* aImage); michael@0: michael@0: // Image resetter, for when mImage is about to go out of scope. mImage is a michael@0: // weak reference, and thus must be set to null when it's object is deleted. michael@0: void ResetImage(); michael@0: michael@0: // Inform this status tracker that it is associated with a multipart image. michael@0: void SetIsMultipart() { mIsMultipart = true; } michael@0: michael@0: // Schedule an asynchronous "replaying" of all the notifications that would michael@0: // have to happen to put us in the current state. michael@0: // We will also take note of any notifications that happen between the time michael@0: // Notify() is called and when we call SyncNotify on |proxy|, and replay them michael@0: // as well. michael@0: // Should be called on the main thread only, since imgRequestProxy and GetURI michael@0: // are not threadsafe. michael@0: void Notify(imgRequestProxy* proxy); michael@0: michael@0: // Schedule an asynchronous "replaying" of all the notifications that would michael@0: // have to happen to put us in the state we are in right now. michael@0: // Unlike Notify(), does *not* take into account future notifications. michael@0: // This is only useful if you do not have an imgRequest, e.g., if you are a michael@0: // static request returned from imgIRequest::GetStaticRequest(). michael@0: // Should be called on the main thread only, since imgRequestProxy and GetURI michael@0: // are not threadsafe. michael@0: void NotifyCurrentState(imgRequestProxy* proxy); michael@0: michael@0: // "Replay" all of the notifications that would have to happen to put us in michael@0: // the state we're currently in. michael@0: // Only use this if you're already servicing an asynchronous call (e.g. michael@0: // OnStartRequest). michael@0: // Should be called on the main thread only, since imgRequestProxy and GetURI michael@0: // are not threadsafe. michael@0: void SyncNotify(imgRequestProxy* proxy); michael@0: michael@0: // Send some notifications that would be necessary to make |proxy| believe michael@0: // the request is finished downloading and decoding. We only send michael@0: // OnStopRequest and UnblockOnload, and only if necessary. michael@0: void EmulateRequestFinished(imgRequestProxy* proxy, nsresult aStatus); michael@0: michael@0: // We manage a set of consumers that are using an image and thus concerned michael@0: // with its status. Weak pointers. michael@0: void AddConsumer(imgRequestProxy* aConsumer); michael@0: bool RemoveConsumer(imgRequestProxy* aConsumer, nsresult aStatus); michael@0: size_t ConsumerCount() const { michael@0: MOZ_ASSERT(NS_IsMainThread(), "Use mConsumers on main thread only"); michael@0: return mConsumers.Length(); michael@0: } michael@0: michael@0: // This is intentionally non-general because its sole purpose is to support an michael@0: // some obscure network priority logic in imgRequest. That stuff could probably michael@0: // be improved, but it's too scary to mess with at the moment. michael@0: bool FirstConsumerIs(imgRequestProxy* aConsumer); michael@0: michael@0: void AdoptConsumers(imgStatusTracker* aTracker) { michael@0: MOZ_ASSERT(NS_IsMainThread(), "Use mConsumers on main thread only"); michael@0: MOZ_ASSERT(aTracker); michael@0: mConsumers = aTracker->mConsumers; michael@0: } michael@0: michael@0: // Returns whether we are in the process of loading; that is, whether we have michael@0: // not received OnStopRequest. michael@0: bool IsLoading() const; michael@0: michael@0: // Get the current image status (as in imgIRequest). michael@0: uint32_t GetImageStatus() const; michael@0: michael@0: // Following are all the notification methods. You must call the Record michael@0: // variant on this status tracker, then call the Send variant for each proxy michael@0: // you want to notify. michael@0: michael@0: // Call when the request is being cancelled. michael@0: void RecordCancel(); michael@0: michael@0: // Shorthand for recording all the load notifications: StartRequest, michael@0: // StartContainer, StopRequest. michael@0: void RecordLoaded(); michael@0: michael@0: // Shorthand for recording all the decode notifications: StartDecode, michael@0: // StartFrame, DataAvailable, StopFrame, StopDecode. michael@0: void RecordDecoded(); michael@0: michael@0: /* non-virtual imgDecoderObserver methods */ michael@0: // Functions with prefix Send- are main thread only, since they contain calls michael@0: // to imgRequestProxy functions, which are expected on the main thread. michael@0: void RecordStartDecode(); michael@0: void SendStartDecode(imgRequestProxy* aProxy); michael@0: void RecordStartContainer(imgIContainer* aContainer); michael@0: void SendStartContainer(imgRequestProxy* aProxy); michael@0: void RecordStartFrame(); michael@0: // No SendStartFrame since it's not observed below us. michael@0: void RecordFrameChanged(const nsIntRect* aDirtyRect); michael@0: void SendFrameChanged(imgRequestProxy* aProxy, const nsIntRect* aDirtyRect); michael@0: void RecordStopFrame(); michael@0: void SendStopFrame(imgRequestProxy* aProxy); michael@0: void RecordStopDecode(nsresult statusg); michael@0: void SendStopDecode(imgRequestProxy* aProxy, nsresult aStatus); michael@0: void RecordDiscard(); michael@0: void SendDiscard(imgRequestProxy* aProxy); michael@0: void RecordUnlockedDraw(); michael@0: void SendUnlockedDraw(imgRequestProxy* aProxy); michael@0: void RecordImageIsAnimated(); michael@0: void SendImageIsAnimated(imgRequestProxy *aProxy); michael@0: michael@0: /* non-virtual sort-of-nsIRequestObserver methods */ michael@0: // Functions with prefix Send- are main thread only, since they contain calls michael@0: // to imgRequestProxy functions, which are expected on the main thread. michael@0: void RecordStartRequest(); michael@0: void SendStartRequest(imgRequestProxy* aProxy); michael@0: void RecordStopRequest(bool aLastPart, nsresult aStatus); michael@0: void SendStopRequest(imgRequestProxy* aProxy, bool aLastPart, nsresult aStatus); michael@0: michael@0: // All main thread only because they call functions (like SendStartRequest) michael@0: // which are expected to be called on the main thread. michael@0: void OnStartRequest(); michael@0: // OnDataAvailable will dispatch a call to itself onto the main thread if not michael@0: // called there. michael@0: void OnDataAvailable(); michael@0: void OnStopRequest(bool aLastPart, nsresult aStatus); michael@0: void OnDiscard(); michael@0: void FrameChanged(const nsIntRect* aDirtyRect); michael@0: void OnUnlockedDraw(); michael@0: // This is called only by VectorImage, and only to ensure tests work michael@0: // properly. Do not use it. michael@0: void OnStopFrame(); michael@0: michael@0: /* non-virtual imgIOnloadBlocker methods */ michael@0: // NB: If UnblockOnload is sent, and then we are asked to replay the michael@0: // notifications, we will not send a BlockOnload/UnblockOnload pair. This michael@0: // is different from all the other notifications. michael@0: void RecordBlockOnload(); michael@0: void SendBlockOnload(imgRequestProxy* aProxy); michael@0: void RecordUnblockOnload(); michael@0: void SendUnblockOnload(imgRequestProxy* aProxy); michael@0: michael@0: // Main thread only because mConsumers is not threadsafe. michael@0: void MaybeUnblockOnload(); michael@0: michael@0: void RecordError(); michael@0: michael@0: bool IsMultipart() const { return mIsMultipart; } michael@0: michael@0: // Weak pointer getters - no AddRefs. michael@0: inline already_AddRefed GetImage() const { michael@0: nsRefPtr image = mImage; michael@0: return image.forget(); michael@0: } michael@0: inline bool HasImage() { return mImage; } michael@0: michael@0: inline imgDecoderObserver* GetDecoderObserver() { return mTrackerObserver.get(); } michael@0: michael@0: already_AddRefed CloneForRecording(); michael@0: michael@0: // Compute the difference between this status tracker and aOther. michael@0: mozilla::image::ImageStatusDiff Difference(imgStatusTracker* aOther) const; michael@0: michael@0: // Captures all of the decode notifications (i.e., not OnStartRequest / michael@0: // OnStopRequest) so far as an ImageStatusDiff. michael@0: mozilla::image::ImageStatusDiff DecodeStateAsDifference() const; michael@0: michael@0: // Update our state to incorporate the changes in aDiff. michael@0: void ApplyDifference(const mozilla::image::ImageStatusDiff& aDiff); michael@0: michael@0: // Notify for the changes captured in an ImageStatusDiff. Because this may michael@0: // result in recursive notifications, no decoding locks may be held. michael@0: // Called on the main thread only. michael@0: void SyncNotifyDifference(const mozilla::image::ImageStatusDiff& aDiff); michael@0: michael@0: nsIntRect GetInvalidRect() const { return mInvalidRect; } michael@0: michael@0: private: michael@0: typedef nsTObserverArray> ProxyArray; michael@0: friend class imgStatusNotifyRunnable; michael@0: friend class imgRequestNotifyRunnable; michael@0: friend class imgStatusTrackerObserver; michael@0: friend class imgStatusTrackerInit; michael@0: imgStatusTracker(const imgStatusTracker& aOther); michael@0: michael@0: // Main thread only because it deals with the observer service. michael@0: void FireFailureNotification(); michael@0: michael@0: // Main thread only, since imgRequestProxy calls are expected on the main michael@0: // thread, and mConsumers is not threadsafe. michael@0: static void SyncNotifyState(ProxyArray& proxies, michael@0: bool hasImage, uint32_t state, michael@0: nsIntRect& dirtyRect, bool hadLastPart); michael@0: michael@0: nsCOMPtr mRequestRunnable; michael@0: michael@0: // The invalid area of the most recent frame we know about. (All previous michael@0: // frames are assumed to be fully valid.) michael@0: nsIntRect mInvalidRect; michael@0: michael@0: // This weak ref should be set null when the image goes out of scope. michael@0: mozilla::image::Image* mImage; michael@0: michael@0: // List of proxies attached to the image. Each proxy represents a consumer michael@0: // using the image. Array and/or individual elements should only be accessed michael@0: // on the main thread. michael@0: ProxyArray mConsumers; michael@0: michael@0: mozilla::RefPtr mTrackerObserver; michael@0: michael@0: uint32_t mState; michael@0: uint32_t mImageStatus; michael@0: bool mIsMultipart : 1; michael@0: bool mHadLastPart : 1; michael@0: bool mHasBeenDecoded : 1; michael@0: }; michael@0: michael@0: class imgStatusTrackerInit michael@0: { michael@0: public: michael@0: imgStatusTrackerInit(mozilla::image::Image* aImage, michael@0: imgStatusTracker* aTracker); michael@0: ~imgStatusTrackerInit(); michael@0: private: michael@0: imgStatusTracker* mTracker; michael@0: }; michael@0: michael@0: #endif