Backed out changeset f594e6f00208 (bug 1940883) for causing crashes in bug 1941164.
[gecko.git] / dom / media / webvtt / TextTrack.cpp
blob692d872f96445eba5e168c9729cb6c920c25a815
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim:set ts=2 sw=2 et tw=78: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "mozilla/dom/TextTrack.h"
9 #include "mozilla/AsyncEventDispatcher.h"
10 #include "mozilla/dom/HTMLMediaElement.h"
11 #include "mozilla/dom/HTMLTrackElement.h"
12 #include "mozilla/dom/TextTrackBinding.h"
13 #include "mozilla/dom/TextTrackCue.h"
14 #include "mozilla/dom/TextTrackCueList.h"
15 #include "mozilla/dom/TextTrackList.h"
16 #include "mozilla/dom/TextTrackRegion.h"
17 #include "nsGlobalWindowInner.h"
19 extern mozilla::LazyLogModule gTextTrackLog;
21 #define WEBVTT_LOG(msg, ...) \
22 MOZ_LOG(gTextTrackLog, LogLevel::Debug, \
23 ("TextTrack=%p, " msg, this, ##__VA_ARGS__))
25 namespace mozilla::dom {
27 NS_IMPL_CYCLE_COLLECTION_INHERITED(TextTrack, DOMEventTargetHelper, mCueList,
28 mActiveCueList, mTextTrackList,
29 mTrackElement)
31 NS_IMPL_ADDREF_INHERITED(TextTrack, DOMEventTargetHelper)
32 NS_IMPL_RELEASE_INHERITED(TextTrack, DOMEventTargetHelper)
33 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(TextTrack)
34 NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
36 TextTrack::TextTrack(nsPIDOMWindowInner* aOwnerWindow, TextTrackKind aKind,
37 const nsAString& aLabel, const nsAString& aLanguage,
38 TextTrackMode aMode, TextTrackReadyState aReadyState,
39 TextTrackSource aTextTrackSource)
40 : DOMEventTargetHelper(aOwnerWindow),
41 mKind(aKind),
42 mLabel(aLabel),
43 mLanguage(aLanguage),
44 mMode(aMode),
45 mReadyState(aReadyState),
46 mTextTrackSource(aTextTrackSource) {
47 SetDefaultSettings();
50 TextTrack::TextTrack(nsPIDOMWindowInner* aOwnerWindow,
51 TextTrackList* aTextTrackList, TextTrackKind aKind,
52 const nsAString& aLabel, const nsAString& aLanguage,
53 TextTrackMode aMode, TextTrackReadyState aReadyState,
54 TextTrackSource aTextTrackSource)
55 : DOMEventTargetHelper(aOwnerWindow),
56 mTextTrackList(aTextTrackList),
57 mKind(aKind),
58 mLabel(aLabel),
59 mLanguage(aLanguage),
60 mMode(aMode),
61 mReadyState(aReadyState),
62 mTextTrackSource(aTextTrackSource) {
63 SetDefaultSettings();
66 TextTrack::~TextTrack() = default;
68 void TextTrack::SetDefaultSettings() {
69 nsPIDOMWindowInner* ownerWindow = GetOwnerWindow();
70 mCueList = new TextTrackCueList(ownerWindow);
71 mActiveCueList = new TextTrackCueList(ownerWindow);
72 mCuePos = 0;
73 mDirty = false;
76 JSObject* TextTrack::WrapObject(JSContext* aCx,
77 JS::Handle<JSObject*> aGivenProto) {
78 return TextTrack_Binding::Wrap(aCx, this, aGivenProto);
81 void TextTrack::SetMode(TextTrackMode aValue) {
82 if (mMode == aValue) {
83 return;
85 WEBVTT_LOG("Set mode=%s for track kind %s", GetEnumString(aValue).get(),
86 GetEnumString(mKind).get());
87 mMode = aValue;
89 HTMLMediaElement* mediaElement = GetMediaElement();
90 if (aValue == TextTrackMode::Disabled) {
91 for (size_t i = 0; i < mCueList->Length() && mediaElement; ++i) {
92 mediaElement->NotifyCueRemoved(*(*mCueList)[i]);
94 SetCuesInactive();
95 } else {
96 for (size_t i = 0; i < mCueList->Length() && mediaElement; ++i) {
97 mediaElement->NotifyCueAdded(*(*mCueList)[i]);
100 if (mediaElement) {
101 mediaElement->NotifyTextTrackModeChanged();
103 // https://html.spec.whatwg.org/multipage/media.html#sourcing-out-of-band-text-tracks:start-the-track-processing-model
104 // Run the `start-the-track-processing-model` to track's corresponding track
105 // element whenever track's mode changes.
106 if (mTrackElement) {
107 mTrackElement->MaybeDispatchLoadResource();
109 // Ensure the TimeMarchesOn is called in case that the mCueList
110 // is empty.
111 NotifyCueUpdated(nullptr);
114 void TextTrack::GetId(nsAString& aId) const {
115 // If the track has a track element then its id should be the same as the
116 // track element's id.
117 if (mTrackElement) {
118 mTrackElement->GetAttr(nsGkAtoms::id, aId);
122 void TextTrack::AddCue(TextTrackCue& aCue) {
123 WEBVTT_LOG("AddCue %p [%f:%f]", &aCue, aCue.StartTime(), aCue.EndTime());
124 TextTrack* oldTextTrack = aCue.GetTrack();
125 if (oldTextTrack) {
126 ErrorResult dummy;
127 oldTextTrack->RemoveCue(aCue, dummy);
129 mCueList->AddCue(aCue);
130 aCue.SetTrack(this);
131 HTMLMediaElement* mediaElement = GetMediaElement();
132 if (mediaElement && (mMode != TextTrackMode::Disabled)) {
133 mediaElement->NotifyCueAdded(aCue);
137 void TextTrack::RemoveCue(TextTrackCue& aCue, ErrorResult& aRv) {
138 WEBVTT_LOG("RemoveCue %p", &aCue);
139 // Bug1304948, check the aCue belongs to the TextTrack.
140 mCueList->RemoveCue(aCue, aRv);
141 if (aRv.Failed()) {
142 return;
144 aCue.SetActive(false);
145 aCue.SetTrack(nullptr);
146 HTMLMediaElement* mediaElement = GetMediaElement();
147 if (mediaElement) {
148 mediaElement->NotifyCueRemoved(aCue);
152 void TextTrack::ClearAllCues() {
153 WEBVTT_LOG("ClearAllCues");
154 ErrorResult dummy;
155 while (!mCueList->IsEmpty()) {
156 RemoveCue(*(*mCueList)[0], dummy);
160 void TextTrack::SetCuesDirty() {
161 for (uint32_t i = 0; i < mCueList->Length(); i++) {
162 ((*mCueList)[i])->Reset();
166 TextTrackCueList* TextTrack::GetActiveCues() {
167 if (mMode != TextTrackMode::Disabled) {
168 return mActiveCueList;
170 return nullptr;
173 void TextTrack::GetActiveCueArray(nsTArray<RefPtr<TextTrackCue> >& aCues) {
174 if (mMode != TextTrackMode::Disabled) {
175 mActiveCueList->GetArray(aCues);
179 TextTrackReadyState TextTrack::ReadyState() const { return mReadyState; }
181 void TextTrack::SetReadyState(TextTrackReadyState aState) {
182 WEBVTT_LOG("SetReadyState=%s", EnumValueToString(aState));
183 mReadyState = aState;
184 HTMLMediaElement* mediaElement = GetMediaElement();
185 if (mediaElement && (mReadyState == TextTrackReadyState::Loaded ||
186 mReadyState == TextTrackReadyState::FailedToLoad)) {
187 mediaElement->RemoveTextTrack(this, true);
188 mediaElement->UpdateReadyState();
192 TextTrackList* TextTrack::GetTextTrackList() { return mTextTrackList; }
194 void TextTrack::SetTextTrackList(TextTrackList* aTextTrackList) {
195 mTextTrackList = aTextTrackList;
198 HTMLTrackElement* TextTrack::GetTrackElement() { return mTrackElement; }
200 void TextTrack::SetTrackElement(HTMLTrackElement* aTrackElement) {
201 mTrackElement = aTrackElement;
204 void TextTrack::SetCuesInactive() {
205 WEBVTT_LOG("SetCuesInactive");
206 mCueList->SetCuesInactive();
209 void TextTrack::NotifyCueUpdated(TextTrackCue* aCue) {
210 WEBVTT_LOG("NotifyCueUpdated, cue=%p", aCue);
211 mCueList->NotifyCueUpdated(aCue);
212 HTMLMediaElement* mediaElement = GetMediaElement();
213 if (mediaElement) {
214 mediaElement->NotifyCueUpdated(aCue);
218 void TextTrack::GetLabel(nsAString& aLabel) const {
219 if (mTrackElement) {
220 mTrackElement->GetLabel(aLabel);
221 } else {
222 aLabel = mLabel;
225 void TextTrack::GetLanguage(nsAString& aLanguage) const {
226 if (mTrackElement) {
227 mTrackElement->GetSrclang(aLanguage);
228 } else {
229 aLanguage = mLanguage;
233 void TextTrack::DispatchAsyncTrustedEvent(const nsString& aEventName) {
234 nsGlobalWindowInner* win = GetOwnerWindow();
235 if (!win) {
236 return;
238 win->Dispatch(
239 NS_NewRunnableFunction("dom::TextTrack::DispatchAsyncTrustedEvent",
240 [self = RefPtr{this}, aEventName]() {
241 self->DispatchTrustedEvent(aEventName);
242 }));
245 bool TextTrack::IsLoaded() {
246 if (mMode == TextTrackMode::Disabled) {
247 return true;
249 // If the TrackElement's src is null, we can not block the
250 // MediaElement.
251 if (mTrackElement) {
252 nsAutoString src;
253 if (!(mTrackElement->GetAttr(nsGkAtoms::src, src))) {
254 return true;
257 return mReadyState >= TextTrackReadyState::Loaded;
260 void TextTrack::NotifyCueActiveStateChanged(TextTrackCue* aCue) {
261 MOZ_ASSERT(aCue);
262 if (aCue->GetActive()) {
263 MOZ_ASSERT(!mActiveCueList->IsCueExist(aCue));
264 WEBVTT_LOG("NotifyCueActiveStateChanged, add cue %p to the active list",
265 aCue);
266 mActiveCueList->AddCue(*aCue);
267 } else {
268 MOZ_ASSERT(mActiveCueList->IsCueExist(aCue));
269 WEBVTT_LOG(
270 "NotifyCueActiveStateChanged, remove cue %p from the active list",
271 aCue);
272 mActiveCueList->RemoveCue(*aCue);
276 void TextTrack::GetCurrentCuesAndOtherCues(
277 RefPtr<TextTrackCueList>& aCurrentCues,
278 RefPtr<TextTrackCueList>& aOtherCues,
279 const media::TimeInterval& aInterval) const {
280 const HTMLMediaElement* mediaElement = GetMediaElement();
281 if (!mediaElement) {
282 return;
285 if (Mode() == TextTrackMode::Disabled) {
286 return;
289 // According to `time marches on` step1, current cue list contains the cues
290 // whose start times are less than or equal to the current playback position
291 // and whose end times are greater than the current playback position.
292 // https://html.spec.whatwg.org/multipage/media.html#time-marches-on
293 MOZ_ASSERT(aCurrentCues && aOtherCues);
294 const double playbackTime = mediaElement->CurrentTime();
295 for (uint32_t idx = 0; idx < mCueList->Length(); idx++) {
296 TextTrackCue* cue = (*mCueList)[idx];
297 WEBVTT_LOG("cue %p [%f:%f], playbackTime=%f", cue, cue->StartTime(),
298 cue->EndTime(), playbackTime);
299 if (cue->StartTime() <= playbackTime && cue->EndTime() > playbackTime) {
300 WEBVTT_LOG("Add cue %p [%f:%f] to current cue list", cue,
301 cue->StartTime(), cue->EndTime());
302 aCurrentCues->AddCue(*cue);
303 } else {
304 // As the spec didn't have a restriction for the negative duration, it
305 // does happen sometime if user sets it explicitly. It would be treated as
306 // a `missing cue` later in the `TimeMarchesOn` but it won't be displayed.
307 if (cue->EndTime() < cue->StartTime()) {
308 // Add cue into `otherCue` only when its start time is contained by the
309 // current time interval.
310 if (aInterval.Contains(
311 media::TimeUnit::FromSeconds(cue->StartTime()))) {
312 WEBVTT_LOG("[Negative duration] Add cue %p [%f:%f] to other cue list",
313 cue, cue->StartTime(), cue->EndTime());
314 aOtherCues->AddCue(*cue);
316 continue;
318 media::TimeInterval cueInterval(
319 media::TimeUnit::FromSeconds(cue->StartTime()),
320 media::TimeUnit::FromSeconds(cue->EndTime()));
321 // cues are completely outside the time interval.
322 if (!aInterval.Touches(cueInterval)) {
323 continue;
325 // contains any cues which are overlapping within the time interval.
326 WEBVTT_LOG("Add cue %p [%f:%f] to other cue list", cue, cue->StartTime(),
327 cue->EndTime());
328 aOtherCues->AddCue(*cue);
333 HTMLMediaElement* TextTrack::GetMediaElement() const {
334 return mTextTrackList ? mTextTrackList->GetMediaElement() : nullptr;
337 } // namespace mozilla::dom