script/dom/webvtt/
texttracklist.rs1use 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 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 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 fn Length(&self, no_gc: &NoGC) -> u32 {
165 self.iter(no_gc).count() as u32
168 }
169
170 fn IndexedGetter(&self, no_gc: &NoGC, idx: u32) -> Option<DomRoot<TextTrack>> {
172 self.iter(no_gc)
176 .nth(idx as usize)
177 .map(|track| track.as_rooted())
178 }
179
180 fn GetTrackById(&self, no_gc: &NoGC, id: DOMString) -> Option<DomRoot<TextTrack>> {
182 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 event_handler!(change, GetOnchange, SetOnchange);
194
195 event_handler!(addtrack, GetOnaddtrack, SetOnaddtrack);
197
198 event_handler!(removetrack, GetOnremovetrack, SetOnremovetrack);
200}
201
202pub(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 if let Some(track_element) = self.track_elements.next() {
216 return Some(track_element.track(self.no_gc));
217 }
218 if let Some(dom_track) = self.dom_tracks.next() {
221 return Some(dom_track);
222 }
223 None
228 }
229}