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