Skip to main content

script/dom/webvtt/
texttracklist.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 dom_struct::dom_struct;
6use js::context::{JSContext, NoGC};
7use script_bindings::cell::DomRefCell;
8use script_bindings::reflector::reflect_dom_object;
9
10use crate::dom::bindings::codegen::Bindings::TextTrackListBinding::TextTrackListMethods;
11use crate::dom::bindings::codegen::UnionTypes::VideoTrackOrAudioTrackOrTextTrack;
12use crate::dom::bindings::inheritance::Castable;
13use crate::dom::bindings::refcounted::Trusted;
14use crate::dom::bindings::reflector::DomGlobal;
15use crate::dom::bindings::root::{Dom, DomRoot, UnrootedDom};
16use crate::dom::bindings::str::DOMString;
17use crate::dom::event::Event;
18use crate::dom::eventtarget::EventTarget;
19use crate::dom::html::htmlmediaelement::HTMLMediaElement;
20use crate::dom::html::htmltrackelement::HTMLTrackElement;
21use crate::dom::node::node::Node;
22use crate::dom::texttrack::TextTrack;
23use crate::dom::texttrackcue::TextTrackCue;
24use crate::dom::trackevent::TrackEvent;
25use crate::dom::window::Window;
26
27#[dom_struct]
28pub(crate) struct TextTrackList {
29    eventtarget: EventTarget,
30    /// <https://html.spec.whatwg.org/multipage/#list-of-text-tracks>
31    media_element: Dom<HTMLMediaElement>,
32    dom_tracks: DomRefCell<Vec<Dom<TextTrack>>>,
33}
34
35impl TextTrackList {
36    pub(crate) fn new_inherited(
37        media_element: &HTMLMediaElement,
38        tracks: &[&TextTrack],
39    ) -> TextTrackList {
40        TextTrackList {
41            eventtarget: EventTarget::new_inherited(),
42            media_element: Dom::from_ref(media_element),
43            dom_tracks: DomRefCell::new(tracks.iter().map(|g| Dom::from_ref(&**g)).collect()),
44        }
45    }
46
47    pub(crate) fn new(
48        cx: &mut JSContext,
49        media_element: &HTMLMediaElement,
50        window: &Window,
51        tracks: &[&TextTrack],
52    ) -> DomRoot<TextTrackList> {
53        reflect_dom_object(
54            cx,
55            Box::new(TextTrackList::new_inherited(media_element, tracks)),
56            window,
57        )
58    }
59
60    pub(crate) fn notify_media_element_for_added_cue(
61        &self,
62        cx: &mut JSContext,
63        cue: &TextTrackCue,
64    ) {
65        self.media_element.add_newly_added_cue(cx, cue);
66    }
67
68    pub(crate) fn add(&self, cx: &mut JSContext, track: &TextTrack) {
69        // We should only store the tracks created by `addTextTrack`
70        // since the iterator already traverses children of a media
71        // element for <track> elements. Otherwise, we would be double
72        // counting.
73        if track.associated_track().is_none() {
74            self.dom_tracks
75                .safe_borrow_mut(cx.no_gc())
76                .push(Dom::from_ref(track));
77        }
78
79        track.add_track_list(self);
80        self.media_element
81            .was_added_to_list_of_text_tracks(cx, track);
82
83        let this = Trusted::new(self);
84        let track = Trusted::new(track);
85        self.global()
86            .task_manager()
87            .media_element_task_source()
88            .queue(task!(track_event_queue: move |cx| {
89                let this = this.root();
90                let track = track.root();
91
92                let event = TrackEvent::new(
93                    cx,
94                    this.global().as_window(),
95                    atom!("addtrack"),
96                    false,
97                    false,
98                    &Some(VideoTrackOrAudioTrackOrTextTrack::TextTrack(
99                        DomRoot::from_ref(&track)
100                    )),
101                );
102
103                event.upcast::<Event>().fire(cx, this.upcast::<EventTarget>());
104            }));
105    }
106
107    pub(crate) fn remove(&self, track: &TextTrack) {
108        if let Some(idx) = self
109            .dom_tracks
110            .borrow()
111            .iter()
112            .position(|dom_track| &**dom_track == track)
113        {
114            self.dom_tracks.borrow_mut().remove(idx);
115        };
116        track.remove_track_list();
117
118        let this = Trusted::new(self);
119        let track = Trusted::new(track);
120        self.global()
121            .task_manager()
122            .media_element_task_source()
123            .queue(task!(track_event_queue: move |cx| {
124                let this = this.root();
125                let track = track.root();
126
127                let event = TrackEvent::new(
128                    cx,
129                    this.global().as_window(),
130                    atom!("removetrack"),
131                    false,
132                    false,
133                    &Some(VideoTrackOrAudioTrackOrTextTrack::TextTrack(
134                        DomRoot::from_ref(&track)
135                    )),
136                );
137
138                event.upcast::<Event>().fire(cx, this.upcast::<EventTarget>());
139            }));
140    }
141
142    pub(crate) fn iter<'a>(&'a self, no_gc: &'a NoGC) -> TextTrackListIterator<'a> {
143        TextTrackListIterator {
144            no_gc,
145            track_elements: Box::new(
146                self.media_element
147                    .upcast::<Node>()
148                    .children_unrooted(no_gc)
149                    .filter_map(UnrootedDom::downcast::<HTMLTrackElement>),
150            ),
151            dom_tracks: Box::new(
152                self.dom_tracks
153                    .borrow()
154                    .clone()
155                    .into_iter()
156                    .map(|track| track.as_unrooted(no_gc)),
157            ),
158        }
159    }
160}
161
162impl TextTrackListMethods<crate::DomTypeHolder> for TextTrackList {
163    /// <https://html.spec.whatwg.org/multipage/#dom-texttracklist-length>
164    fn Length(&self, no_gc: &NoGC) -> u32 {
165        // > The length attribute of a TextTrackList object must return
166        // > the number of text tracks in the list represented by the TextTrackList object.
167        self.iter(no_gc).count() as u32
168    }
169
170    /// <https://html.spec.whatwg.org/multipage/#dom-texttracklist-item>
171    fn IndexedGetter(&self, no_gc: &NoGC, idx: u32) -> Option<DomRoot<TextTrack>> {
172        // > To determine the value of an indexed property of a TextTrackList object
173        // > for a given index index, the user agent must return the indexth
174        // > text track in the list represented by the TextTrackList object.
175        self.iter(no_gc)
176            .nth(idx as usize)
177            .map(|track| track.as_rooted())
178    }
179
180    /// <https://html.spec.whatwg.org/multipage/#dom-texttracklist-gettrackbyid>
181    fn GetTrackById(&self, no_gc: &NoGC, id: DOMString) -> Option<DomRoot<TextTrack>> {
182        // > The getTrackById(id) method must return the first TextTrack in
183        // > the TextTrackList object whose id IDL attribute would return
184        // > a value equal to the value of the id argument.
185        // > When no tracks match the given argument, the method must return null.
186        let id_str = String::from(id);
187        self.iter(no_gc)
188            .find(|track| *track.id() == id_str)
189            .map(|track| track.as_rooted())
190    }
191
192    // https://html.spec.whatwg.org/multipage/#handler-texttracklist-onchange
193    event_handler!(change, GetOnchange, SetOnchange);
194
195    // https://html.spec.whatwg.org/multipage/#handler-texttracklist-onaddtrack
196    event_handler!(addtrack, GetOnaddtrack, SetOnaddtrack);
197
198    // https://html.spec.whatwg.org/multipage/#handler-texttracklist-onremovetrack
199    event_handler!(removetrack, GetOnremovetrack, SetOnremovetrack);
200}
201
202/// <https://html.spec.whatwg.org/multipage/#list-of-text-tracks>
203pub(crate) struct TextTrackListIterator<'a> {
204    no_gc: &'a NoGC,
205    track_elements: Box<dyn Iterator<Item = UnrootedDom<'a, HTMLTrackElement>> + 'a>,
206    dom_tracks: Box<dyn Iterator<Item = UnrootedDom<'a, TextTrack>> + 'a>,
207}
208
209impl<'a> Iterator for TextTrackListIterator<'a> {
210    type Item = UnrootedDom<'a, TextTrack>;
211
212    fn next(&mut self) -> Option<Self::Item> {
213        // > The text tracks are sorted as follows:
214        // Step 1. The text tracks corresponding to track element children of the media element, in tree order.
215        if let Some(track_element) = self.track_elements.next() {
216            return Some(track_element.track(self.no_gc));
217        }
218        // Step 2. Any text tracks added using the addTextTrack() method,
219        // in the order they were added, oldest first.
220        if let Some(dom_track) = self.dom_tracks.next() {
221            return Some(dom_track);
222        }
223        // Step 3. Any media-resource-specific text tracks
224        // (text tracks corresponding to data in the media resource),
225        // in the order defined by the media resource's format specification.
226        // TODO
227        None
228    }
229}