summaryrefslogtreecommitdiffstats
path: root/dom/media/utils/MediaElementEventRunners.h
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--dom/media/utils/MediaElementEventRunners.h190
1 files changed, 190 insertions, 0 deletions
diff --git a/dom/media/utils/MediaElementEventRunners.h b/dom/media/utils/MediaElementEventRunners.h
new file mode 100644
index 0000000000..3f13494493
--- /dev/null
+++ b/dom/media/utils/MediaElementEventRunners.h
@@ -0,0 +1,190 @@
+/* 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_media_mediaelementeventrunners_h
+#define mozilla_media_mediaelementeventrunners_h
+
+#include "mozilla/dom/PlayPromise.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsIContent.h"
+#include "nsINamed.h"
+#include "nsIRunnable.h"
+#include "nsString.h"
+#include "nsISupportsImpl.h"
+#include "nsTString.h"
+
+namespace mozilla::dom {
+
+class HTMLMediaElement;
+
+// Under certain conditions there may be no-one holding references to
+// a media element from script, DOM parent, etc, but the element may still
+// fire meaningful events in the future so we can't destroy it yet:
+// 1) If the element is delaying the load event (or would be, if it were
+// in a document), then events up to loadeddata or error could be fired,
+// so we need to stay alive.
+// 2) If the element is not paused and playback has not ended, then
+// we will (or might) play, sending timeupdate and ended events and possibly
+// audio output, so we need to stay alive.
+// 3) if the element is seeking then we will fire seeking events and possibly
+// start playing afterward, so we need to stay alive.
+// 4) If autoplay could start playback in this element (if we got enough data),
+// then we need to stay alive.
+// 5) if the element is currently loading, not suspended, and its source is
+// not a MediaSource, then script might be waiting for progress events or a
+// 'stalled' or 'suspend' event, so we need to stay alive.
+// If we're already suspended then (all other conditions being met),
+// it's OK to just disappear without firing any more events,
+// since we have the freedom to remain suspended indefinitely. Note
+// that we could use this 'suspended' loophole to garbage-collect a suspended
+// element in case 4 even if it had 'autoplay' set, but we choose not to.
+// If someone throws away all references to a loading 'autoplay' element
+// sound should still eventually play.
+// 6) If the source is a MediaSource, most loading events will not fire unless
+// appendBuffer() is called on a SourceBuffer, in which case something is
+// already referencing the SourceBuffer, which keeps the associated media
+// element alive. Further, a MediaSource will never time out the resource
+// fetch, and so should not keep the media element alive if it is
+// unreferenced. A pending 'stalled' event keeps the media element alive.
+//
+// Media elements owned by inactive documents (i.e. documents not contained in
+// any document viewer) should never hold a self-reference because none of the
+// above conditions are allowed: the element will stop loading and playing
+// and never resume loading or playing unless its owner document changes to
+// an active document (which can only happen if there is an external reference
+// to the element).
+// Media elements with no owner doc should be able to hold a self-reference.
+// Something native must have created the element and may expect it to
+// stay alive to play.
+
+// It's very important that any change in state which could change the value of
+// needSelfReference in AddRemoveSelfReference be followed by a call to
+// AddRemoveSelfReference before this element could die!
+// It's especially important if needSelfReference would change to 'true',
+// since if we neglect to add a self-reference, this element might be
+// garbage collected while there are still event listeners that should
+// receive events. If we neglect to remove the self-reference then the element
+// just lives longer than it needs to.
+
+class nsMediaEventRunner : public nsIRunnable, public nsINamed {
+ public:
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(nsMediaEventRunner, nsIRunnable)
+
+ explicit nsMediaEventRunner(const nsAString& aName,
+ HTMLMediaElement* aElement,
+ const nsAString& aEventName = u"unknown"_ns);
+
+ void Cancel() { mElement = nullptr; }
+ NS_IMETHODIMP GetName(nsACString& aName) override {
+ aName = NS_ConvertUTF16toUTF8(mName).get();
+ return NS_OK;
+ }
+ nsString Name() const { return mName; }
+ nsString EventName() const { return mEventName; }
+
+ protected:
+ virtual ~nsMediaEventRunner() = default;
+ bool IsCancelled();
+ nsresult DispatchEvent(const nsAString& aName);
+
+ RefPtr<HTMLMediaElement> mElement;
+ nsString mName;
+ nsString mEventName;
+ uint32_t mLoadID;
+};
+
+/**
+ * This runner is used to dispatch async event on media element.
+ */
+class nsAsyncEventRunner : public nsMediaEventRunner {
+ public:
+ nsAsyncEventRunner(const nsAString& aEventName, HTMLMediaElement* aElement)
+ : nsMediaEventRunner(u"nsAsyncEventRunner"_ns, aElement, aEventName) {}
+ NS_IMETHOD Run() override;
+};
+
+/**
+ * These runners are used to handle `playing` event and address play promise.
+ *
+ * If no error is passed while constructing an instance, the instance will
+ * resolve the passed promises with undefined; otherwise, the instance will
+ * reject the passed promises with the passed error.
+ *
+ * The constructor appends the constructed instance into the passed media
+ * element's mPendingPlayPromisesRunners member and once the the runner is run
+ * (whether fulfilled or canceled), it removes itself from
+ * mPendingPlayPromisesRunners.
+ */
+class nsResolveOrRejectPendingPlayPromisesRunner : public nsMediaEventRunner {
+ public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(
+ nsResolveOrRejectPendingPlayPromisesRunner, nsMediaEventRunner)
+
+ nsResolveOrRejectPendingPlayPromisesRunner(
+ HTMLMediaElement* aElement, nsTArray<RefPtr<PlayPromise>>&& aPromises,
+ nsresult aError = NS_OK);
+ void ResolveOrReject();
+ NS_IMETHOD Run() override;
+
+ protected:
+ virtual ~nsResolveOrRejectPendingPlayPromisesRunner() = default;
+
+ private:
+ nsTArray<RefPtr<PlayPromise>> mPromises;
+ nsresult mError;
+};
+
+class nsNotifyAboutPlayingRunner
+ : public nsResolveOrRejectPendingPlayPromisesRunner {
+ public:
+ nsNotifyAboutPlayingRunner(
+ HTMLMediaElement* aElement,
+ nsTArray<RefPtr<PlayPromise>>&& aPendingPlayPromises)
+ : nsResolveOrRejectPendingPlayPromisesRunner(
+ aElement, std::move(aPendingPlayPromises)) {}
+ NS_IMETHOD Run() override;
+};
+
+/**
+ * This runner is used to dispatch a source error event, which would happen when
+ * loading resource failed.
+ */
+class nsSourceErrorEventRunner : public nsMediaEventRunner {
+ public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(nsSourceErrorEventRunner,
+ nsMediaEventRunner)
+ nsSourceErrorEventRunner(HTMLMediaElement* aElement, nsIContent* aSource)
+ : nsMediaEventRunner(u"nsSourceErrorEventRunner"_ns, aElement),
+ mSource(aSource) {}
+ NS_IMETHOD Run() override;
+
+ private:
+ virtual ~nsSourceErrorEventRunner() = default;
+ nsCOMPtr<nsIContent> mSource;
+};
+
+/**
+ * This runner is used to dispatch `timeupdate` event and ensure we don't
+ * dispatch `timeupdate` more often than once per `TIMEUPDATE_MS` if that is not
+ * a mandatory event.
+ */
+class nsTimeupdateRunner : public nsMediaEventRunner {
+ public:
+ nsTimeupdateRunner(HTMLMediaElement* aElement, bool aIsMandatory)
+ : nsMediaEventRunner(u"nsTimeupdateRunner"_ns, aElement,
+ u"timeupdate"_ns),
+ mIsMandatory(aIsMandatory) {}
+ NS_IMETHOD Run() override;
+
+ private:
+ bool ShouldDispatchTimeupdate() const;
+ bool mIsMandatory;
+};
+
+} // namespace mozilla::dom
+
+#endif // mozilla_media_mediaelementeventrunners_h