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;
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    /// <https://html.spec.whatwg.org/multipage/#text-track-kind>
33    kind: Cell<TextTrackKind>,
34    /// <https://html.spec.whatwg.org/multipage/#text-track-label>
35    label: DomRefCell<DOMString>,
36    /// <https://html.spec.whatwg.org/multipage/#text-track-language>
37    language: DomRefCell<DOMString>,
38    /// <https://html.spec.whatwg.org/multipage/#text-track-identifier>
39    id: DomRefCell<DOMString>,
40    /// <https://html.spec.whatwg.org/multipage/#text-track-mode>
41    mode: Cell<TextTrackMode>,
42    /// <https://html.spec.whatwg.org/multipage/#text-track-list-of-cues>
43    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    /// <https://html.spec.whatwg.org/multipage/#sourcing-out-of-band-text-tracks>
129    pub(crate) fn set_associated_track(&self, track_element: &HTMLTrackElement) {
130        *self.associated_track.borrow_mut() = Some(Dom::from_ref(track_element));
131        // > When a track element is created, it must be associated with
132        // > a new text track (with its value set as defined below).
133        self.update_attributes_from_track_element(track_element);
134    }
135
136    /// <https://html.spec.whatwg.org/multipage/#sourcing-out-of-band-text-tracks>
137    pub(crate) fn update_attributes_from_track_element(&self, track_element: &HTMLTrackElement) {
138        // > The text track kind is determined from the state of the
139        // > element's kind attribute according to the following table;
140        // > for a state given in a cell of the first column,
141        // > the kind is the string given in the second column:
142        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        // > The text track label is the element's track label.
151        *self.label.borrow_mut() = track_element.Label();
152        // > The text track language is the element's track language,
153        // > if any; otherwise the empty string.
154        *self.language.borrow_mut() = track_element.Srclang();
155        // > The text track identifier is the element's id attribute value,
156        // > if any; otherwise the empty string.
157        *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        // https://html.spec.whatwg.org/multipage/#sourcing-out-of-band-text-tracks:start-the-track-processing-model
176        // > The text track has its text track mode changed.
177        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    /// <https://html.spec.whatwg.org/multipage/#dom-texttrack-kind>
185    fn Kind(&self) -> TextTrackKind {
186        self.kind.get()
187    }
188
189    /// <https://html.spec.whatwg.org/multipage/#dom-texttrack-label>
190    fn Label(&self) -> DOMString {
191        self.label.borrow().clone()
192    }
193
194    /// <https://html.spec.whatwg.org/multipage/#dom-texttrack-language>
195    fn Language(&self) -> DOMString {
196        self.language.borrow().clone()
197    }
198
199    /// <https://html.spec.whatwg.org/multipage/#dom-texttrack-id>
200    fn Id(&self) -> DOMString {
201        self.id.borrow().clone()
202    }
203
204    /// <https://html.spec.whatwg.org/multipage/#dom-texttrack-mode>
205    fn Mode(&self) -> TextTrackMode {
206        self.mode.get()
207    }
208
209    /// <https://html.spec.whatwg.org/multipage/#dom-texttrack-mode>
210    fn SetMode(&self, cx: &mut JSContext, value: TextTrackMode) {
211        self.set_text_track_mode(cx, value)
212    }
213
214    /// <https://html.spec.whatwg.org/multipage/#dom-texttrack-cues>
215    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    /// <https://html.spec.whatwg.org/multipage/#dom-texttrack-activecues>
223    fn GetActiveCues(&self, cx: &mut JSContext) -> Option<DomRoot<TextTrackCueList>> {
224        // XXX implement active cues logic
225        //      https://github.com/servo/servo/issues/22314
226        Some(TextTrackCueList::new(
227            cx,
228            self,
229            self.global().as_window(),
230            &[],
231        ))
232    }
233
234    /// <https://html.spec.whatwg.org/multipage/#dom-texttrack-addcue>
235    fn AddCue(&self, cx: &mut JSContext, cue: &TextTrackCue) -> ErrorResult {
236        // FIXME(#22314, dlrobertson) add Step 1 & 2
237        // Step 3
238        if let Some(old_track) = cue.get_text_track() {
239            // gecko calls RemoveCue when the given cue
240            // has an associated track, but doesn't return
241            // the error from it, so we wont either.
242            if old_track.RemoveCue(cx, cue).is_err() {
243                warn!("Failed to remove cues for the added cue's text track");
244            }
245        }
246        // Step 4
247        cue.set_text_track(Some(self));
248        self.get_text_track_cue_list(cx).add(cx, cue);
249        Ok(())
250    }
251
252    /// <https://html.spec.whatwg.org/multipage/#dom-texttrack-removecue>
253    fn RemoveCue(&self, cx: &mut JSContext, cue: &TextTrackCue) -> ErrorResult {
254        // Step 1
255        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        // Step 2
261        cue.set_text_track(None);
262        cues.remove(index);
263        Ok(())
264    }
265
266    // https://html.spec.whatwg.org/multipage/#handler-texttrack-oncuechange
267    event_handler!(cuechange, GetOncuechange, SetOncuechange);
268}