1use std::cell::{Cell, Ref};
6
7use dom_struct::dom_struct;
8use js::context::{JSContext, NoGC};
9use script_bindings::cell::DomRefCell;
10use script_bindings::inheritance::Castable;
11use script_bindings::reflector::reflect_dom_object;
12
13use crate::dom::bindings::codegen::Bindings::HTMLTrackElementBinding::HTMLTrackElementMethods;
14use crate::dom::bindings::codegen::Bindings::TextTrackBinding::{
15 TextTrackKind, TextTrackMethods, TextTrackMode,
16};
17use crate::dom::bindings::error::{Error, ErrorResult};
18use crate::dom::bindings::reflector::DomGlobal;
19use crate::dom::bindings::root::{DomRoot, MutNullableDom, UnrootedDom};
20use crate::dom::bindings::str::DOMString;
21use crate::dom::element::Element;
22use crate::dom::eventtarget::EventTarget;
23use crate::dom::html::htmltrackelement::HTMLTrackElement;
24use crate::dom::texttrackcue::TextTrackCue;
25use crate::dom::texttrackcuelist::TextTrackCueList;
26use crate::dom::texttracklist::TextTrackList;
27use crate::dom::webvtt::rules_for_rendering::RulesForUpdatingTheTextTrackRendering;
28use crate::dom::window::Window;
29
30#[dom_struct]
31pub(crate) struct TextTrack {
32 eventtarget: EventTarget,
33 kind: Cell<TextTrackKind>,
35 label: DomRefCell<DOMString>,
37 language: DomRefCell<DOMString>,
39 id: DomRefCell<DOMString>,
41 mode: Cell<TextTrackMode>,
43 cue_list: MutNullableDom<TextTrackCueList>,
45 active_cue_list: MutNullableDom<TextTrackCueList>,
47 track_list: MutNullableDom<TextTrackList>,
48 associated_track: MutNullableDom<HTMLTrackElement>,
49 rules_for_updating_the_text_track_rendering:
51 Cell<Option<RulesForUpdatingTheTextTrackRendering>>,
52}
53
54impl TextTrack {
55 pub(crate) fn new_inherited(
56 id: DOMString,
57 kind: TextTrackKind,
58 label: DOMString,
59 language: DOMString,
60 mode: TextTrackMode,
61 track_list: Option<&TextTrackList>,
62 ) -> TextTrack {
63 TextTrack {
64 eventtarget: EventTarget::new_inherited(),
65 kind: Cell::new(kind),
66 label: DomRefCell::new(label),
67 language: DomRefCell::new(language),
68 id: DomRefCell::new(id),
69 mode: Cell::new(mode),
70 cue_list: Default::default(),
71 active_cue_list: Default::default(),
72 track_list: MutNullableDom::new(track_list),
73 associated_track: Default::default(),
74 rules_for_updating_the_text_track_rendering: Default::default(),
75 }
76 }
77
78 #[expect(clippy::too_many_arguments)]
79 pub(crate) fn new(
80 cx: &mut JSContext,
81 window: &Window,
82 id: DOMString,
83 kind: TextTrackKind,
84 label: DOMString,
85 language: DOMString,
86 mode: TextTrackMode,
87 track_list: Option<&TextTrackList>,
88 ) -> DomRoot<TextTrack> {
89 reflect_dom_object(
90 cx,
91 Box::new(TextTrack::new_inherited(
92 id, kind, label, language, mode, track_list,
93 )),
94 window,
95 )
96 }
97
98 pub(crate) fn cues<'no_gc>(
99 &self,
100 no_gc: &'no_gc NoGC,
101 ) -> Vec<UnrootedDom<'no_gc, TextTrackCue>> {
102 self.cue_list
103 .get_unrooted(no_gc)
104 .map(|list| list.cues(no_gc))
105 .unwrap_or_default()
106 }
107
108 pub(crate) fn text_track_cue_list(&self, cx: &mut JSContext) -> DomRoot<TextTrackCueList> {
109 self.cue_list
110 .or_init(|| TextTrackCueList::new(cx, self, self.global().as_window()))
111 }
112
113 fn active_text_track_cue_list(&self, cx: &mut JSContext) -> DomRoot<TextTrackCueList> {
114 let active_cue_list = self
115 .active_cue_list
116 .or_init(|| TextTrackCueList::new(cx, self, self.global().as_window()));
117 if let Some(cue_list) = self.cue_list.get_unrooted(cx.no_gc()) {
118 active_cue_list.refresh_active_cues(cx.no_gc(), cue_list);
119 }
120 active_cue_list
121 }
122
123 pub(crate) fn id(&self) -> Ref<'_, DOMString> {
124 self.id.borrow()
125 }
126
127 pub(crate) fn track_list(&self) -> Option<DomRoot<TextTrackList>> {
128 self.track_list.get()
129 }
130
131 pub(crate) fn add_track_list(&self, track_list: &TextTrackList) {
132 self.track_list.set(Some(track_list));
133 }
134
135 pub(crate) fn remove_track_list(&self) {
136 self.track_list.set(None);
137 }
138
139 pub(crate) fn associated_track(&self) -> Option<DomRoot<HTMLTrackElement>> {
140 self.associated_track.get()
141 }
142
143 pub(crate) fn set_associated_track(&self, track_element: &HTMLTrackElement) {
145 self.associated_track.set(Some(track_element));
146 self.update_attributes_from_track_element(track_element);
149 }
150
151 pub(crate) fn update_attributes_from_track_element(&self, track_element: &HTMLTrackElement) {
153 self.kind.set(match track_element.Kind().str().as_ref() {
158 "subtitles" => TextTrackKind::Subtitles,
159 "captions" => TextTrackKind::Captions,
160 "descriptions" => TextTrackKind::Descriptions,
161 "chapters" => TextTrackKind::Chapters,
162 "metadata" => TextTrackKind::Metadata,
163 _ => unreachable!("Must always have these specific kind states"),
164 });
165 *self.label.borrow_mut() = track_element.Label();
167 *self.language.borrow_mut() = track_element.Srclang();
170 *self.id.borrow_mut() = track_element
173 .upcast::<Element>()
174 .get_id()
175 .map(|value| DOMString::from(&*value))
176 .unwrap_or_default();
177 }
178
179 pub(crate) fn empty_cue_list(&self) {
180 if let Some(cue_list) = self.cue_list.get() {
181 cue_list.empty();
182 }
183 }
184
185 pub(crate) fn sort_cue_list(&self) {
186 if let Some(cue_list) = self.cue_list.get() {
187 cue_list.sort();
188 }
189 }
190
191 pub(crate) fn set_text_track_mode(&self, cx: &mut JSContext, value: TextTrackMode) {
192 if self.mode.get() == value {
193 return;
194 }
195 self.mode.set(value);
196 if let Some(track_element) = self.associated_track.get() {
199 track_element.start_the_track_processing_model(cx);
200 }
201 }
202
203 pub(crate) fn rules_for_updating_the_text_track_rendering(
205 &self,
206 ) -> Option<RulesForUpdatingTheTextTrackRendering> {
207 self.rules_for_updating_the_text_track_rendering.get()
208 }
209
210 pub(crate) fn set_rules_for_updating_the_text_track_rendering(
212 &self,
213 rules: RulesForUpdatingTheTextTrackRendering,
214 ) {
215 debug_assert!(
216 self.rules_for_updating_the_text_track_rendering
217 .get()
218 .is_none()
219 );
220 self.rules_for_updating_the_text_track_rendering
221 .set(Some(rules));
222 }
223}
224
225impl TextTrackMethods<crate::DomTypeHolder> for TextTrack {
226 fn Kind(&self) -> TextTrackKind {
228 self.kind.get()
229 }
230
231 fn Label(&self) -> DOMString {
233 self.label.borrow().clone()
234 }
235
236 fn Language(&self) -> DOMString {
238 self.language.borrow().clone()
239 }
240
241 fn Id(&self) -> DOMString {
243 self.id.borrow().clone()
244 }
245
246 fn Mode(&self) -> TextTrackMode {
248 self.mode.get()
249 }
250
251 fn SetMode(&self, cx: &mut JSContext, value: TextTrackMode) {
253 self.set_text_track_mode(cx, value)
254 }
255
256 fn GetCues(&self, cx: &mut JSContext) -> Option<DomRoot<TextTrackCueList>> {
258 match self.Mode() {
259 TextTrackMode::Disabled => None,
260 _ => Some(self.text_track_cue_list(cx)),
261 }
262 }
263
264 fn GetActiveCues(&self, cx: &mut JSContext) -> Option<DomRoot<TextTrackCueList>> {
266 if self.mode.get() == TextTrackMode::Disabled {
268 return None;
269 }
270 Some(self.active_text_track_cue_list(cx))
274 }
275
276 fn AddCue(&self, cx: &mut JSContext, cue: &TextTrackCue) -> ErrorResult {
278 if let Some(rules_for_updating_the_text_track_rendering) =
283 self.rules_for_updating_the_text_track_rendering()
284 {
285 if rules_for_updating_the_text_track_rendering !=
289 cue.rules_for_updating_the_text_track_rendering()
290 {
291 return Err(Error::InvalidState(Some(
292 "Text cue rules of rendering do not match text track rules of rendering".into(),
293 )));
294 }
295 } else {
296 self.set_rules_for_updating_the_text_track_rendering(
299 cue.rules_for_updating_the_text_track_rendering(),
300 );
301 }
302
303 if let Some(old_track) = cue.get_text_track() {
306 if old_track.RemoveCue(cx, cue).is_err() {
310 warn!("Failed to remove cues for the added cue's text track");
311 }
312 }
313 cue.set_text_track(Some(self));
315 self.text_track_cue_list(cx).add(cx, cue);
316 Ok(())
317 }
318
319 fn RemoveCue(&self, cx: &mut JSContext, cue: &TextTrackCue) -> ErrorResult {
321 let cues = self.text_track_cue_list(cx);
323 let index = match cues.find(cue) {
324 Some(i) => Ok(i),
325 None => Err(Error::NotFound(None)),
326 }?;
327 cue.set_text_track(None);
329 cues.remove(index);
330 Ok(())
331 }
332
333 event_handler!(cuechange, GetOncuechange, SetOncuechange);
335}