summaryrefslogtreecommitdiffstats
path: root/image/ProgressTracker.h
diff options
context:
space:
mode:
Diffstat (limited to 'image/ProgressTracker.h')
-rw-r--r--image/ProgressTracker.h263
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