1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3  * License, v. 2.0. If a copy of the MPL was not distributed with this
4  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 
6 #include "mozilla/dom/TextTrackCue.h"
7 
8 #include "mozilla/dom/Document.h"
9 #include "mozilla/dom/HTMLTrackElement.h"
10 #include "mozilla/dom/TextTrackList.h"
11 #include "mozilla/dom/TextTrackRegion.h"
12 #include "nsComponentManagerUtils.h"
13 #include "mozilla/ClearOnShutdown.h"
14 #include "unicode/ubidi.h"
15 
16 extern mozilla::LazyLogModule gTextTrackLog;
17 
18 #define LOG(msg, ...)                     \
19   MOZ_LOG(gTextTrackLog, LogLevel::Debug, \
20           ("TextTrackCue=%p, " msg, this, ##__VA_ARGS__))
21 
22 namespace mozilla::dom {
23 
24 NS_IMPL_CYCLE_COLLECTION_INHERITED(TextTrackCue, DOMEventTargetHelper,
25                                    mDocument, mTrack, mTrackElement,
26                                    mDisplayState, mRegion)
27 
28 NS_IMPL_ADDREF_INHERITED(TextTrackCue, DOMEventTargetHelper)
29 NS_IMPL_RELEASE_INHERITED(TextTrackCue, DOMEventTargetHelper)
30 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(TextTrackCue)
31 NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
32 
33 StaticRefPtr<nsIWebVTTParserWrapper> TextTrackCue::sParserWrapper;
34 
35 // Set default value for cue, spec https://w3c.github.io/webvtt/#model-cues
SetDefaultCueSettings()36 void TextTrackCue::SetDefaultCueSettings() {
37   mPositionIsAutoKeyword = true;
38   // Spec https://www.w3.org/TR/webvtt1/#webvtt-cue-position-automatic-alignment
39   mPositionAlign = PositionAlignSetting::Auto;
40   mSize = 100.0;
41   mPauseOnExit = false;
42   mSnapToLines = true;
43   mLineIsAutoKeyword = true;
44   mAlign = AlignSetting::Center;
45   mLineAlign = LineAlignSetting::Start;
46   mVertical = DirectionSetting::_empty;
47   mActive = false;
48 }
49 
TextTrackCue(nsPIDOMWindowInner * aOwnerWindow,double aStartTime,double aEndTime,const nsAString & aText,ErrorResult & aRv)50 TextTrackCue::TextTrackCue(nsPIDOMWindowInner* aOwnerWindow, double aStartTime,
51                            double aEndTime, const nsAString& aText,
52                            ErrorResult& aRv)
53     : DOMEventTargetHelper(aOwnerWindow),
54       mText(aText),
55       mStartTime(aStartTime),
56       mEndTime(aEndTime),
57       mPosition(0.0),
58       mLine(0.0),
59       mReset(false, "TextTrackCue::mReset"),
60       mHaveStartedWatcher(false),
61       mWatchManager(
62           this, GetOwnerGlobal()->AbstractMainThreadFor(TaskCategory::Other)) {
63   LOG("create TextTrackCue");
64   SetDefaultCueSettings();
65   MOZ_ASSERT(aOwnerWindow);
66   if (NS_FAILED(StashDocument())) {
67     aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
68   }
69 }
70 
TextTrackCue(nsPIDOMWindowInner * aOwnerWindow,double aStartTime,double aEndTime,const nsAString & aText,HTMLTrackElement * aTrackElement,ErrorResult & aRv)71 TextTrackCue::TextTrackCue(nsPIDOMWindowInner* aOwnerWindow, double aStartTime,
72                            double aEndTime, const nsAString& aText,
73                            HTMLTrackElement* aTrackElement, ErrorResult& aRv)
74     : DOMEventTargetHelper(aOwnerWindow),
75       mText(aText),
76       mStartTime(aStartTime),
77       mEndTime(aEndTime),
78       mTrackElement(aTrackElement),
79       mPosition(0.0),
80       mLine(0.0),
81       mReset(false, "TextTrackCue::mReset"),
82       mHaveStartedWatcher(false),
83       mWatchManager(
84           this, GetOwnerGlobal()->AbstractMainThreadFor(TaskCategory::Other)) {
85   LOG("create TextTrackCue");
86   SetDefaultCueSettings();
87   MOZ_ASSERT(aOwnerWindow);
88   if (NS_FAILED(StashDocument())) {
89     aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
90   }
91 }
92 
93 TextTrackCue::~TextTrackCue() = default;
94 
95 /** Save a reference to our creating document so we don't have to
96  *  keep getting it from our window.
97  */
StashDocument()98 nsresult TextTrackCue::StashDocument() {
99   nsPIDOMWindowInner* window = GetOwner();
100   if (!window) {
101     return NS_ERROR_NO_INTERFACE;
102   }
103   mDocument = window->GetDoc();
104   if (!mDocument) {
105     return NS_ERROR_NOT_AVAILABLE;
106   }
107   return NS_OK;
108 }
109 
GetCueAsHTML()110 already_AddRefed<DocumentFragment> TextTrackCue::GetCueAsHTML() {
111   // mDocument may be null during cycle collector shutdown.
112   // See bug 941701.
113   if (!mDocument) {
114     return nullptr;
115   }
116 
117   if (!sParserWrapper) {
118     nsresult rv;
119     nsCOMPtr<nsIWebVTTParserWrapper> parserWrapper =
120         do_CreateInstance(NS_WEBVTTPARSERWRAPPER_CONTRACTID, &rv);
121     if (NS_FAILED(rv)) {
122       return mDocument->CreateDocumentFragment();
123     }
124     sParserWrapper = parserWrapper;
125     ClearOnShutdown(&sParserWrapper);
126   }
127 
128   nsPIDOMWindowInner* window = mDocument->GetInnerWindow();
129   if (!window) {
130     return mDocument->CreateDocumentFragment();
131   }
132 
133   RefPtr<DocumentFragment> frag;
134   sParserWrapper->ConvertCueToDOMTree(window, this, getter_AddRefs(frag));
135   if (!frag) {
136     return mDocument->CreateDocumentFragment();
137   }
138   return frag.forget();
139 }
140 
SetTrackElement(HTMLTrackElement * aTrackElement)141 void TextTrackCue::SetTrackElement(HTMLTrackElement* aTrackElement) {
142   mTrackElement = aTrackElement;
143 }
144 
WrapObject(JSContext * aCx,JS::Handle<JSObject * > aGivenProto)145 JSObject* TextTrackCue::WrapObject(JSContext* aCx,
146                                    JS::Handle<JSObject*> aGivenProto) {
147   return VTTCue_Binding::Wrap(aCx, this, aGivenProto);
148 }
149 
GetRegion()150 TextTrackRegion* TextTrackCue::GetRegion() { return mRegion; }
151 
SetRegion(TextTrackRegion * aRegion)152 void TextTrackCue::SetRegion(TextTrackRegion* aRegion) {
153   if (mRegion == aRegion) {
154     return;
155   }
156   mRegion = aRegion;
157   mReset = true;
158 }
159 
ComputedLine()160 double TextTrackCue::ComputedLine() {
161   // See spec https://w3c.github.io/webvtt/#cue-computed-line
162   if (!mLineIsAutoKeyword && !mSnapToLines && (mLine < 0.0 || mLine > 100.0)) {
163     return 100.0;
164   } else if (!mLineIsAutoKeyword) {
165     return mLine;
166   } else if (mLineIsAutoKeyword && !mSnapToLines) {
167     return 100.0;
168   } else if (!mTrack || !mTrack->GetTextTrackList() ||
169              !mTrack->GetTextTrackList()->GetMediaElement()) {
170     return -1.0;
171   }
172 
173   RefPtr<TextTrackList> trackList = mTrack->GetTextTrackList();
174   bool dummy;
175   uint32_t showingTracksNum = 0;
176   for (uint32_t idx = 0; idx < trackList->Length(); idx++) {
177     RefPtr<TextTrack> track = trackList->IndexedGetter(idx, dummy);
178     if (track->Mode() == TextTrackMode::Showing) {
179       showingTracksNum++;
180     }
181 
182     if (mTrack == track) {
183       break;
184     }
185   }
186 
187   return (-1.0) * showingTracksNum;
188 }
189 
ComputedPosition()190 double TextTrackCue::ComputedPosition() {
191   // See spec https://w3c.github.io/webvtt/#cue-computed-position
192   if (!mPositionIsAutoKeyword) {
193     return mPosition;
194   }
195   if (ComputedPositionAlign() == PositionAlignSetting::Line_left) {
196     return 0.0;
197   }
198   if (ComputedPositionAlign() == PositionAlignSetting::Line_right) {
199     return 100.0;
200   }
201   return 50.0;
202 }
203 
ComputedPositionAlign()204 PositionAlignSetting TextTrackCue::ComputedPositionAlign() {
205   // See spec https://w3c.github.io/webvtt/#cue-computed-position-alignment
206   if (mPositionAlign != PositionAlignSetting::Auto) {
207     return mPositionAlign;
208   } else if (mAlign == AlignSetting::Left) {
209     return PositionAlignSetting::Line_left;
210   } else if (mAlign == AlignSetting::Right) {
211     return PositionAlignSetting::Line_right;
212   } else if (mAlign == AlignSetting::Start) {
213     return IsTextBaseDirectionLTR() ? PositionAlignSetting::Line_left
214                                     : PositionAlignSetting::Line_right;
215   } else if (mAlign == AlignSetting::End) {
216     return IsTextBaseDirectionLTR() ? PositionAlignSetting::Line_right
217                                     : PositionAlignSetting::Line_left;
218   }
219   return PositionAlignSetting::Center;
220 }
221 
IsTextBaseDirectionLTR() const222 bool TextTrackCue::IsTextBaseDirectionLTR() const {
223   // The returned result by `ubidi_getBaseDirection` might be `neutral` if the
224   // text only contains netural charaters. In this case, we would treat its
225   // base direction as LTR.
226   return ubidi_getBaseDirection(mText.BeginReading(), mText.Length()) !=
227          UBIDI_RTL;
228 }
229 
NotifyDisplayStatesChanged()230 void TextTrackCue::NotifyDisplayStatesChanged() {
231   if (!mReset) {
232     return;
233   }
234 
235   if (!mTrack || !mTrack->GetTextTrackList() ||
236       !mTrack->GetTextTrackList()->GetMediaElement()) {
237     return;
238   }
239 
240   mTrack->GetTextTrackList()
241       ->GetMediaElement()
242       ->NotifyCueDisplayStatesChanged();
243 }
244 
SetActive(bool aActive)245 void TextTrackCue::SetActive(bool aActive) {
246   if (mActive == aActive) {
247     return;
248   }
249 
250   LOG("TextTrackCue, SetActive=%d", aActive);
251   mActive = aActive;
252   mDisplayState = mActive ? mDisplayState : nullptr;
253   if (mTrack) {
254     mTrack->NotifyCueActiveStateChanged(this);
255   }
256 }
257 
258 #undef LOG
259 
260 }  // namespace mozilla::dom
261