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;
6
7use dom_struct::dom_struct;
8use js::context::JSContext;
9use script_bindings::cell::DomRefCell;
10use script_bindings::reflector::reflect_dom_object_with_cx;
11
12use crate::dom::bindings::codegen::Bindings::TextTrackBinding::{
13    TextTrackKind, TextTrackMethods, TextTrackMode,
14};
15use crate::dom::bindings::error::{Error, ErrorResult};
16use crate::dom::bindings::reflector::DomGlobal;
17use crate::dom::bindings::root::{Dom, DomRoot, MutNullableDom};
18use crate::dom::bindings::str::DOMString;
19use crate::dom::eventtarget::EventTarget;
20use crate::dom::html::htmltrackelement::HTMLTrackElement;
21use crate::dom::texttrackcue::TextTrackCue;
22use crate::dom::texttrackcuelist::TextTrackCueList;
23use crate::dom::texttracklist::TextTrackList;
24use crate::dom::window::Window;
25
26#[dom_struct]
27pub(crate) struct TextTrack {
28    eventtarget: EventTarget,
29    /// <https://html.spec.whatwg.org/multipage/#text-track-kind>
30    kind: TextTrackKind,
31    /// <https://html.spec.whatwg.org/multipage/#text-track-label>
32    label: String,
33    /// <https://html.spec.whatwg.org/multipage/#text-track-language>
34    language: String,
35    id: String,
36    /// <https://html.spec.whatwg.org/multipage/#text-track-mode>
37    mode: Cell<TextTrackMode>,
38    /// <https://html.spec.whatwg.org/multipage/#text-track-list-of-cues>
39    cue_list: MutNullableDom<TextTrackCueList>,
40    track_list: DomRefCell<Option<Dom<TextTrackList>>>,
41    associated_track: DomRefCell<Option<Dom<HTMLTrackElement>>>,
42}
43
44impl TextTrack {
45    pub(crate) fn new_inherited(
46        id: DOMString,
47        kind: TextTrackKind,
48        label: DOMString,
49        language: DOMString,
50        mode: TextTrackMode,
51        track_list: Option<&TextTrackList>,
52    ) -> TextTrack {
53        TextTrack {
54            eventtarget: EventTarget::new_inherited(),
55            kind,
56            label: label.into(),
57            language: language.into(),
58            id: id.into(),
59            mode: Cell::new(mode),
60            cue_list: Default::default(),
61            track_list: DomRefCell::new(track_list.map(Dom::from_ref)),
62            associated_track: Default::default(),
63        }
64    }
65
66    #[expect(clippy::too_many_arguments)]
67    pub(crate) fn new(
68        cx: &mut JSContext,
69        window: &Window,
70        id: DOMString,
71        kind: TextTrackKind,
72        label: DOMString,
73        language: DOMString,
74        mode: TextTrackMode,
75        track_list: Option<&TextTrackList>,
76    ) -> DomRoot<TextTrack> {
77        reflect_dom_object_with_cx(
78            Box::new(TextTrack::new_inherited(
79                id, kind, label, language, mode, track_list,
80            )),
81            window,
82            cx,
83        )
84    }
85
86    pub(crate) fn get_cues(&self, cx: &mut JSContext) -> DomRoot<TextTrackCueList> {
87        self.cue_list
88            .or_init(|| TextTrackCueList::new(cx, self.global().as_window(), &[]))
89    }
90
91    pub(crate) fn id(&self) -> &str {
92        &self.id
93    }
94
95    pub(crate) fn add_track_list(&self, track_list: &TextTrackList) {
96        *self.track_list.borrow_mut() = Some(Dom::from_ref(track_list));
97    }
98
99    pub(crate) fn remove_track_list(&self) {
100        *self.track_list.borrow_mut() = None;
101    }
102
103    pub(crate) fn associated_track(&self) -> Option<DomRoot<HTMLTrackElement>> {
104        self.associated_track
105            .borrow()
106            .as_ref()
107            .map(|track| DomRoot::from_ref(&**track))
108    }
109
110    pub(crate) fn set_associated_track(&self, track_element: &HTMLTrackElement) {
111        *self.associated_track.borrow_mut() = Some(Dom::from_ref(track_element));
112    }
113
114    pub(crate) fn empty_cue_list(&self) {
115        if let Some(cue_list) = self.cue_list.get() {
116            cue_list.empty();
117        }
118    }
119
120    pub(crate) fn set_text_track_mode(&self, cx: &mut JSContext, value: TextTrackMode) {
121        if self.mode.get() == value {
122            return;
123        }
124        self.mode.set(value);
125        // https://html.spec.whatwg.org/multipage/#sourcing-out-of-band-text-tracks:start-the-track-processing-model
126        // > The text track has its text track mode changed.
127        if let Some(track_element) = self.associated_track.borrow().as_ref() {
128            track_element.start_the_track_processing_model(cx);
129        }
130    }
131}
132
133impl TextTrackMethods<crate::DomTypeHolder> for TextTrack {
134    /// <https://html.spec.whatwg.org/multipage/#dom-texttrack-kind>
135    fn Kind(&self) -> TextTrackKind {
136        self.kind
137    }
138
139    /// <https://html.spec.whatwg.org/multipage/#dom-texttrack-label>
140    fn Label(&self) -> DOMString {
141        DOMString::from(self.label.clone())
142    }
143
144    /// <https://html.spec.whatwg.org/multipage/#dom-texttrack-language>
145    fn Language(&self) -> DOMString {
146        DOMString::from(self.language.clone())
147    }
148
149    /// <https://html.spec.whatwg.org/multipage/#dom-texttrack-id>
150    fn Id(&self) -> DOMString {
151        DOMString::from(self.id.clone())
152    }
153
154    /// <https://html.spec.whatwg.org/multipage/#dom-texttrack-mode>
155    fn Mode(&self) -> TextTrackMode {
156        self.mode.get()
157    }
158
159    /// <https://html.spec.whatwg.org/multipage/#dom-texttrack-mode>
160    fn SetMode(&self, cx: &mut JSContext, value: TextTrackMode) {
161        self.set_text_track_mode(cx, value)
162    }
163
164    /// <https://html.spec.whatwg.org/multipage/#dom-texttrack-cues>
165    fn GetCues(&self, cx: &mut JSContext) -> Option<DomRoot<TextTrackCueList>> {
166        match self.Mode() {
167            TextTrackMode::Disabled => None,
168            _ => Some(self.get_cues(cx)),
169        }
170    }
171
172    /// <https://html.spec.whatwg.org/multipage/#dom-texttrack-activecues>
173    fn GetActiveCues(&self, cx: &mut JSContext) -> Option<DomRoot<TextTrackCueList>> {
174        // XXX implement active cues logic
175        //      https://github.com/servo/servo/issues/22314
176        Some(TextTrackCueList::new(cx, self.global().as_window(), &[]))
177    }
178
179    /// <https://html.spec.whatwg.org/multipage/#dom-texttrack-addcue>
180    fn AddCue(&self, cx: &mut JSContext, cue: &TextTrackCue) -> ErrorResult {
181        // FIXME(#22314, dlrobertson) add Step 1 & 2
182        // Step 3
183        if let Some(old_track) = cue.get_track() {
184            // gecko calls RemoveCue when the given cue
185            // has an associated track, but doesn't return
186            // the error from it, so we wont either.
187            if old_track.RemoveCue(cx, cue).is_err() {
188                warn!("Failed to remove cues for the added cue's text track");
189            }
190        }
191        // Step 4
192        self.get_cues(cx).add(cue);
193        Ok(())
194    }
195
196    /// <https://html.spec.whatwg.org/multipage/#dom-texttrack-removecue>
197    fn RemoveCue(&self, cx: &mut JSContext, cue: &TextTrackCue) -> ErrorResult {
198        // Step 1
199        let cues = self.get_cues(cx);
200        let index = match cues.find(cue) {
201            Some(i) => Ok(i),
202            None => Err(Error::NotFound(None)),
203        }?;
204        // Step 2
205        cues.remove(index);
206        Ok(())
207    }
208
209    // https://html.spec.whatwg.org/multipage/#handler-texttrack-oncuechange
210    event_handler!(cuechange, GetOncuechange, SetOncuechange);
211}