aboutsummaryrefslogtreecommitdiffstats
path: root/components/script/dom
diff options
context:
space:
mode:
authorsreeise <reeisesean@gmail.com>2019-01-04 07:55:38 -0500
committersreeise <reeisesean@gmail.com>2019-03-03 09:04:50 -0500
commitcac4aa56f77c46db75c9ca1bfdf23be39cfd4604 (patch)
tree5b58d44fef2d68fb6031bf7c9a959d9e2b85eeb4 /components/script/dom
parent4d8d54fc00644204768886569959429dd67998a0 (diff)
downloadservo-cac4aa56f77c46db75c9ca1bfdf23be39cfd4604.tar.gz
servo-cac4aa56f77c46db75c9ca1bfdf23be39cfd4604.zip
Added AudioTrack, AudioTrackList, VideoTrack, VideoTrackList, and TrackEvent interfaces
Diffstat (limited to 'components/script/dom')
-rw-r--r--components/script/dom/audiotrack.rs97
-rw-r--r--components/script/dom/audiotracklist.rs126
-rw-r--r--components/script/dom/htmlmediaelement.rs121
-rw-r--r--components/script/dom/mod.rs5
-rw-r--r--components/script/dom/texttracklist.rs43
-rw-r--r--components/script/dom/trackevent.rs114
-rw-r--r--components/script/dom/videotrack.rs97
-rw-r--r--components/script/dom/videotracklist.rs164
-rw-r--r--components/script/dom/webidls/AudioTrack.webidl14
-rw-r--r--components/script/dom/webidls/AudioTrackList.webidl16
-rw-r--r--components/script/dom/webidls/HTMLMediaElement.webidl4
-rw-r--r--components/script/dom/webidls/TrackEvent.webidl15
-rw-r--r--components/script/dom/webidls/VideoTrack.webidl14
-rw-r--r--components/script/dom/webidls/VideoTrackList.webidl17
14 files changed, 839 insertions, 8 deletions
diff --git a/components/script/dom/audiotrack.rs b/components/script/dom/audiotrack.rs
new file mode 100644
index 00000000000..9233b1d9c93
--- /dev/null
+++ b/components/script/dom/audiotrack.rs
@@ -0,0 +1,97 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+use crate::dom::bindings::codegen::Bindings::AudioTrackBinding::{self, AudioTrackMethods};
+use crate::dom::bindings::reflector::{reflect_dom_object, Reflector};
+use crate::dom::bindings::root::DomRoot;
+use crate::dom::bindings::str::DOMString;
+use crate::dom::window::Window;
+use dom_struct::dom_struct;
+use std::cell::Cell;
+
+#[dom_struct]
+pub struct AudioTrack {
+ reflector_: Reflector,
+ id: DOMString,
+ kind: DOMString,
+ label: DOMString,
+ language: DOMString,
+ enabled: Cell<bool>,
+}
+
+impl AudioTrack {
+ pub fn new_inherited(
+ id: DOMString,
+ kind: DOMString,
+ label: DOMString,
+ language: DOMString,
+ ) -> AudioTrack {
+ AudioTrack {
+ reflector_: Reflector::new(),
+ id: id.into(),
+ kind: kind.into(),
+ label: label.into(),
+ language: language.into(),
+ enabled: Cell::new(false),
+ }
+ }
+
+ pub fn new(
+ window: &Window,
+ id: DOMString,
+ kind: DOMString,
+ label: DOMString,
+ language: DOMString,
+ ) -> DomRoot<AudioTrack> {
+ reflect_dom_object(
+ Box::new(AudioTrack::new_inherited(id, kind, label, language)),
+ window,
+ AudioTrackBinding::Wrap,
+ )
+ }
+
+ pub fn id(&self) -> DOMString {
+ self.id.clone()
+ }
+
+ pub fn enabled(&self) -> bool {
+ self.enabled.get()
+ }
+
+ pub fn set_enabled(&self, value: bool) {
+ self.enabled.set(value);
+ }
+}
+
+impl AudioTrackMethods for AudioTrack {
+ // https://html.spec.whatwg.org/multipage/#dom-audiotrack-id
+ fn Id(&self) -> DOMString {
+ self.id()
+ }
+
+ // https://html.spec.whatwg.org/multipage/#dom-audiotrack-kind
+ fn Kind(&self) -> DOMString {
+ self.kind.clone()
+ }
+
+ // https://html.spec.whatwg.org/multipage/#dom-audiotrack-label
+ fn Label(&self) -> DOMString {
+ self.label.clone()
+ }
+
+ // https://html.spec.whatwg.org/multipage/#dom-audiotrack-language
+ fn Language(&self) -> DOMString {
+ self.language.clone()
+ }
+
+ // https://html.spec.whatwg.org/multipage/#dom-audiotrack-enabled
+ fn Enabled(&self) -> bool {
+ self.enabled()
+ }
+
+ // https://html.spec.whatwg.org/multipage/#dom-audiotrack-enabled
+ fn SetEnabled(&self, value: bool) {
+ self.set_enabled(value);
+ }
+}
diff --git a/components/script/dom/audiotracklist.rs b/components/script/dom/audiotracklist.rs
new file mode 100644
index 00000000000..353c97c2598
--- /dev/null
+++ b/components/script/dom/audiotracklist.rs
@@ -0,0 +1,126 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+use crate::dom::audiotrack::AudioTrack;
+use crate::dom::bindings::cell::DomRefCell;
+use crate::dom::bindings::codegen::Bindings::AudioTrackListBinding::{self, AudioTrackListMethods};
+use crate::dom::bindings::inheritance::Castable;
+use crate::dom::bindings::refcounted::Trusted;
+use crate::dom::bindings::reflector::{reflect_dom_object, DomObject};
+use crate::dom::bindings::root::{Dom, DomRoot};
+use crate::dom::bindings::str::DOMString;
+use crate::dom::eventtarget::EventTarget;
+use crate::dom::window::Window;
+use crate::task_source::TaskSource;
+use dom_struct::dom_struct;
+
+#[dom_struct]
+pub struct AudioTrackList {
+ eventtarget: EventTarget,
+ tracks: DomRefCell<Vec<Dom<AudioTrack>>>,
+}
+
+impl AudioTrackList {
+ pub fn new_inherited(tracks: &[&AudioTrack]) -> AudioTrackList {
+ AudioTrackList {
+ eventtarget: EventTarget::new_inherited(),
+ tracks: DomRefCell::new(tracks.iter().map(|track| Dom::from_ref(&**track)).collect()),
+ }
+ }
+
+ pub fn new(window: &Window, tracks: &[&AudioTrack]) -> DomRoot<AudioTrackList> {
+ reflect_dom_object(
+ Box::new(AudioTrackList::new_inherited(tracks)),
+ window,
+ AudioTrackListBinding::Wrap,
+ )
+ }
+
+ pub fn len(&self) -> usize {
+ self.tracks.borrow().len()
+ }
+
+ pub fn item(&self, idx: usize) -> Option<DomRoot<AudioTrack>> {
+ self.tracks
+ .borrow()
+ .get(idx)
+ .map(|track| DomRoot::from_ref(&**track))
+ }
+
+ pub fn enabled_index(&self) -> Option<usize> {
+ self.tracks
+ .borrow()
+ .iter()
+ .position(|track| track.enabled())
+ }
+
+ // TODO(#22799) Integrate DOM Audio and Video track selection with media player.
+ pub fn set_enabled(&self, idx: usize, value: bool) {
+ let track = match self.item(idx) {
+ Some(t) => t,
+ None => return,
+ };
+
+ // If the chosen tracks enabled status is the same as the new status, return early.
+ if track.enabled() == value {
+ return;
+ }
+ // Set the tracks enabled status.
+ track.set_enabled(value);
+
+ // Queue a task to fire an event named change.
+ let global = &self.global();
+ let this = Trusted::new(self);
+ let (source, canceller) = global
+ .as_window()
+ .task_manager()
+ .media_element_task_source_with_canceller();
+
+ let _ = source.queue_with_canceller(
+ task!(media_track_change: move || {
+ let this = this.root();
+ this.upcast::<EventTarget>().fire_event(atom!("change"));
+ }),
+ &canceller,
+ );
+ }
+
+ pub fn add(&self, track: &AudioTrack) {
+ self.tracks.borrow_mut().push(Dom::from_ref(track));
+ }
+
+ pub fn clear(&self) {
+ self.tracks.borrow_mut().clear();
+ }
+}
+
+impl AudioTrackListMethods for AudioTrackList {
+ // https://html.spec.whatwg.org/multipage/#dom-audiotracklist-length
+ fn Length(&self) -> u32 {
+ self.len() as u32
+ }
+
+ // https://html.spec.whatwg.org/multipage/#dom-tracklist-item
+ fn IndexedGetter(&self, idx: u32) -> Option<DomRoot<AudioTrack>> {
+ self.item(idx as usize)
+ }
+
+ // https://html.spec.whatwg.org/multipage/#dom-audiotracklist-gettrackbyid
+ fn GetTrackById(&self, id: DOMString) -> Option<DomRoot<AudioTrack>> {
+ self.tracks
+ .borrow()
+ .iter()
+ .find(|track| track.id() == id)
+ .map(|track| DomRoot::from_ref(&**track))
+ }
+
+ // https://html.spec.whatwg.org/multipage/#handler-tracklist-onchange
+ event_handler!(change, GetOnchange, SetOnchange);
+
+ // https://html.spec.whatwg.org/multipage/#handler-tracklist-onaddtrack
+ event_handler!(addtrack, GetOnaddtrack, SetOnaddtrack);
+
+ // https://html.spec.whatwg.org/multipage/#handler-tracklist-onremovetrack
+ event_handler!(removetrack, GetOnremovetrack, SetOnremovetrack);
+}
diff --git a/components/script/dom/htmlmediaelement.rs b/components/script/dom/htmlmediaelement.rs
index c64bd26e57c..703ce9438a7 100644
--- a/components/script/dom/htmlmediaelement.rs
+++ b/components/script/dom/htmlmediaelement.rs
@@ -4,6 +4,8 @@
use crate::document_loader::{LoadBlocker, LoadType};
use crate::dom::attr::Attr;
+use crate::dom::audiotrack::AudioTrack;
+use crate::dom::audiotracklist::AudioTrackList;
use crate::dom::bindings::cell::DomRefCell;
use crate::dom::bindings::codegen::Bindings::AttrBinding::AttrMethods;
use crate::dom::bindings::codegen::Bindings::HTMLMediaElementBinding::CanPlayTypeResult;
@@ -15,6 +17,7 @@ use crate::dom::bindings::codegen::Bindings::MediaErrorBinding::MediaErrorMethod
use crate::dom::bindings::codegen::Bindings::TextTrackBinding::{TextTrackKind, TextTrackMode};
use crate::dom::bindings::codegen::InheritTypes::{ElementTypeId, HTMLElementTypeId};
use crate::dom::bindings::codegen::InheritTypes::{HTMLMediaElementTypeId, NodeTypeId};
+use crate::dom::bindings::codegen::UnionTypes::VideoTrackOrAudioTrackOrTextTrack;
use crate::dom::bindings::error::{Error, ErrorResult, Fallible};
use crate::dom::bindings::inheritance::Castable;
use crate::dom::bindings::num::Finite;
@@ -25,6 +28,7 @@ use crate::dom::bindings::str::{DOMString, USVString};
use crate::dom::blob::Blob;
use crate::dom::document::Document;
use crate::dom::element::{AttributeMutation, Element};
+use crate::dom::event::Event;
use crate::dom::eventtarget::EventTarget;
use crate::dom::globalscope::GlobalScope;
use crate::dom::htmlelement::HTMLElement;
@@ -37,6 +41,9 @@ use crate::dom::promise::Promise;
use crate::dom::texttrack::TextTrack;
use crate::dom::texttracklist::TextTrackList;
use crate::dom::timeranges::{TimeRanges, TimeRangesContainer};
+use crate::dom::trackevent::TrackEvent;
+use crate::dom::videotrack::VideoTrack;
+use crate::dom::videotracklist::VideoTrackList;
use crate::dom::virtualmethods::VirtualMethods;
use crate::fetch::FetchCanceller;
use crate::microtask::{Microtask, MicrotaskRunnable};
@@ -206,6 +213,10 @@ pub struct HTMLMediaElement {
/// https://html.spec.whatwg.org/multipage/#dom-media-played
#[ignore_malloc_size_of = "Rc"]
played: DomRefCell<TimeRangesContainer>,
+ // https://html.spec.whatwg.org/multipage/#dom-media-audiotracks
+ audio_tracks_list: MutNullableDom<AudioTrackList>,
+ // https://html.spec.whatwg.org/multipage/#dom-media-videotracks
+ video_tracks_list: MutNullableDom<VideoTrackList>,
/// https://html.spec.whatwg.org/multipage/#dom-media-texttracks
text_tracks_list: MutNullableDom<TextTrackList>,
/// Time of last timeupdate notification.
@@ -268,6 +279,8 @@ impl HTMLMediaElement {
seeking: Cell::new(false),
resource_url: DomRefCell::new(None),
played: DomRefCell::new(TimeRangesContainer::new()),
+ audio_tracks_list: Default::default(),
+ video_tracks_list: Default::default(),
text_tracks_list: Default::default(),
next_timeupdate_event: Cell::new(time::get_time() + Duration::milliseconds(250)),
current_fetch_context: DomRefCell::new(None),
@@ -803,7 +816,8 @@ impl HTMLMediaElement {
)));
// Step 2.
- // FIXME(nox): Forget the media-resource-specific tracks.
+ this.AudioTracks().clear();
+ this.VideoTracks().clear();
// Step 3.
this.network_state.set(NetworkState::NoSource);
@@ -904,7 +918,8 @@ impl HTMLMediaElement {
// FIXME(nox): Detach MediaSource media provider object.
// Step 6.4.
- // FIXME(nox): Forget the media-resource-specific tracks.
+ self.AudioTracks().clear();
+ self.VideoTracks().clear();
// Step 6.5.
if self.ready_state.get() != ReadyState::HaveNothing {
@@ -1227,6 +1242,92 @@ impl HTMLMediaElement {
},
PlayerEvent::MetadataUpdated(ref metadata) => {
// https://html.spec.whatwg.org/multipage/#media-data-processing-steps-list
+ // => If the media resource is found to have an audio track
+ if !metadata.audio_tracks.is_empty() {
+ for (i, _track) in metadata.audio_tracks.iter().enumerate() {
+ // Step 1.
+ let kind = match i {
+ 0 => DOMString::from("main"),
+ _ => DOMString::new(),
+ };
+ let window = window_from_node(self);
+ let audio_track = AudioTrack::new(
+ &window,
+ DOMString::new(),
+ kind,
+ DOMString::new(),
+ DOMString::new(),
+ );
+
+ // Steps 2. & 3.
+ self.AudioTracks().add(&audio_track);
+
+ // Step 4
+ // https://www.w3.org/TR/media-frags/#media-fragment-syntax
+ // https://github.com/servo/servo/issues/22366
+
+ // Step 5. & 6,
+ if self.AudioTracks().enabled_index().is_none() {
+ self.AudioTracks()
+ .set_enabled(self.AudioTracks().len() - 1, true);
+ }
+
+ // Steps 7.
+ let event = TrackEvent::new(
+ &self.global(),
+ atom!("addtrack"),
+ false,
+ false,
+ &Some(VideoTrackOrAudioTrackOrTextTrack::AudioTrack(audio_track)),
+ );
+
+ event.upcast::<Event>().fire(self.upcast::<EventTarget>());
+ }
+ }
+
+ // => If the media resource is found to have a video track
+ if !metadata.video_tracks.is_empty() {
+ for (i, _track) in metadata.video_tracks.iter().enumerate() {
+ // Step 1.
+ let kind = match i {
+ 0 => DOMString::from("main"),
+ _ => DOMString::new(),
+ };
+ let window = window_from_node(self);
+ let video_track = VideoTrack::new(
+ &window,
+ DOMString::new(),
+ kind,
+ DOMString::new(),
+ DOMString::new(),
+ );
+
+ // Steps 2. & 3.
+ self.VideoTracks().add(&video_track);
+
+ // Step 4.
+ // https://www.w3.org/TR/media-frags/#media-fragment-syntax
+ // https://github.com/servo/servo/issues/22366
+
+ // Step 5. & 6.
+ if self.VideoTracks().selected_index().is_none() {
+ self.VideoTracks()
+ .set_selected(self.VideoTracks().len() - 1, true);
+ }
+
+ // Steps 7.
+ let event = TrackEvent::new(
+ &self.global(),
+ atom!("addtrack"),
+ false,
+ false,
+ &Some(VideoTrackOrAudioTrackOrTextTrack::VideoTrack(video_track)),
+ );
+
+ event.upcast::<Event>().fire(self.upcast::<EventTarget>());
+ }
+ }
+
// => "Once enough of the media data has been fetched to determine the duration..."
// Step 1.
// servo-media owns the media timeline.
@@ -1287,7 +1388,7 @@ impl HTMLMediaElement {
// https://www.w3.org/TR/media-frags/#media-fragment-syntax
// https://github.com/servo/media/issues/156
- // XXX Steps 12 and 13 require audio and video tracks support.
+ // Step 12 & 13 are already handled by the earlier media track processing.
},
PlayerEvent::NeedData => {
// The player needs more data.
@@ -1702,6 +1803,20 @@ impl HTMLMediaElementMethods for HTMLMediaElement {
TimeRanges::new(self.global().as_window(), buffered)
}
+ // https://html.spec.whatwg.org/multipage/#dom-media-audiotracks
+ fn AudioTracks(&self) -> DomRoot<AudioTrackList> {
+ let window = window_from_node(self);
+ self.audio_tracks_list
+ .or_init(|| AudioTrackList::new(&window, &[]))
+ }
+
+ // https://html.spec.whatwg.org/multipage/#dom-media-videotracks
+ fn VideoTracks(&self) -> DomRoot<VideoTrackList> {
+ let window = window_from_node(self);
+ self.video_tracks_list
+ .or_init(|| VideoTrackList::new(&window, &[]))
+ }
+
// https://html.spec.whatwg.org/multipage/#dom-media-texttracks
fn TextTracks(&self) -> DomRoot<TextTrackList> {
let window = window_from_node(self);
diff --git a/components/script/dom/mod.rs b/components/script/dom/mod.rs
index a965b6893bc..b8f288a7e4e 100644
--- a/components/script/dom/mod.rs
+++ b/components/script/dom/mod.rs
@@ -225,6 +225,8 @@ pub mod audiolistener;
pub mod audionode;
pub mod audioparam;
pub mod audioscheduledsourcenode;
+pub mod audiotrack;
+pub mod audiotracklist;
pub mod baseaudiocontext;
pub mod beforeunloadevent;
pub mod bindings;
@@ -478,6 +480,7 @@ pub mod timeranges;
pub mod touch;
pub mod touchevent;
pub mod touchlist;
+pub mod trackevent;
pub mod transitionevent;
pub mod treewalker;
pub mod uievent;
@@ -488,6 +491,8 @@ pub mod userscripts;
pub mod validation;
pub mod validitystate;
pub mod values;
+pub mod videotrack;
+pub mod videotracklist;
pub mod virtualmethods;
pub mod vrdisplay;
pub mod vrdisplaycapabilities;
diff --git a/components/script/dom/texttracklist.rs b/components/script/dom/texttracklist.rs
index 220d012cdfd..df5e2d630ac 100644
--- a/components/script/dom/texttracklist.rs
+++ b/components/script/dom/texttracklist.rs
@@ -4,13 +4,18 @@
use crate::dom::bindings::cell::DomRefCell;
use crate::dom::bindings::codegen::Bindings::TextTrackListBinding::{self, TextTrackListMethods};
+use crate::dom::bindings::codegen::UnionTypes::VideoTrackOrAudioTrackOrTextTrack;
use crate::dom::bindings::inheritance::Castable;
-use crate::dom::bindings::reflector::reflect_dom_object;
+use crate::dom::bindings::refcounted::Trusted;
+use crate::dom::bindings::reflector::{reflect_dom_object, DomObject};
use crate::dom::bindings::root::{Dom, DomRoot};
use crate::dom::bindings::str::DOMString;
+use crate::dom::event::Event;
use crate::dom::eventtarget::EventTarget;
use crate::dom::texttrack::TextTrack;
+use crate::dom::trackevent::TrackEvent;
use crate::dom::window::Window;
+use crate::task_source::TaskSource;
use dom_struct::dom_struct;
#[dom_struct]
@@ -56,8 +61,40 @@ impl TextTrackList {
// Only add a track if it does not exist in the list
if self.find(track).is_none() {
self.dom_tracks.borrow_mut().push(Dom::from_ref(track));
- };
- self.upcast::<EventTarget>().fire_event(atom!("addtrack"));
+
+ let this = Trusted::new(self);
+ let (source, canceller) = &self
+ .global()
+ .as_window()
+ .task_manager()
+ .media_element_task_source_with_canceller();
+
+ let idx = match self.find(&track) {
+ Some(t) => t,
+ None => return,
+ };
+
+ let _ = source.queue_with_canceller(
+ task!(track_event_queue: move || {
+ let this = this.root();
+
+ if let Some(track) = this.item(idx) {
+ let event = TrackEvent::new(
+ &this.global(),
+ atom!("addtrack"),
+ false,
+ false,
+ &Some(VideoTrackOrAudioTrackOrTextTrack::TextTrack(
+ DomRoot::from_ref(&track)
+ )),
+ );
+
+ event.upcast::<Event>().fire(this.upcast::<EventTarget>());
+ }
+ }),
+ &canceller,
+ );
+ }
}
// FIXME(#22314, dlrobertson) allow TextTracks to be
diff --git a/components/script/dom/trackevent.rs b/components/script/dom/trackevent.rs
new file mode 100644
index 00000000000..684a0fcde52
--- /dev/null
+++ b/components/script/dom/trackevent.rs
@@ -0,0 +1,114 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+use crate::dom::audiotrack::AudioTrack;
+use crate::dom::bindings::codegen::Bindings::EventBinding::EventBinding::EventMethods;
+use crate::dom::bindings::codegen::Bindings::TrackEventBinding;
+use crate::dom::bindings::codegen::Bindings::TrackEventBinding::TrackEventMethods;
+use crate::dom::bindings::codegen::UnionTypes::VideoTrackOrAudioTrackOrTextTrack;
+use crate::dom::bindings::error::Fallible;
+use crate::dom::bindings::inheritance::Castable;
+use crate::dom::bindings::reflector::{reflect_dom_object, DomObject};
+use crate::dom::bindings::root::{Dom, DomRoot};
+use crate::dom::bindings::str::DOMString;
+use crate::dom::event::Event;
+use crate::dom::globalscope::GlobalScope;
+use crate::dom::texttrack::TextTrack;
+use crate::dom::videotrack::VideoTrack;
+use crate::dom::window::Window;
+use dom_struct::dom_struct;
+use servo_atoms::Atom;
+
+#[must_root]
+#[derive(JSTraceable, MallocSizeOf)]
+enum MediaTrack {
+ Video(Dom<VideoTrack>),
+ Audio(Dom<AudioTrack>),
+ Text(Dom<TextTrack>),
+}
+
+#[dom_struct]
+pub struct TrackEvent {
+ event: Event,
+ track: Option<MediaTrack>,
+}
+
+impl TrackEvent {
+ #[allow(unrooted_must_root)]
+ fn new_inherited(track: &Option<VideoTrackOrAudioTrackOrTextTrack>) -> TrackEvent {
+ let media_track = match track {
+ Some(VideoTrackOrAudioTrackOrTextTrack::VideoTrack(VideoTrack)) => {
+ Some(MediaTrack::Video(Dom::from_ref(VideoTrack)))
+ },
+ Some(VideoTrackOrAudioTrackOrTextTrack::AudioTrack(AudioTrack)) => {
+ Some(MediaTrack::Audio(Dom::from_ref(AudioTrack)))
+ },
+ Some(VideoTrackOrAudioTrackOrTextTrack::TextTrack(TextTrack)) => {
+ Some(MediaTrack::Text(Dom::from_ref(TextTrack)))
+ },
+ None => None,
+ };
+
+ TrackEvent {
+ event: Event::new_inherited(),
+ track: media_track,
+ }
+ }
+
+ pub fn new(
+ global: &GlobalScope,
+ type_: Atom,
+ bubbles: bool,
+ cancelable: bool,
+ track: &Option<VideoTrackOrAudioTrackOrTextTrack>,
+ ) -> DomRoot<TrackEvent> {
+ let te = reflect_dom_object(
+ Box::new(TrackEvent::new_inherited(&track)),
+ global,
+ TrackEventBinding::Wrap,
+ );
+ {
+ let event = te.upcast::<Event>();
+ event.init_event(type_, bubbles, cancelable);
+ }
+ te
+ }
+
+ pub fn Constructor(
+ window: &Window,
+ type_: DOMString,
+ init: &TrackEventBinding::TrackEventInit,
+ ) -> Fallible<DomRoot<TrackEvent>> {
+ Ok(TrackEvent::new(
+ &window.global(),
+ Atom::from(type_),
+ init.parent.bubbles,
+ init.parent.cancelable,
+ &init.track,
+ ))
+ }
+}
+
+impl TrackEventMethods for TrackEvent {
+ // https://html.spec.whatwg.org/multipage/#dom-trackevent-track
+ fn GetTrack(&self) -> Option<VideoTrackOrAudioTrackOrTextTrack> {
+ match &self.track {
+ Some(MediaTrack::Video(VideoTrack)) => Some(
+ VideoTrackOrAudioTrackOrTextTrack::VideoTrack(DomRoot::from_ref(VideoTrack)),
+ ),
+ Some(MediaTrack::Audio(AudioTrack)) => Some(
+ VideoTrackOrAudioTrackOrTextTrack::AudioTrack(DomRoot::from_ref(AudioTrack)),
+ ),
+ Some(MediaTrack::Text(TextTrack)) => Some(
+ VideoTrackOrAudioTrackOrTextTrack::TextTrack(DomRoot::from_ref(TextTrack)),
+ ),
+ None => None,
+ }
+ }
+
+ // https://dom.spec.whatwg.org/#dom-event-istrusted
+ fn IsTrusted(&self) -> bool {
+ self.event.IsTrusted()
+ }
+}
diff --git a/components/script/dom/videotrack.rs b/components/script/dom/videotrack.rs
new file mode 100644
index 00000000000..fd337db5e8a
--- /dev/null
+++ b/components/script/dom/videotrack.rs
@@ -0,0 +1,97 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+use crate::dom::bindings::codegen::Bindings::VideoTrackBinding::{self, VideoTrackMethods};
+use crate::dom::bindings::reflector::{reflect_dom_object, Reflector};
+use crate::dom::bindings::root::DomRoot;
+use crate::dom::bindings::str::DOMString;
+use crate::dom::window::Window;
+use dom_struct::dom_struct;
+use std::cell::Cell;
+
+#[dom_struct]
+pub struct VideoTrack {
+ reflector_: Reflector,
+ id: DOMString,
+ kind: DOMString,
+ label: DOMString,
+ language: DOMString,
+ selected: Cell<bool>,
+}
+
+impl VideoTrack {
+ pub fn new_inherited(
+ id: DOMString,
+ kind: DOMString,
+ label: DOMString,
+ language: DOMString,
+ ) -> VideoTrack {
+ VideoTrack {
+ reflector_: Reflector::new(),
+ id: id.into(),
+ kind: kind.into(),
+ label: label.into(),
+ language: language.into(),
+ selected: Cell::new(false),
+ }
+ }
+
+ pub fn new(
+ window: &Window,
+ id: DOMString,
+ kind: DOMString,
+ label: DOMString,
+ language: DOMString,
+ ) -> DomRoot<VideoTrack> {
+ reflect_dom_object(
+ Box::new(VideoTrack::new_inherited(id, kind, label, language)),
+ window,
+ VideoTrackBinding::Wrap,
+ )
+ }
+
+ pub fn id(&self) -> DOMString {
+ self.id.clone()
+ }
+
+ pub fn selected(&self) -> bool {
+ self.selected.get().clone()
+ }
+
+ pub fn set_selected(&self, value: bool) {
+ self.selected.set(value);
+ }
+}
+
+impl VideoTrackMethods for VideoTrack {
+ // https://html.spec.whatwg.org/multipage/#dom-videotrack-id
+ fn Id(&self) -> DOMString {
+ self.id()
+ }
+
+ // https://html.spec.whatwg.org/multipage/#dom-videotrack-kind
+ fn Kind(&self) -> DOMString {
+ self.kind.clone()
+ }
+
+ // https://html.spec.whatwg.org/multipage/#dom-videotrack-label
+ fn Label(&self) -> DOMString {
+ self.label.clone()
+ }
+
+ // https://html.spec.whatwg.org/multipage/#dom-videotrack-language
+ fn Language(&self) -> DOMString {
+ self.language.clone()
+ }
+
+ // https://html.spec.whatwg.org/multipage/#dom-videotrack-selected
+ fn Selected(&self) -> bool {
+ self.selected()
+ }
+
+ // https://html.spec.whatwg.org/multipage/#dom-videotrack-selected
+ fn SetSelected(&self, value: bool) {
+ self.set_selected(value);
+ }
+}
diff --git a/components/script/dom/videotracklist.rs b/components/script/dom/videotracklist.rs
new file mode 100644
index 00000000000..e495efcccd2
--- /dev/null
+++ b/components/script/dom/videotracklist.rs
@@ -0,0 +1,164 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+use crate::dom::bindings::cell::DomRefCell;
+use crate::dom::bindings::codegen::Bindings::VideoTrackListBinding::{self, VideoTrackListMethods};
+use crate::dom::bindings::inheritance::Castable;
+use crate::dom::bindings::refcounted::Trusted;
+use crate::dom::bindings::reflector::{reflect_dom_object, DomObject};
+use crate::dom::bindings::root::Dom;
+use crate::dom::bindings::root::DomRoot;
+use crate::dom::bindings::str::DOMString;
+use crate::dom::eventtarget::EventTarget;
+use crate::dom::videotrack::VideoTrack;
+use crate::dom::window::Window;
+use crate::task_source::TaskSource;
+use dom_struct::dom_struct;
+
+#[dom_struct]
+pub struct VideoTrackList {
+ eventtarget: EventTarget,
+ tracks: DomRefCell<Vec<Dom<VideoTrack>>>,
+}
+
+impl VideoTrackList {
+ pub fn new_inherited(tracks: &[&VideoTrack]) -> VideoTrackList {
+ VideoTrackList {
+ eventtarget: EventTarget::new_inherited(),
+ tracks: DomRefCell::new(tracks.iter().map(|track| Dom::from_ref(&**track)).collect()),
+ }
+ }
+
+ pub fn new(window: &Window, tracks: &[&VideoTrack]) -> DomRoot<VideoTrackList> {
+ reflect_dom_object(
+ Box::new(VideoTrackList::new_inherited(tracks)),
+ window,
+ VideoTrackListBinding::Wrap,
+ )
+ }
+
+ pub fn len(&self) -> usize {
+ self.tracks.borrow().len()
+ }
+
+ pub fn item(&self, idx: usize) -> Option<DomRoot<VideoTrack>> {
+ self.tracks
+ .borrow()
+ .get(idx)
+ .map(|track| DomRoot::from_ref(&**track))
+ }
+
+ pub fn selected_index(&self) -> Option<usize> {
+ self.tracks
+ .borrow()
+ .iter()
+ .position(|track| track.selected())
+ }
+
+ // TODO(#22799) Integrate DOM Audio and Video track selection with media player.
+ pub fn set_selected(&self, idx: usize, value: bool) {
+ let track = match self.item(idx) {
+ Some(t) => t,
+ None => return,
+ };
+
+ // If the chosen tracks selected status is the same as the new status, return early.
+ if track.selected() == value {
+ return;
+ }
+
+ let global = &self.global();
+ let this = Trusted::new(self);
+ let (source, canceller) = global
+ .as_window()
+ .task_manager()
+ .media_element_task_source_with_canceller();
+
+ if let Some(current) = self.selected_index() {
+ if current != idx {
+ self.tracks.borrow()[current].set_selected(false);
+ track.set_selected(true);
+
+ let _ = source.queue_with_canceller(
+ task!(media_track_change: move || {
+ let this = this.root();
+ this.upcast::<EventTarget>().fire_event(atom!("change"));
+ }),
+ &canceller,
+ );
+ } else {
+ self.tracks.borrow()[current].set_selected(false);
+
+ let _ = source.queue_with_canceller(
+ task!(media_track_change: move || {
+ let this = this.root();
+ this.upcast::<EventTarget>().fire_event(atom!("change"));
+ }),
+ &canceller,
+ );
+ }
+ } else {
+ track.set_selected(true);
+
+ let _ = source.queue_with_canceller(
+ task!(media_track_change: move || {
+ let this = this.root();
+ this.upcast::<EventTarget>().fire_event(atom!("change"));
+ }),
+ &canceller,
+ );
+ }
+ }
+
+ pub fn add(&self, track: &VideoTrack) {
+ self.tracks.borrow_mut().push(Dom::from_ref(track));
+ if track.selected() {
+ if let Some(idx) = self.selected_index() {
+ self.set_selected(idx, false);
+ }
+ }
+ }
+
+ pub fn clear(&self) {
+ self.tracks.borrow_mut().clear();
+ }
+}
+
+impl VideoTrackListMethods for VideoTrackList {
+ // https://html.spec.whatwg.org/multipage/#dom-videotracklist-length
+ fn Length(&self) -> u32 {
+ self.len() as u32
+ }
+
+ // https://html.spec.whatwg.org/multipage/#dom-tracklist-item
+ fn IndexedGetter(&self, idx: u32) -> Option<DomRoot<VideoTrack>> {
+ self.item(idx as usize)
+ }
+
+ // https://html.spec.whatwg.org/multipage/#dom-videotracklist-gettrackbyid
+ fn GetTrackById(&self, id: DOMString) -> Option<DomRoot<VideoTrack>> {
+ self.tracks
+ .borrow()
+ .iter()
+ .find(|track| track.id() == id)
+ .map(|track| DomRoot::from_ref(&**track))
+ }
+
+ // https://html.spec.whatwg.org/multipage/#dom-videotrack-selected
+ fn SelectedIndex(&self) -> i32 {
+ if let Some(idx) = self.selected_index() {
+ return idx as i32;
+ }
+ return -1;
+ }
+
+ // https://html.spec.whatwg.org/multipage/#handler-tracklist-onchange
+ event_handler!(change, GetOnchange, SetOnchange);
+
+ // https://html.spec.whatwg.org/multipage/#handler-tracklist-onaddtrack
+ event_handler!(addtrack, GetOnaddtrack, SetOnaddtrack);
+
+ // https://html.spec.whatwg.org/multipage/#handler-tracklist-onremovetrack
+ event_handler!(removetrack, GetOnremovetrack, SetOnremovetrack);
+}
diff --git a/components/script/dom/webidls/AudioTrack.webidl b/components/script/dom/webidls/AudioTrack.webidl
new file mode 100644
index 00000000000..2fa2ec9a5fa
--- /dev/null
+++ b/components/script/dom/webidls/AudioTrack.webidl
@@ -0,0 +1,14 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+// https://html.spec.whatwg.org/multipage/#audiotrack
+
+[Exposed=Window]
+interface AudioTrack {
+ readonly attribute DOMString id;
+ readonly attribute DOMString kind;
+ readonly attribute DOMString label;
+ readonly attribute DOMString language;
+ attribute boolean enabled;
+};
diff --git a/components/script/dom/webidls/AudioTrackList.webidl b/components/script/dom/webidls/AudioTrackList.webidl
new file mode 100644
index 00000000000..4428776972c
--- /dev/null
+++ b/components/script/dom/webidls/AudioTrackList.webidl
@@ -0,0 +1,16 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+// https://html.spec.whatwg.org/multipage/#audiotracklist
+
+[Exposed=Window]
+interface AudioTrackList : EventTarget {
+ readonly attribute unsigned long length;
+ getter AudioTrack (unsigned long index);
+ AudioTrack? getTrackById(DOMString id);
+
+ attribute EventHandler onchange;
+ attribute EventHandler onaddtrack;
+ attribute EventHandler onremovetrack;
+};
diff --git a/components/script/dom/webidls/HTMLMediaElement.webidl b/components/script/dom/webidls/HTMLMediaElement.webidl
index c932516773c..ba5669315d8 100644
--- a/components/script/dom/webidls/HTMLMediaElement.webidl
+++ b/components/script/dom/webidls/HTMLMediaElement.webidl
@@ -59,8 +59,8 @@ interface HTMLMediaElement : HTMLElement {
[CEReactions] attribute boolean defaultMuted;
// tracks
- // readonly attribute AudioTrackList audioTracks;
- // readonly attribute VideoTrackList videoTracks;
+ readonly attribute AudioTrackList audioTracks;
+ readonly attribute VideoTrackList videoTracks;
readonly attribute TextTrackList textTracks;
TextTrack addTextTrack(TextTrackKind kind, optional DOMString label = "", optional DOMString language = "");
};
diff --git a/components/script/dom/webidls/TrackEvent.webidl b/components/script/dom/webidls/TrackEvent.webidl
new file mode 100644
index 00000000000..214583e3245
--- /dev/null
+++ b/components/script/dom/webidls/TrackEvent.webidl
@@ -0,0 +1,15 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+// https://html.spec.whatwg.org/multipage/#the-trackevent-interface
+
+[Exposed=Window,
+ Constructor(DOMString type, optional TrackEventInit eventInitDict)]
+interface TrackEvent : Event {
+ readonly attribute (VideoTrack or AudioTrack or TextTrack)? track;
+};
+
+dictionary TrackEventInit : EventInit {
+ (VideoTrack or AudioTrack or TextTrack)? track = null;
+};
diff --git a/components/script/dom/webidls/VideoTrack.webidl b/components/script/dom/webidls/VideoTrack.webidl
new file mode 100644
index 00000000000..90d6c487eaa
--- /dev/null
+++ b/components/script/dom/webidls/VideoTrack.webidl
@@ -0,0 +1,14 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+// https://html.spec.whatwg.org/multipage/#videotrack
+
+[Exposed=Window]
+interface VideoTrack {
+ readonly attribute DOMString id;
+ readonly attribute DOMString kind;
+ readonly attribute DOMString label;
+ readonly attribute DOMString language;
+ attribute boolean selected;
+};
diff --git a/components/script/dom/webidls/VideoTrackList.webidl b/components/script/dom/webidls/VideoTrackList.webidl
new file mode 100644
index 00000000000..9c880f0d2b7
--- /dev/null
+++ b/components/script/dom/webidls/VideoTrackList.webidl
@@ -0,0 +1,17 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+// https://html.spec.whatwg.org/multipage/#videotracklist
+
+[Exposed=Window]
+interface VideoTrackList : EventTarget {
+ readonly attribute unsigned long length;
+ getter VideoTrack (unsigned long index);
+ VideoTrack? getTrackById(DOMString id);
+ readonly attribute long selectedIndex;
+
+ attribute EventHandler onchange;
+ attribute EventHandler onaddtrack;
+ attribute EventHandler onremovetrack;
+};