tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

MediaElementEventRunners.h (8364B)


      1 /* This Source Code Form is subject to the terms of the Mozilla Public
      2 * License, v. 2.0. If a copy of the MPL was not distributed with this
      3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      4 
      5 #ifndef mozilla_media_mediaelementeventrunners_h
      6 #define mozilla_media_mediaelementeventrunners_h
      7 
      8 #include "mozilla/dom/PlayPromise.h"
      9 #include "nsCycleCollectionParticipant.h"
     10 #include "nsIContent.h"
     11 #include "nsINamed.h"
     12 #include "nsIRunnable.h"
     13 #include "nsISupportsImpl.h"
     14 #include "nsString.h"
     15 #include "nsTString.h"
     16 
     17 namespace mozilla::dom {
     18 
     19 class HTMLMediaElement;
     20 
     21 // Under certain conditions there may be no-one holding references to
     22 // a media element from script, DOM parent, etc, but the element may still
     23 // fire meaningful events in the future so we can't destroy it yet:
     24 // 1) If the element is delaying the load event (or would be, if it were
     25 // in a document), then events up to loadeddata or error could be fired,
     26 // so we need to stay alive.
     27 // 2) If the element is not paused and playback has not ended, then
     28 // we will (or might) play, sending timeupdate and ended events and possibly
     29 // audio output, so we need to stay alive.
     30 // 3) if the element is seeking then we will fire seeking events and possibly
     31 // start playing afterward, so we need to stay alive.
     32 // 4) If autoplay could start playback in this element (if we got enough data),
     33 // then we need to stay alive.
     34 // 5) if the element is currently loading, not suspended, and its source is
     35 // not a MediaSource, then script might be waiting for progress events or a
     36 // 'stalled' or 'suspend' event, so we need to stay alive.
     37 // If we're already suspended then (all other conditions being met),
     38 // it's OK to just disappear without firing any more events,
     39 // since we have the freedom to remain suspended indefinitely. Note
     40 // that we could use this 'suspended' loophole to garbage-collect a suspended
     41 // element in case 4 even if it had 'autoplay' set, but we choose not to.
     42 // If someone throws away all references to a loading 'autoplay' element
     43 // sound should still eventually play.
     44 // 6) If the source is a MediaSource, most loading events will not fire unless
     45 // appendBuffer() is called on a SourceBuffer, in which case something is
     46 // already referencing the SourceBuffer, which keeps the associated media
     47 // element alive. Further, a MediaSource will never time out the resource
     48 // fetch, and so should not keep the media element alive if it is
     49 // unreferenced. A pending 'stalled' event keeps the media element alive.
     50 //
     51 // Media elements owned by inactive documents (i.e. documents not contained in
     52 // any document viewer) should never hold a self-reference because none of the
     53 // above conditions are allowed: the element will stop loading and playing
     54 // and never resume loading or playing unless its owner document changes to
     55 // an active document (which can only happen if there is an external reference
     56 // to the element).
     57 // Media elements with no owner doc should be able to hold a self-reference.
     58 // Something native must have created the element and may expect it to
     59 // stay alive to play.
     60 
     61 // It's very important that any change in state which could change the value of
     62 // needSelfReference in AddRemoveSelfReference be followed by a call to
     63 // AddRemoveSelfReference before this element could die!
     64 // It's especially important if needSelfReference would change to 'true',
     65 // since if we neglect to add a self-reference, this element might be
     66 // garbage collected while there are still event listeners that should
     67 // receive events. If we neglect to remove the self-reference then the element
     68 // just lives longer than it needs to.
     69 
     70 // Runnable for media element tasks.
     71 // These tasks have special behavior if the load algorithm is triggered before
     72 // the task is popped from the task queue, which is usually to skip running
     73 // the task.  See nsResolveOrRejectPendingPlayPromisesRunner for the exception.
     74 class nsMediaEventRunner : public nsIRunnable, public nsINamed {
     75 public:
     76  NS_DECL_CYCLE_COLLECTING_ISUPPORTS
     77  NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(nsMediaEventRunner, nsIRunnable)
     78 
     79  explicit nsMediaEventRunner(const char* aName, HTMLMediaElement* aElement,
     80                              const nsAString& aEventName = u"unknown"_ns);
     81 
     82  void Cancel() { mElement = nullptr; }
     83  NS_IMETHODIMP GetName(nsACString& aName) override {
     84    aName.AssignASCII(mName);
     85    return NS_OK;
     86  }
     87  const char* Name() const { return mName; }
     88  nsString EventName() const { return mEventName; }
     89 
     90 protected:
     91  virtual ~nsMediaEventRunner() = default;
     92  bool IsCancelled() const;
     93  MOZ_CAN_RUN_SCRIPT nsresult FireEvent(const nsAString& aName);
     94 
     95  virtual void ReportProfilerMarker();
     96  uint64_t GetElementDurationMs() const;
     97 
     98  RefPtr<HTMLMediaElement> mElement;
     99  const char* mName;
    100  nsString mEventName;
    101  uint32_t mLoadID;
    102 };
    103 
    104 /**
    105 * This runner is used to dispatch async event on media element.
    106 */
    107 class nsAsyncEventRunner : public nsMediaEventRunner {
    108 public:
    109  nsAsyncEventRunner(const nsAString& aEventName, HTMLMediaElement* aElement)
    110      : nsMediaEventRunner("nsAsyncEventRunner", aElement, aEventName) {}
    111  MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHOD Run() override;
    112 };
    113 
    114 /**
    115 * These runners are used to handle `playing` event and address play promise.
    116 *
    117 * If no error is passed while constructing an instance, the instance will
    118 * resolve the passed promises with undefined; otherwise, the instance will
    119 * reject the passed promises with the passed error.
    120 *
    121 * The constructor appends the constructed instance into the passed media
    122 * element's mPendingPlayPromisesRunners member and once the the runner is run
    123 * (whether fulfilled or canceled), it removes itself from
    124 * mPendingPlayPromisesRunners.
    125 *
    126 * If the load algorithm is triggered before the task is run then the pending
    127 * play promises passed will be settled at commencement of the load algorithm.
    128 */
    129 class nsResolveOrRejectPendingPlayPromisesRunner : public nsMediaEventRunner {
    130 public:
    131  NS_DECL_ISUPPORTS_INHERITED
    132  NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(
    133      nsResolveOrRejectPendingPlayPromisesRunner, nsMediaEventRunner)
    134 
    135  nsResolveOrRejectPendingPlayPromisesRunner(
    136      HTMLMediaElement* aElement, nsTArray<RefPtr<PlayPromise>>&& aPromises,
    137      nsresult aError = NS_OK);
    138  void ResolveOrReject();
    139  NS_IMETHOD Run() override;
    140 
    141 protected:
    142  virtual ~nsResolveOrRejectPendingPlayPromisesRunner() = default;
    143 
    144 private:
    145  nsTArray<RefPtr<PlayPromise>> mPromises;
    146  nsresult mError;
    147 };
    148 
    149 class nsNotifyAboutPlayingRunner
    150    : public nsResolveOrRejectPendingPlayPromisesRunner {
    151 public:
    152  nsNotifyAboutPlayingRunner(
    153      HTMLMediaElement* aElement,
    154      nsTArray<RefPtr<PlayPromise>>&& aPendingPlayPromises)
    155      : nsResolveOrRejectPendingPlayPromisesRunner(
    156            aElement, std::move(aPendingPlayPromises)) {}
    157  MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHOD Run() override;
    158 };
    159 
    160 /**
    161 * This runner is used to dispatch a source error event, which would happen when
    162 * loading resource failed.
    163 */
    164 class nsSourceErrorEventRunner : public nsMediaEventRunner {
    165 public:
    166  NS_DECL_ISUPPORTS_INHERITED
    167  NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(nsSourceErrorEventRunner,
    168                                           nsMediaEventRunner)
    169  nsSourceErrorEventRunner(HTMLMediaElement* aElement, nsIContent* aSource,
    170                           const nsACString& aErrorDetails)
    171      : nsMediaEventRunner("nsSourceErrorEventRunner", aElement),
    172        mSource(aSource),
    173        mErrorDetails(NS_ConvertUTF8toUTF16(aErrorDetails)) {}
    174  NS_IMETHOD Run() override;
    175 
    176 private:
    177  virtual ~nsSourceErrorEventRunner() = default;
    178  nsCOMPtr<nsIContent> mSource;
    179  const nsString mErrorDetails;
    180 };
    181 
    182 /**
    183 * This runner is used to dispatch `timeupdate` event and ensure we don't
    184 * dispatch `timeupdate` more often than once per `TIMEUPDATE_MS` if that is not
    185 * a mandatory event.
    186 */
    187 class nsTimeupdateRunner : public nsMediaEventRunner {
    188 public:
    189  nsTimeupdateRunner(HTMLMediaElement* aElement, bool aIsMandatory)
    190      : nsMediaEventRunner("nsTimeupdateRunner", aElement, u"timeupdate"_ns),
    191        mIsMandatory(aIsMandatory) {}
    192  MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHOD Run() override;
    193 
    194 private:
    195  void ReportProfilerMarker() override;
    196  bool ShouldDispatchTimeupdate() const;
    197  bool mIsMandatory;
    198 };
    199 
    200 }  // namespace mozilla::dom
    201 
    202 #endif  // mozilla_media_mediaelementeventrunners_h