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