Skip to main content

script/dom/webvtt/
texttrack.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5use 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    /// <https://html.spec.whatwg.org/multipage/#text-track-kind>
35    kind: Cell<TextTrackKind>,
36    /// <https://html.spec.whatwg.org/multipage/#text-track-label>
37    label: DomRefCell<TracedDOMString>,
38    /// <https://html.spec.whatwg.org/multipage/#text-track-language>
39    language: DomRefCell<TracedDOMString>,
40    /// <https://html.spec.whatwg.org/multipage/#text-track-identifier>
41    id: DomRefCell<TracedDOMString>,
42    /// <https://html.spec.whatwg.org/multipage/#text-track-mode>
43    mode: Cell<TextTrackMode>,
44    /// <https://html.spec.whatwg.org/multipage/#text-track-list-of-cues>
45    cue_list: MutNullableDom<TextTrackCueList>,
46    /// <https://html.spec.whatwg.org/multipage/#dom-texttrack-activecues>
47    active_cue_list: MutNullableDom<TextTrackCueList>,
48    track_list: MutNullableDom<TextTrackList>,
49    associated_track: MutNullableDom<HTMLTrackElement>,
50    /// <https://html.spec.whatwg.org/multipage/#rules-for-updating-the-text-track-rendering>
51    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    /// <https://html.spec.whatwg.org/multipage/#sourcing-out-of-band-text-tracks>
145    pub(crate) fn set_associated_track(&self, track_element: &HTMLTrackElement) {
146        self.associated_track.set(Some(track_element));
147        // > When a track element is created, it must be associated with
148        // > a new text track (with its value set as defined below).
149        self.update_attributes_from_track_element(track_element);
150    }
151
152    /// <https://html.spec.whatwg.org/multipage/#sourcing-out-of-band-text-tracks>
153    pub(crate) fn update_attributes_from_track_element(&self, track_element: &HTMLTrackElement) {
154        // > The text track kind is determined from the state of the
155        // > element's kind attribute according to the following table;
156        // > for a state given in a cell of the first column,
157        // > the kind is the string given in the second column:
158        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        // > The text track label is the element's track label.
167        *self.label.borrow_mut() = track_element.Label().traced();
168        // > The text track language is the element's track language,
169        // > if any; otherwise the empty string.
170        *self.language.borrow_mut() = track_element.Srclang().traced();
171        // > The text track identifier is the element's id attribute value,
172        // > if any; otherwise the empty string.
173        *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        // https://html.spec.whatwg.org/multipage/#sourcing-out-of-band-text-tracks:start-the-track-processing-model
199        // > The text track has its text track mode changed.
200        if let Some(track_element) = self.associated_track.get() {
201            track_element.start_the_track_processing_model(cx);
202        }
203    }
204
205    /// <https://html.spec.whatwg.org/multipage/#rules-for-updating-the-text-track-rendering>
206    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    /// <https://html.spec.whatwg.org/multipage/#rules-for-updating-the-text-track-rendering>
213    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    /// <https://html.spec.whatwg.org/multipage/#dom-texttrack-kind>
229    fn Kind(&self) -> TextTrackKind {
230        self.kind.get()
231    }
232
233    /// <https://html.spec.whatwg.org/multipage/#dom-texttrack-label>
234    fn Label(&self) -> DOMString {
235        self.label.borrow().clone().root()
236    }
237
238    /// <https://html.spec.whatwg.org/multipage/#dom-texttrack-language>
239    fn Language(&self) -> DOMString {
240        self.language.borrow().clone().root()
241    }
242
243    /// <https://html.spec.whatwg.org/multipage/#dom-texttrack-id>
244    fn Id(&self) -> DOMString {
245        self.id.borrow().clone().root()
246    }
247
248    /// <https://html.spec.whatwg.org/multipage/#dom-texttrack-mode>
249    fn Mode(&self) -> TextTrackMode {
250        self.mode.get()
251    }
252
253    /// <https://html.spec.whatwg.org/multipage/#dom-texttrack-mode>
254    fn SetMode(&self, cx: &mut JSContext, value: TextTrackMode) {
255        self.set_text_track_mode(cx, value)
256    }
257
258    /// <https://html.spec.whatwg.org/multipage/#dom-texttrack-cues>
259    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    /// <https://html.spec.whatwg.org/multipage/#dom-texttrack-activecues>
267    fn GetActiveCues(&self, cx: &mut JSContext) -> Option<DomRoot<TextTrackCueList>> {
268        // Step 1. If this's mode is the text track disabled mode, then return null.
269        if self.mode.get() == TextTrackMode::Disabled {
270            return None;
271        }
272        // Step 2. Return a live TextTrackCueList object that represents
273        // the subset of this's text track list of cues whose active flag
274        // was set when the script started, in text track cue order.
275        Some(self.active_text_track_cue_list(cx))
276    }
277
278    /// <https://html.spec.whatwg.org/multipage/#dom-texttrack-addcue>
279    fn AddCue(&self, cx: &mut JSContext, cue: &TextTrackCue) -> ErrorResult {
280        // Step 1. Let list be this's text track list of cues.
281        //
282        // We store the rules of rendering in `TextTrack` rather than in `TextTrackCueList`
283
284        if let Some(rules_for_updating_the_text_track_rendering) =
285            self.rules_for_updating_the_text_track_rendering()
286        {
287            // Step 3. If list's associated rules for updating the text track rendering are not
288            // the same rules for updating the text track rendering as appropriate for cue,
289            // then throw an "InvalidStateError" DOMException.
290            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            // Step 2. If list does not yet have any associated rules for updating the text track rendering,
299            // then associate list with the rules for updating the text track rendering appropriate to cue.
300            self.set_rules_for_updating_the_text_track_rendering(
301                cue.rules_for_updating_the_text_track_rendering(),
302            );
303        }
304
305        // Step 4. If the given cue is in a text track list of cues,
306        // then remove cue from that text track list of cues.
307        if let Some(old_track) = cue.get_text_track() {
308            // gecko calls RemoveCue when the given cue
309            // has an associated track, but doesn't return
310            // the error from it, so we wont either.
311            if old_track.RemoveCue(cx, cue).is_err() {
312                warn!("Failed to remove cues for the added cue's text track");
313            }
314        }
315        // Step 5. Add cue to list.
316        cue.set_text_track(Some(self));
317        self.text_track_cue_list(cx).add(cx, cue);
318        Ok(())
319    }
320
321    /// <https://html.spec.whatwg.org/multipage/#dom-texttrack-removecue>
322    fn RemoveCue(&self, cx: &mut JSContext, cue: &TextTrackCue) -> ErrorResult {
323        // Step 1
324        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        // Step 2
330        cue.set_text_track(None);
331        cues.remove(index);
332        Ok(())
333    }
334
335    // https://html.spec.whatwg.org/multipage/#handler-texttrack-oncuechange
336    event_handler!(cuechange, GetOncuechange, SetOncuechange);
337}