script/dom/webvtt/
texttrack.rs1use std::cell::{Cell, Ref};
6
7use dom_struct::dom_struct;
8use js::context::JSContext;
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::{Dom, DomRoot, MutNullableDom};
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::window::Window;
28
29#[dom_struct]
30pub(crate) struct TextTrack {
31 eventtarget: EventTarget,
32 kind: Cell<TextTrackKind>,
34 label: DomRefCell<DOMString>,
36 language: DomRefCell<DOMString>,
38 id: DomRefCell<DOMString>,
40 mode: Cell<TextTrackMode>,
42 cue_list: MutNullableDom<TextTrackCueList>,
44 track_list: DomRefCell<Option<Dom<TextTrackList>>>,
45 associated_track: DomRefCell<Option<Dom<HTMLTrackElement>>>,
46}
47
48impl TextTrack {
49 pub(crate) fn new_inherited(
50 id: DOMString,
51 kind: TextTrackKind,
52 label: DOMString,
53 language: DOMString,
54 mode: TextTrackMode,
55 track_list: Option<&TextTrackList>,
56 ) -> TextTrack {
57 TextTrack {
58 eventtarget: EventTarget::new_inherited(),
59 kind: Cell::new(kind),
60 label: DomRefCell::new(label),
61 language: DomRefCell::new(language),
62 id: DomRefCell::new(id),
63 mode: Cell::new(mode),
64 cue_list: Default::default(),
65 track_list: DomRefCell::new(track_list.map(Dom::from_ref)),
66 associated_track: Default::default(),
67 }
68 }
69
70 #[expect(clippy::too_many_arguments)]
71 pub(crate) fn new(
72 cx: &mut JSContext,
73 window: &Window,
74 id: DOMString,
75 kind: TextTrackKind,
76 label: DOMString,
77 language: DOMString,
78 mode: TextTrackMode,
79 track_list: Option<&TextTrackList>,
80 ) -> DomRoot<TextTrack> {
81 reflect_dom_object(
82 cx,
83 Box::new(TextTrack::new_inherited(
84 id, kind, label, language, mode, track_list,
85 )),
86 window,
87 )
88 }
89
90 pub(crate) fn get_cues(&self) -> Vec<DomRoot<TextTrackCue>> {
91 self.cue_list
92 .get()
93 .map(|list| list.cues())
94 .unwrap_or_default()
95 }
96
97 pub(crate) fn get_text_track_cue_list(&self, cx: &mut JSContext) -> DomRoot<TextTrackCueList> {
98 self.cue_list
99 .or_init(|| TextTrackCueList::new(cx, self, self.global().as_window(), &[]))
100 }
101
102 pub(crate) fn id(&self) -> Ref<'_, DOMString> {
103 self.id.borrow()
104 }
105
106 pub(crate) fn track_list(&self) -> Option<DomRoot<TextTrackList>> {
107 self.track_list
108 .borrow()
109 .as_ref()
110 .map(|track_list| track_list.as_rooted())
111 }
112
113 pub(crate) fn add_track_list(&self, track_list: &TextTrackList) {
114 *self.track_list.borrow_mut() = Some(Dom::from_ref(track_list));
115 }
116
117 pub(crate) fn remove_track_list(&self) {
118 *self.track_list.borrow_mut() = None;
119 }
120
121 pub(crate) fn associated_track(&self) -> Option<DomRoot<HTMLTrackElement>> {
122 self.associated_track
123 .borrow()
124 .as_ref()
125 .map(|track| DomRoot::from_ref(&**track))
126 }
127
128 pub(crate) fn set_associated_track(&self, track_element: &HTMLTrackElement) {
130 *self.associated_track.borrow_mut() = Some(Dom::from_ref(track_element));
131 self.update_attributes_from_track_element(track_element);
134 }
135
136 pub(crate) fn update_attributes_from_track_element(&self, track_element: &HTMLTrackElement) {
138 self.kind.set(match track_element.Kind().str().as_ref() {
143 "subtitles" => TextTrackKind::Subtitles,
144 "captions" => TextTrackKind::Captions,
145 "descriptions" => TextTrackKind::Descriptions,
146 "chapters" => TextTrackKind::Chapters,
147 "metadata" => TextTrackKind::Metadata,
148 _ => unreachable!("Must always have these specific kind states"),
149 });
150 *self.label.borrow_mut() = track_element.Label();
152 *self.language.borrow_mut() = track_element.Srclang();
155 *self.id.borrow_mut() = track_element
158 .upcast::<Element>()
159 .get_id()
160 .map(|value| DOMString::from(&*value))
161 .unwrap_or_default();
162 }
163
164 pub(crate) fn empty_cue_list(&self) {
165 if let Some(cue_list) = self.cue_list.get() {
166 cue_list.empty();
167 }
168 }
169
170 pub(crate) fn set_text_track_mode(&self, cx: &mut JSContext, value: TextTrackMode) {
171 if self.mode.get() == value {
172 return;
173 }
174 self.mode.set(value);
175 if let Some(track_element) = self.associated_track.borrow().as_ref() {
178 track_element.start_the_track_processing_model(cx);
179 }
180 }
181}
182
183impl TextTrackMethods<crate::DomTypeHolder> for TextTrack {
184 fn Kind(&self) -> TextTrackKind {
186 self.kind.get()
187 }
188
189 fn Label(&self) -> DOMString {
191 self.label.borrow().clone()
192 }
193
194 fn Language(&self) -> DOMString {
196 self.language.borrow().clone()
197 }
198
199 fn Id(&self) -> DOMString {
201 self.id.borrow().clone()
202 }
203
204 fn Mode(&self) -> TextTrackMode {
206 self.mode.get()
207 }
208
209 fn SetMode(&self, cx: &mut JSContext, value: TextTrackMode) {
211 self.set_text_track_mode(cx, value)
212 }
213
214 fn GetCues(&self, cx: &mut JSContext) -> Option<DomRoot<TextTrackCueList>> {
216 match self.Mode() {
217 TextTrackMode::Disabled => None,
218 _ => Some(self.get_text_track_cue_list(cx)),
219 }
220 }
221
222 fn GetActiveCues(&self, cx: &mut JSContext) -> Option<DomRoot<TextTrackCueList>> {
224 Some(TextTrackCueList::new(
227 cx,
228 self,
229 self.global().as_window(),
230 &[],
231 ))
232 }
233
234 fn AddCue(&self, cx: &mut JSContext, cue: &TextTrackCue) -> ErrorResult {
236 if let Some(old_track) = cue.get_text_track() {
239 if old_track.RemoveCue(cx, cue).is_err() {
243 warn!("Failed to remove cues for the added cue's text track");
244 }
245 }
246 cue.set_text_track(Some(self));
248 self.get_text_track_cue_list(cx).add(cx, cue);
249 Ok(())
250 }
251
252 fn RemoveCue(&self, cx: &mut JSContext, cue: &TextTrackCue) -> ErrorResult {
254 let cues = self.get_text_track_cue_list(cx);
256 let index = match cues.find(cue) {
257 Some(i) => Ok(i),
258 None => Err(Error::NotFound(None)),
259 }?;
260 cue.set_text_track(None);
262 cues.remove(index);
263 Ok(())
264 }
265
266 event_handler!(cuechange, GetOncuechange, SetOncuechange);
268}