/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #ifndef mozilla_image_ProgressTracker_h #define mozilla_image_ProgressTracker_h #include "CopyOnWrite.h" #include "mozilla/Mutex.h" #include "mozilla/RefPtr.h" #include "mozilla/WeakPtr.h" #include "nsDataHashtable.h" #include "nsCOMPtr.h" #include "nsTObserverArray.h" #include "nsThreadUtils.h" #include "nsRect.h" #include "IProgressObserver.h" class nsIRunnable; namespace mozilla { namespace image { class AsyncNotifyRunnable; class AsyncNotifyCurrentStateRunnable; class Image; /** * Image progress bitflags. * * See CheckProgressConsistency() for the invariants we enforce about the * ordering dependencies betweeen these flags. */ enum { FLAG_SIZE_AVAILABLE = 1u << 0, // STATUS_SIZE_AVAILABLE FLAG_DECODE_COMPLETE = 1u << 1, // STATUS_DECODE_COMPLETE FLAG_FRAME_COMPLETE = 1u << 2, // STATUS_FRAME_COMPLETE FLAG_LOAD_COMPLETE = 1u << 3, // STATUS_LOAD_COMPLETE FLAG_ONLOAD_BLOCKED = 1u << 4, FLAG_ONLOAD_UNBLOCKED = 1u << 5, FLAG_IS_ANIMATED = 1u << 6, // STATUS_IS_ANIMATED FLAG_HAS_TRANSPARENCY = 1u << 7, // STATUS_HAS_TRANSPARENCY FLAG_LAST_PART_COMPLETE = 1u << 8, FLAG_HAS_ERROR = 1u << 9 // STATUS_ERROR }; typedef uint32_t Progress; const uint32_t NoProgress = 0; inline Progress LoadCompleteProgress(bool aLastPart, bool aError, nsresult aStatus) { Progress progress = FLAG_LOAD_COMPLETE; if (aLastPart) { progress |= FLAG_LAST_PART_COMPLETE; } if (NS_FAILED(aStatus) || aError) { progress |= FLAG_HAS_ERROR; } return progress; } /** * ProgressTracker stores its observers in an ObserverTable, which is a hash * table mapping raw pointers to WeakPtr's to the same objects. This sounds like * unnecessary duplication of information, but it's necessary for stable hash * values since WeakPtr's lose the knowledge of which object they used to point * to when that object is destroyed. * * ObserverTable subclasses nsDataHashtable to add reference counting support * and a copy constructor, both of which are needed for use with CopyOnWrite<T>. */ class ObserverTable : public nsDataHashtable<nsPtrHashKey<IProgressObserver>, WeakPtr<IProgressObserver>> { public: NS_INLINE_DECL_REFCOUNTING(ObserverTable); ObserverTable() = default; ObserverTable(const ObserverTable& aOther) { NS_WARNING("Forced to copy ObserverTable due to nested notifications"); for (auto iter = aOther.ConstIter(); !iter.Done(); iter.Next()) { this->Put(iter.Key(), iter.Data()); } } private: ~ObserverTable() { } }; /** * ProgressTracker is a class that records an Image's progress through the * loading and decoding process, and makes it possible to send notifications to * IProgressObservers, both synchronously and asynchronously. * * When a new observer needs to be notified of the current progress of an image, * call the Notify() method on this class with the relevant observer as its * argument, and the notifications will be replayed to the observer * asynchronously. */ class ProgressTracker : public mozilla::SupportsWeakPtr<ProgressTracker> { virtual ~ProgressTracker() { } public: MOZ_DECLARE_WEAKREFERENCE_TYPENAME(ProgressTracker) NS_INLINE_DECL_THREADSAFE_REFCOUNTING(ProgressTracker) ProgressTracker() : mImageMutex("ProgressTracker::mImage") , mImage(nullptr) , mObservers(new ObserverTable) , mProgress(NoProgress) { } bool HasImage() const { MutexAutoLock lock(mImageMutex); return mImage; } already_AddRefed<Image> GetImage() const { MutexAutoLock lock(mImageMutex); RefPtr<Image> image = mImage; return image.forget(); } // Get the current image status (as in imgIRequest). uint32_t GetImageStatus() const; // Get the current Progress. Progress GetProgress() const { return mProgress; } // Schedule an asynchronous "replaying" of all the notifications that would // have to happen to put us in the current state. // We will also take note of any notifications that happen between the time // Notify() is called and when we call SyncNotify on |aObserver|, and replay // them as well. // Should be called on the main thread only, since observers and GetURI are // not threadsafe. void Notify(IProgressObserver* aObserver); // Schedule an asynchronous "replaying" of all the notifications that would // have to happen to put us in the state we are in right now. // Unlike Notify(), does *not* take into account future notifications. // This is only useful if you do not have an imgRequest, e.g., if you are a // static request returned from imgIRequest::GetStaticRequest(). // Should be called on the main thread only, since observers and GetURI are // not threadsafe. void NotifyCurrentState(IProgressObserver* aObserver); // "Replay" all of the notifications that would have to happen to put us in // the state we're currently in. // Only use this if you're already servicing an asynchronous call (e.g. // OnStartRequest). // Should be called on the main thread only, since observers and GetURI are // not threadsafe. void SyncNotify(IProgressObserver* aObserver); // Get this ProgressTracker ready for a new request. This resets all the // state that doesn't persist between requests. void ResetForNewRequest(); // Stateless notifications. These are dispatched and immediately forgotten // about. All of these notifications are main thread only. void OnDiscard(); void OnUnlockedDraw(); void OnImageAvailable(); // Compute the difference between this our progress and aProgress. This allows // callers to predict whether SyncNotifyProgress will send any notifications. Progress Difference(Progress aProgress) const { return ~mProgress & aProgress; } // Update our state to incorporate the changes in aProgress and synchronously // notify our observers. // // Because this may result in recursive notifications, no decoding locks may // be held. Called on the main thread only. void SyncNotifyProgress(Progress aProgress, const nsIntRect& aInvalidRect = nsIntRect()); // We manage a set of observers that are using an image and thus concerned // with its loading progress. Weak pointers. void AddObserver(IProgressObserver* aObserver); bool RemoveObserver(IProgressObserver* aObserver); uint32_t ObserverCount() const; // Resets our weak reference to our image. Image subclasses should call this // in their destructor. void ResetImage(); private: friend class AsyncNotifyRunnable; friend class AsyncNotifyCurrentStateRunnable; friend class ImageFactory; ProgressTracker(const ProgressTracker& aOther) = delete; // Sets our weak reference to our image. Only ImageFactory should call this. void SetImage(Image* aImage); // Send some notifications that would be necessary to make |aObserver| believe // the request is finished downloading and decoding. We only send // FLAG_LOAD_COMPLETE and FLAG_ONLOAD_UNBLOCKED, and only if necessary. void EmulateRequestFinished(IProgressObserver* aObserver); // Main thread only because it deals with the observer service. void FireFailureNotification(); // The runnable, if any, that we've scheduled to deliver async notifications. nsCOMPtr<nsIRunnable> mRunnable; // mImage is a weak ref; it should be set to null when the image goes out of // scope. mImageMutex protects mImage. mutable Mutex mImageMutex; Image* mImage; // Hashtable of observers attached to the image. Each observer represents a // consumer using the image. Main thread only. CopyOnWrite<ObserverTable> mObservers; Progress mProgress; }; } // namespace image } // namespace mozilla #endif // mozilla_image_ProgressTracker_h