diff options
Diffstat (limited to 'image/ProgressTracker.h')
-rw-r--r-- | image/ProgressTracker.h | 263 |
1 files changed, 263 insertions, 0 deletions
diff --git a/image/ProgressTracker.h b/image/ProgressTracker.h new file mode 100644 index 0000000000..ae07a83536 --- /dev/null +++ b/image/ProgressTracker.h @@ -0,0 +1,263 @@ +/* -*- 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/NotNull.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 between 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_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 { + virtual ~ProgressTracker() {} + + public: + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(ProgressTracker) + + ProgressTracker(); + + bool HasImage() const { + MutexAutoLock lock(mMutex); + return mImage; + } + already_AddRefed<Image> GetImage() const { + MutexAutoLock lock(mMutex); + 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; + + // Get the event target we should currently dispatch events to. + already_AddRefed<nsIEventTarget> GetEventTarget() const; + + // Resets our weak reference to our image. Image subclasses should call this + // in their destructor. + void ResetImage(); + + // Tell this progress tracker that it is for a multipart image. + void SetIsMultipart() { mIsMultipart = true; } + + 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(); + + // Wrapper for AsyncNotifyRunnable to make it have medium high priority like + // other imagelib runnables. + class MediumHighRunnable final : public PrioritizableRunnable { + explicit MediumHighRunnable(already_AddRefed<AsyncNotifyRunnable>&& aEvent); + virtual ~MediumHighRunnable() = default; + + public: + void AddObserver(IProgressObserver* aObserver); + void RemoveObserver(IProgressObserver* aObserver); + + static already_AddRefed<MediumHighRunnable> Create( + already_AddRefed<AsyncNotifyRunnable>&& aEvent); + }; + + // The runnable, if any, that we've scheduled to deliver async notifications. + RefPtr<MediumHighRunnable> mRunnable; + + // mMutex protects access to mImage and mEventTarget. + mutable Mutex mMutex; + + // mImage is a weak ref; it should be set to null when the image goes out of + // scope. + Image* mImage; + + // mEventTarget is the current, best effort event target to dispatch + // notifications to from the decoder threads. It will change as observers are + // added and removed (see mObserversWithTargets). + NotNull<nsCOMPtr<nsIEventTarget>> mEventTarget; + + // How many observers have been added that have an explicit event target. + // When the first observer is added with an explicit event target, we will + // default to that as long as all observers use the same target. If a new + // observer is added which has a different event target, we will switch to + // using the unlabeled main thread event target which is safe for all + // observers. If all observers with explicit event targets are removed, we + // will revert back to the initial event target (for SystemGroup). An + // observer without an explicit event target does not care what context it + // is dispatched in, and thus does not impact the state. + uint32_t mObserversWithTargets; + + // Hashtable of observers attached to the image. Each observer represents a + // consumer using the image. Main thread only. + CopyOnWrite<ObserverTable> mObservers; + + Progress mProgress; + + // Whether this is a progress tracker for a multipart image. + bool mIsMultipart; +}; + +} // namespace image +} // namespace mozilla + +#endif // mozilla_image_ProgressTracker_h |