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