From 9aaa42b0615caa051139d3a892872aedc74e3e90 Mon Sep 17 00:00:00 2001 From: Anthony Ramine Date: Tue, 19 Sep 2017 23:28:49 +0200 Subject: [PATCH 1/4] Remove some obsolete comments --- components/script/dom/htmlmediaelement.rs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/components/script/dom/htmlmediaelement.rs b/components/script/dom/htmlmediaelement.rs index 3700a64364e..fc1193e198b 100644 --- a/components/script/dom/htmlmediaelement.rs +++ b/components/script/dom/htmlmediaelement.rs @@ -47,10 +47,8 @@ use time::{self, Timespec, Duration}; pub struct HTMLMediaElement { htmlelement: HTMLElement, /// https://html.spec.whatwg.org/multipage/#dom-media-networkstate - // FIXME(nox): Use an enum. network_state: Cell, /// https://html.spec.whatwg.org/multipage/#dom-media-readystate - // FIXME(nox): Use an enum. ready_state: Cell, /// https://html.spec.whatwg.org/multipage/#dom-media-currentsrc current_src: DOMRefCell, @@ -291,9 +289,6 @@ impl HTMLMediaElement { &window, ); } - - // TODO Step 2: Media controller. - // FIXME(nox): There is no step 2 in the spec. } // https://html.spec.whatwg.org/multipage/#concept-media-load-algorithm @@ -327,7 +322,6 @@ impl HTMLMediaElement { } // https://html.spec.whatwg.org/multipage/#concept-media-load-algorithm - // FIXME(nox): Why does this need to be passed the base URL? fn resource_selection_algorithm_sync(&self, base_url: ServoUrl) { // Step 5. // FIXME(nox): Maybe populate the list of pending text tracks. From 6c5fe041d7afd22f38aa8516617a0f7e8aa8e5c4 Mon Sep 17 00:00:00 2001 From: Anthony Ramine Date: Wed, 20 Sep 2017 16:11:26 +0200 Subject: [PATCH 2/4] Ignore media-related tasks when starting the media load algorithm --- components/script/dom/htmlmediaelement.rs | 35 +++++++++++++------ .../load-removes-queued-error-event.html.ini | 3 -- 2 files changed, 25 insertions(+), 13 deletions(-) diff --git a/components/script/dom/htmlmediaelement.rs b/components/script/dom/htmlmediaelement.rs index fc1193e198b..ba6b7156d62 100644 --- a/components/script/dom/htmlmediaelement.rs +++ b/components/script/dom/htmlmediaelement.rs @@ -52,7 +52,7 @@ pub struct HTMLMediaElement { ready_state: Cell, /// https://html.spec.whatwg.org/multipage/#dom-media-currentsrc current_src: DOMRefCell, - // FIXME(nox): Document this one, I have no idea what it is used for. + /// Incremented whenever tasks associated with this element are cancelled. generation_id: Cell, /// https://html.spec.whatwg.org/multipage/#fire-loadeddata /// @@ -149,17 +149,22 @@ impl HTMLMediaElement { // Step 2.3. let window = window_from_node(self); - let target = Trusted::new(self.upcast::()); + let this = Trusted::new(self); + let generation_id = self.generation_id.get(); // FIXME(nox): Why are errors silenced here? + // FIXME(nox): Media element event task source should be used here. let _ = window.dom_manipulation_task_source().queue( task!(internal_pause_steps: move || { - let target = target.root(); + let this = this.root(); + if generation_id != this.generation_id.get() { + return; + } // Step 2.3.1. - target.fire_event(atom!("timeupdate")); + this.upcast::().fire_event(atom!("timeupdate")); // Step 2.3.2. - target.fire_event(atom!("pause")); + this.upcast::().fire_event(atom!("pause")); // Step 2.3.3. // FIXME(nox): Reject pending play promises with promises @@ -180,15 +185,20 @@ impl HTMLMediaElement { // TODO(nox): Take pending play promises and let promises be the result. // Step 2. - let target = Trusted::new(self.upcast::()); let window = window_from_node(self); + let this = Trusted::new(self); + let generation_id = self.generation_id.get(); // FIXME(nox): Why are errors silenced here? + // FIXME(nox): Media element event task source should be used here. let _ = window.dom_manipulation_task_source().queue( task!(notify_about_playing: move || { - let target = target.root(); + let this = this.root(); + if generation_id != this.generation_id.get() { + return; + } // Step 2.1. - target.fire_event(atom!("playing")); + this.upcast::().fire_event(atom!("playing")); // Step 2.2. // FIXME(nox): Resolve pending play promises with promises. @@ -481,16 +491,21 @@ impl HTMLMediaElement { } } - /// Queues the [dedicated media source failure steps][steps]. + /// Queues a task to run the [dedicated media source failure steps][steps]. /// /// [steps]: https://html.spec.whatwg.org/multipage/#dedicated-media-source-failure-steps fn queue_dedicated_media_source_failure_steps(&self) { - let this = Trusted::new(self); let window = window_from_node(self); + let this = Trusted::new(self); + let generation_id = self.generation_id.get(); // FIXME(nox): Why are errors silenced here? + // FIXME(nox): Media element event task source should be used here. let _ = window.dom_manipulation_task_source().queue( task!(dedicated_media_source_failure_steps: move || { let this = this.root(); + if generation_id != this.generation_id.get() { + return; + } // Step 1. this.error.set(Some(&*MediaError::new( diff --git a/tests/wpt/metadata/html/semantics/embedded-content/media-elements/loading-the-media-resource/load-removes-queued-error-event.html.ini b/tests/wpt/metadata/html/semantics/embedded-content/media-elements/loading-the-media-resource/load-removes-queued-error-event.html.ini index 62d34da092e..731fe16fc1b 100644 --- a/tests/wpt/metadata/html/semantics/embedded-content/media-elements/loading-the-media-resource/load-removes-queued-error-event.html.ini +++ b/tests/wpt/metadata/html/semantics/embedded-content/media-elements/loading-the-media-resource/load-removes-queued-error-event.html.ini @@ -1,9 +1,6 @@ [load-removes-queued-error-event.html] type: testharness expected: TIMEOUT - [video error event] - expected: FAIL - [source error event] expected: TIMEOUT From 49dd04cd8b6beb4cc44991f5bbf945f6c3fa8fd5 Mon Sep 17 00:00:00 2001 From: Anthony Ramine Date: Thu, 21 Sep 2017 00:12:28 +0200 Subject: [PATCH 3/4] Return a promise from HTMLMediaElement::Play --- components/script/dom/bindings/trace.rs | 2 + components/script/dom/htmlmediaelement.rs | 294 ++++++++++++------ .../dom/webidls/HTMLMediaElement.webidl | 2 +- 3 files changed, 201 insertions(+), 97 deletions(-) diff --git a/components/script/dom/bindings/trace.rs b/components/script/dom/bindings/trace.rs index ed70a9657f9..ab2b20b97c8 100644 --- a/components/script/dom/bindings/trace.rs +++ b/components/script/dom/bindings/trace.rs @@ -39,6 +39,7 @@ use cssparser::RGBA; use devtools_traits::{CSSError, TimelineMarkerType, WorkerId}; use dom::abstractworker::SharedRt; use dom::bindings::cell::DOMRefCell; +use dom::bindings::error::Error; use dom::bindings::js::{JS, Root}; use dom::bindings::refcounted::{Trusted, TrustedPromise}; use dom::bindings::reflector::{DomObject, Reflector}; @@ -337,6 +338,7 @@ unsafe impl JSTraceable for (A, unsafe_no_jsmanaged_fields!(bool, f32, f64, String, AtomicBool, AtomicUsize, Uuid, char); unsafe_no_jsmanaged_fields!(usize, u8, u16, u32, u64); unsafe_no_jsmanaged_fields!(isize, i8, i16, i32, i64); +unsafe_no_jsmanaged_fields!(Error); unsafe_no_jsmanaged_fields!(ServoUrl, ImmutableOrigin, MutableOrigin); unsafe_no_jsmanaged_fields!(Image, ImageMetadata, ImageCache, PendingImageId); unsafe_no_jsmanaged_fields!(Metadata); diff --git a/components/script/dom/htmlmediaelement.rs b/components/script/dom/htmlmediaelement.rs index ba6b7156d62..bcceffc11d4 100644 --- a/components/script/dom/htmlmediaelement.rs +++ b/components/script/dom/htmlmediaelement.rs @@ -14,6 +14,7 @@ use dom::bindings::codegen::Bindings::MediaErrorBinding::MediaErrorConstants::*; use dom::bindings::codegen::Bindings::MediaErrorBinding::MediaErrorMethods; use dom::bindings::codegen::InheritTypes::{ElementTypeId, HTMLElementTypeId}; use dom::bindings::codegen::InheritTypes::{HTMLMediaElementTypeId, NodeTypeId}; +use dom::bindings::error::{Error, ErrorResult}; use dom::bindings::inheritance::Castable; use dom::bindings::js::{MutNullableJS, Root}; use dom::bindings::refcounted::Trusted; @@ -26,6 +27,7 @@ use dom::htmlelement::HTMLElement; use dom::htmlsourceelement::HTMLSourceElement; use dom::mediaerror::MediaError; use dom::node::{window_from_node, document_from_node, Node, UnbindContext}; +use dom::promise::Promise; use dom::virtualmethods::VirtualMethods; use dom_struct::dom_struct; use html5ever::{LocalName, Prefix}; @@ -39,11 +41,16 @@ use network_listener::{NetworkListener, PreInvoke}; use script_thread::ScriptThread; use servo_url::ServoUrl; use std::cell::Cell; +use std::collections::VecDeque; +use std::mem; +use std::rc::Rc; use std::sync::{Arc, Mutex}; use task_source::TaskSource; use time::{self, Timespec, Duration}; #[dom_struct] +// FIXME(nox): A lot of tasks queued for this element should probably be in the +// media element event task source. pub struct HTMLMediaElement { htmlelement: HTMLElement, /// https://html.spec.whatwg.org/multipage/#dom-media-networkstate @@ -64,6 +71,12 @@ pub struct HTMLMediaElement { paused: Cell, /// https://html.spec.whatwg.org/multipage/#attr-media-autoplay autoplaying: Cell, + /// https://html.spec.whatwg.org/multipage/#list-of-pending-play-promises + #[ignore_heap_size_of = "promises are hard"] + pending_play_promises: DOMRefCell>>, + /// Play promises which are soon to be fulfilled by a queued task. + #[ignore_heap_size_of = "promises are hard"] + in_flight_play_promises_queue: DOMRefCell]>, ErrorResult)>>, /// The details of the video currently related to this media element. // FIXME(nox): Why isn't this in HTMLVideoElement? video: DOMRefCell>, @@ -118,6 +131,8 @@ impl HTMLMediaElement { paused: Cell::new(true), // FIXME(nox): Why is this initialised to true? autoplaying: Cell::new(true), + pending_play_promises: Default::default(), + in_flight_play_promises_queue: Default::default(), video: DOMRefCell::new(None), } } @@ -133,6 +148,87 @@ impl HTMLMediaElement { } } + /// https://html.spec.whatwg.org/multipage/#dom-media-play + // FIXME(nox): Move this back to HTMLMediaElementMethods::Play once + // Rc doesn't require #[allow(unrooted_must_root)] anymore. + fn play(&self, promise: &Rc) { + // Step 1. + // FIXME(nox): Reject promise if not allowed to play. + + // Step 2. + if self.error.get().map_or(false, |e| e.Code() == MEDIA_ERR_SRC_NOT_SUPPORTED) { + promise.reject_error(Error::NotSupported); + return; + } + + // Step 3. + self.push_pending_play_promise(promise); + + // Step 4. + if self.network_state.get() == NetworkState::Empty { + self.invoke_resource_selection_algorithm(); + } + + // Step 5. + // FIXME(nox): Seek to earliest possible position if playback has ended + // and direction of playback is forwards. + + let state = self.ready_state.get(); + + let window = window_from_node(self); + let task_source = window.dom_manipulation_task_source(); + if self.Paused() { + // Step 6.1. + self.paused.set(false); + + // Step 6.2. + // FIXME(nox): Set show poster flag to false and run time marches on + // steps if show poster flag is true. + + // Step 6.3. + task_source.queue_simple_event(self.upcast(), atom!("play"), &window); + + // Step 6.4. + match state { + ReadyState::HaveNothing | + ReadyState::HaveMetadata | + ReadyState::HaveCurrentData => { + task_source.queue_simple_event( + self.upcast(), + atom!("waiting"), + &window, + ); + }, + ReadyState::HaveFutureData | + ReadyState::HaveEnoughData => { + self.notify_about_playing(); + } + } + } else if state == ReadyState::HaveFutureData || state == ReadyState::HaveEnoughData { + // Step 7. + self.take_pending_play_promises(Ok(())); + let this = Trusted::new(self); + let generation_id = self.generation_id.get(); + task_source.queue( + task!(resolve_pending_play_promises: move || { + let this = this.root(); + if generation_id != this.generation_id.get() { + return; + } + + this.fulfill_in_flight_play_promises(|| ()); + }), + window.upcast(), + ).unwrap(); + } + + // Step 8. + self.autoplaying.set(false); + + // Step 9. + // Not applicable here, the promise is returned from Play. + } + /// https://html.spec.whatwg.org/multipage/#internal-pause-steps fn internal_pause_steps(&self) { // Step 1. @@ -144,8 +240,7 @@ impl HTMLMediaElement { self.paused.set(true); // Step 2.2. - // FIXME(nox): Take pending play promises and let promises be the - // result. + self.take_pending_play_promises(Err(Error::Abort)); // Step 2.3. let window = window_from_node(self); @@ -160,15 +255,17 @@ impl HTMLMediaElement { return; } - // Step 2.3.1. - this.upcast::().fire_event(atom!("timeupdate")); + this.fulfill_in_flight_play_promises(|| { + // Step 2.3.1. + this.upcast::().fire_event(atom!("timeupdate")); - // Step 2.3.2. - this.upcast::().fire_event(atom!("pause")); + // Step 2.3.2. + this.upcast::().fire_event(atom!("pause")); - // Step 2.3.3. - // FIXME(nox): Reject pending play promises with promises - // and an "AbortError" DOMException. + // Step 2.3.3. + // Done after running this closure in + // `fulfill_in_flight_play_promises`. + }); }), window.upcast(), ); @@ -182,7 +279,7 @@ impl HTMLMediaElement { // https://html.spec.whatwg.org/multipage/#notify-about-playing fn notify_about_playing(&self) { // Step 1. - // TODO(nox): Take pending play promises and let promises be the result. + self.take_pending_play_promises(Ok(())); // Step 2. let window = window_from_node(self); @@ -197,11 +294,15 @@ impl HTMLMediaElement { return; } - // Step 2.1. - this.upcast::().fire_event(atom!("playing")); + this.fulfill_in_flight_play_promises(|| { + // Step 2.1. + this.upcast::().fire_event(atom!("playing")); + + // Step 2.2. + // Done after running this closure in + // `fulfill_in_flight_play_promises`. + }); - // Step 2.2. - // FIXME(nox): Resolve pending play promises with promises. }), window.upcast(), ); @@ -498,6 +599,7 @@ impl HTMLMediaElement { let window = window_from_node(self); let this = Trusted::new(self); let generation_id = self.generation_id.get(); + self.take_pending_play_promises(Err(Error::NotSupported)); // FIXME(nox): Why are errors silenced here? // FIXME(nox): Media element event task source should be used here. let _ = window.dom_manipulation_task_source().queue( @@ -507,26 +609,29 @@ impl HTMLMediaElement { return; } - // Step 1. - this.error.set(Some(&*MediaError::new( - &window_from_node(&*this), - MEDIA_ERR_SRC_NOT_SUPPORTED, - ))); + this.fulfill_in_flight_play_promises(|| { + // Step 1. + this.error.set(Some(&*MediaError::new( + &window_from_node(&*this), + MEDIA_ERR_SRC_NOT_SUPPORTED, + ))); - // Step 2. - // FIXME(nox): Forget the media-resource-specific tracks. + // Step 2. + // FIXME(nox): Forget the media-resource-specific tracks. - // Step 3. - this.network_state.set(NetworkState::NoSource); + // Step 3. + this.network_state.set(NetworkState::NoSource); - // Step 4. - // FIXME(nox): Set show poster flag to true. + // Step 4. + // FIXME(nox): Set show poster flag to true. - // Step 5. - this.upcast::().fire_event(atom!("error")); + // Step 5. + this.upcast::().fire_event(atom!("error")); - // Step 6. - // FIXME(nox): Reject pending play promises. + // Step 6. + // Done after running this closure in + // `fulfill_in_flight_play_promises`. + }); // Step 7. // FIXME(nox): Set the delaying-the-load-event flag to false. @@ -546,9 +651,10 @@ impl HTMLMediaElement { // resource selection algorithm. // Steps 2-4. - // FIXME(nox): Cancel all tasks related to this element and resolve or - // reject all pending play promises. self.generation_id.set(self.generation_id.get() + 1); + while !self.in_flight_play_promises_queue.borrow().is_empty() { + self.fulfill_in_flight_play_promises(|| ()); + } let window = window_from_node(self); let task_source = window.dom_manipulation_task_source(); @@ -584,7 +690,8 @@ impl HTMLMediaElement { self.paused.set(true); // Step 6.6.2. - // FIXME(nox): Reject pending play promises. + self.take_pending_play_promises(Err(Error::Abort)); + self.fulfill_in_flight_play_promises(|| ()); } // Step 6.7. @@ -614,6 +721,60 @@ impl HTMLMediaElement { // Step 10. // FIXME(nox): Stop playback of any previously running media resource. } + + /// Appends a promise to the list of pending play promises. + #[allow(unrooted_must_root)] + fn push_pending_play_promise(&self, promise: &Rc) { + self.pending_play_promises.borrow_mut().push(promise.clone()); + } + + /// Takes the pending play promises. + /// + /// The result with which these promises will be fulfilled is passed here + /// and this method returns nothing because we actually just move the + /// current list of pending play promises to the + /// `in_flight_play_promises_queue` field. + /// + /// Each call to this method must be followed by a call to + /// `fulfill_in_flight_play_promises`, to actually fulfill the promises + /// which were taken and moved to the in-flight queue. + #[allow(unrooted_must_root)] + fn take_pending_play_promises(&self, result: ErrorResult) { + let pending_play_promises = mem::replace( + &mut *self.pending_play_promises.borrow_mut(), + vec![], + ); + self.in_flight_play_promises_queue.borrow_mut().push_back(( + pending_play_promises.into(), + result, + )); + } + + /// Fulfills the next in-flight play promises queue after running a closure. + /// + /// See the comment on `take_pending_play_promises` for why this method + /// does not take a list of promises to fulfill. Callers cannot just pop + /// the front list off of `in_flight_play_promises_queue` and later fulfill + /// the promises because that would mean putting + /// `#[allow(unrooted_must_root)]` on even more functions, potentially + /// hiding actual safety bugs. + #[allow(unrooted_must_root)] + fn fulfill_in_flight_play_promises(&self, f: F) + where + F: FnOnce(), + { + let (promises, result) = self.in_flight_play_promises_queue + .borrow_mut() + .pop_front() + .expect("there should be at least one list of in flight play promises"); + f(); + for promise in &*promises { + match result { + Ok(ref value) => promise.resolve_native(value), + Err(ref error) => promise.reject_error(error.clone()), + } + } + } } impl HTMLMediaElementMethods for HTMLMediaElement { @@ -670,70 +831,11 @@ impl HTMLMediaElementMethods for HTMLMediaElement { } // https://html.spec.whatwg.org/multipage/#dom-media-play - // FIXME(nox): This should return a promise. - fn Play(&self) { - // Step 1. - // FIXME(nox): Return a rejected promise if not allowed to play. - - // Step 2. - if self.error.get().map_or(false, |e| e.Code() == MEDIA_ERR_SRC_NOT_SUPPORTED) { - // FIXME(nox): This should return a rejected promise. - return; - } - - // Step 3. - // Create promise and add it to list of pending play promises. - - // Step 4. - if self.network_state.get() == NetworkState::Empty { - self.invoke_resource_selection_algorithm(); - } - - // Step 5. - // FIXME(nox): Seek to earliest possible position if playback has ended - // and direction of playback is forwards. - - let state = self.ready_state.get(); - - if self.Paused() { - // Step 6.1. - self.paused.set(false); - - // Step 6.2. - // FIXME(nox): Set show poster flag to false and run time marches on - // steps if show poster flag is true. - - // Step 6.3. - let window = window_from_node(self); - let task_source = window.dom_manipulation_task_source(); - task_source.queue_simple_event(self.upcast(), atom!("play"), &window); - - // Step 7.4. - match state { - ReadyState::HaveNothing | - ReadyState::HaveMetadata | - ReadyState::HaveCurrentData => { - task_source.queue_simple_event( - self.upcast(), - atom!("waiting"), - &window, - ); - }, - ReadyState::HaveFutureData | - ReadyState::HaveEnoughData => { - self.notify_about_playing(); - } - } - } else if state == ReadyState::HaveFutureData || state == ReadyState::HaveEnoughData { - // Step 7. - // FIXME(nox): Queue a task to resolve pending play promises. - } - - // Step 8. - self.autoplaying.set(false); - - // Step 9. - // FIXME(nox): Return promise created in step 3. + #[allow(unrooted_must_root)] + fn Play(&self) -> Rc { + let promise = Promise::new(&self.global()); + self.play(&promise); + promise } // https://html.spec.whatwg.org/multipage/#dom-media-pause diff --git a/components/script/dom/webidls/HTMLMediaElement.webidl b/components/script/dom/webidls/HTMLMediaElement.webidl index d7388f444f7..b9c112bf44d 100644 --- a/components/script/dom/webidls/HTMLMediaElement.webidl +++ b/components/script/dom/webidls/HTMLMediaElement.webidl @@ -50,7 +50,7 @@ interface HTMLMediaElement : HTMLElement { attribute boolean autoplay; // [CEReactions] // attribute boolean loop; - void play(); + Promise play(); void pause(); // media controller From 5245931dc283af528bb085f652d41856246b01bb Mon Sep 17 00:00:00 2001 From: Anthony Ramine Date: Mon, 25 Sep 2017 00:28:59 +0200 Subject: [PATCH 4/4] Implement the error path for source children of media elements This removes some test timeout. --- components/script/dom/htmlmediaelement.rs | 34 ++++++++++++++++--- components/script/dom/htmlsourceelement.rs | 19 +++++++++++ components/script/dom/virtualmethods.rs | 4 +++ .../load-removes-queued-error-event.html.ini | 3 +- ...selection-candidate-insert-before.html.ini | 3 +- ...oke-insert-source-not-in-document.html.ini | 5 --- ...ce-selection-invoke-insert-source.html.ini | 5 --- .../resource-selection-remove-source.html.ini | 5 --- 8 files changed, 54 insertions(+), 24 deletions(-) delete mode 100644 tests/wpt/metadata/html/semantics/embedded-content/media-elements/loading-the-media-resource/resource-selection-invoke-insert-source-not-in-document.html.ini delete mode 100644 tests/wpt/metadata/html/semantics/embedded-content/media-elements/loading-the-media-resource/resource-selection-invoke-insert-source.html.ini delete mode 100644 tests/wpt/metadata/html/semantics/embedded-content/media-elements/loading-the-media-resource/resource-selection-remove-source.html.ini diff --git a/components/script/dom/htmlmediaelement.rs b/components/script/dom/htmlmediaelement.rs index bcceffc11d4..13986728f32 100644 --- a/components/script/dom/htmlmediaelement.rs +++ b/components/script/dom/htmlmediaelement.rs @@ -85,7 +85,7 @@ pub struct HTMLMediaElement { /// https://html.spec.whatwg.org/multipage/#dom-media-networkstate #[derive(Clone, Copy, HeapSizeOf, JSTraceable, PartialEq)] #[repr(u8)] -enum NetworkState { +pub enum NetworkState { Empty = HTMLMediaElementConstants::NETWORK_EMPTY as u8, Idle = HTMLMediaElementConstants::NETWORK_IDLE as u8, Loading = HTMLMediaElementConstants::NETWORK_LOADING as u8, @@ -443,12 +443,23 @@ impl HTMLMediaElement { #[allow(dead_code)] Object, Attribute(String), - // FIXME(nox): Support source element child. - #[allow(dead_code)] Children(Root), } - let mode = if let Some(attr) = self.upcast::().get_attribute(&ns!(), &local_name!("src")) { - Mode::Attribute(attr.Value().into()) + fn mode(media: &HTMLMediaElement) -> Option { + if let Some(attr) = media.upcast::().get_attribute(&ns!(), &local_name!("src")) { + return Some(Mode::Attribute(attr.Value().into())); + } + let source_child_element = media.upcast::() + .children() + .filter_map(Root::downcast::) + .next(); + if let Some(element) = source_child_element { + return Some(Mode::Children(element)); + } + None + } + let mode = if let Some(mode) = mode(self) { + mode } else { self.network_state.set(NetworkState::Empty); return; @@ -775,6 +786,19 @@ impl HTMLMediaElement { } } } + + /// Handles insertion of `source` children. + /// + /// https://html.spec.whatwg.org/multipage/#the-source-element:nodes-are-inserted + pub fn handle_source_child_insertion(&self) { + if self.upcast::().has_attribute(&local_name!("src")) { + return; + } + if self.network_state.get() != NetworkState::Empty { + return; + } + self.media_element_load_algorithm(); + } } impl HTMLMediaElementMethods for HTMLMediaElement { diff --git a/components/script/dom/htmlsourceelement.rs b/components/script/dom/htmlsourceelement.rs index 84adbc16548..865b21d6acb 100644 --- a/components/script/dom/htmlsourceelement.rs +++ b/components/script/dom/htmlsourceelement.rs @@ -3,10 +3,14 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ use dom::bindings::codegen::Bindings::HTMLSourceElementBinding; +use dom::bindings::codegen::Bindings::NodeBinding::NodeBinding::NodeMethods; +use dom::bindings::inheritance::Castable; use dom::bindings::js::Root; use dom::document::Document; use dom::htmlelement::HTMLElement; +use dom::htmlmediaelement::HTMLMediaElement; use dom::node::Node; +use dom::virtualmethods::VirtualMethods; use dom_struct::dom_struct; use html5ever::{LocalName, Prefix}; @@ -34,3 +38,18 @@ impl HTMLSourceElement { HTMLSourceElementBinding::Wrap) } } + +impl VirtualMethods for HTMLSourceElement { + fn super_type(&self) -> Option<&VirtualMethods> { + Some(self.upcast::() as &VirtualMethods) + } + + /// https://html.spec.whatwg.org/multipage/#the-source-element:nodes-are-inserted + fn bind_to_tree(&self, tree_in_doc: bool) { + self.super_type().unwrap().bind_to_tree(tree_in_doc); + let parent = self.upcast::().GetParentNode().unwrap(); + if let Some(media) = parent.downcast::() { + media.handle_source_child_insertion(); + } + } +} diff --git a/components/script/dom/virtualmethods.rs b/components/script/dom/virtualmethods.rs index 2e4376ab975..b9adcea198e 100644 --- a/components/script/dom/virtualmethods.rs +++ b/components/script/dom/virtualmethods.rs @@ -41,6 +41,7 @@ use dom::htmloptionelement::HTMLOptionElement; use dom::htmloutputelement::HTMLOutputElement; use dom::htmlscriptelement::HTMLScriptElement; use dom::htmlselectelement::HTMLSelectElement; +use dom::htmlsourceelement::HTMLSourceElement; use dom::htmlstyleelement::HTMLStyleElement; use dom::htmltablecellelement::HTMLTableCellElement; use dom::htmltableelement::HTMLTableElement; @@ -231,6 +232,9 @@ pub fn vtable_for(node: &Node) -> &VirtualMethods { NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLSelectElement)) => { node.downcast::().unwrap() as &VirtualMethods } + NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLSourceElement)) => { + node.downcast::().unwrap() as &VirtualMethods + } NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLStyleElement)) => { node.downcast::().unwrap() as &VirtualMethods } diff --git a/tests/wpt/metadata/html/semantics/embedded-content/media-elements/loading-the-media-resource/load-removes-queued-error-event.html.ini b/tests/wpt/metadata/html/semantics/embedded-content/media-elements/loading-the-media-resource/load-removes-queued-error-event.html.ini index 731fe16fc1b..c19f8ec60ce 100644 --- a/tests/wpt/metadata/html/semantics/embedded-content/media-elements/loading-the-media-resource/load-removes-queued-error-event.html.ini +++ b/tests/wpt/metadata/html/semantics/embedded-content/media-elements/loading-the-media-resource/load-removes-queued-error-event.html.ini @@ -1,6 +1,5 @@ [load-removes-queued-error-event.html] type: testharness - expected: TIMEOUT [source error event] - expected: TIMEOUT + expected: FAIL diff --git a/tests/wpt/metadata/html/semantics/embedded-content/media-elements/loading-the-media-resource/resource-selection-candidate-insert-before.html.ini b/tests/wpt/metadata/html/semantics/embedded-content/media-elements/loading-the-media-resource/resource-selection-candidate-insert-before.html.ini index 4908ff33efe..76c0a336069 100644 --- a/tests/wpt/metadata/html/semantics/embedded-content/media-elements/loading-the-media-resource/resource-selection-candidate-insert-before.html.ini +++ b/tests/wpt/metadata/html/semantics/embedded-content/media-elements/loading-the-media-resource/resource-selection-candidate-insert-before.html.ini @@ -1,6 +1,5 @@ [resource-selection-candidate-insert-before.html] type: testharness - expected: TIMEOUT [inserting another source before the candidate] - expected: TIMEOUT + expected: FAIL diff --git a/tests/wpt/metadata/html/semantics/embedded-content/media-elements/loading-the-media-resource/resource-selection-invoke-insert-source-not-in-document.html.ini b/tests/wpt/metadata/html/semantics/embedded-content/media-elements/loading-the-media-resource/resource-selection-invoke-insert-source-not-in-document.html.ini deleted file mode 100644 index d6c3a5c9926..00000000000 --- a/tests/wpt/metadata/html/semantics/embedded-content/media-elements/loading-the-media-resource/resource-selection-invoke-insert-source-not-in-document.html.ini +++ /dev/null @@ -1,5 +0,0 @@ -[resource-selection-invoke-insert-source-not-in-document.html] - type: testharness - [invoking resource selection by inserting in video not in a document] - expected: FAIL - diff --git a/tests/wpt/metadata/html/semantics/embedded-content/media-elements/loading-the-media-resource/resource-selection-invoke-insert-source.html.ini b/tests/wpt/metadata/html/semantics/embedded-content/media-elements/loading-the-media-resource/resource-selection-invoke-insert-source.html.ini deleted file mode 100644 index e7fd87a4aa0..00000000000 --- a/tests/wpt/metadata/html/semantics/embedded-content/media-elements/loading-the-media-resource/resource-selection-invoke-insert-source.html.ini +++ /dev/null @@ -1,5 +0,0 @@ -[resource-selection-invoke-insert-source.html] - type: testharness - [invoking resource selection by inserting ] - expected: FAIL - diff --git a/tests/wpt/metadata/html/semantics/embedded-content/media-elements/loading-the-media-resource/resource-selection-remove-source.html.ini b/tests/wpt/metadata/html/semantics/embedded-content/media-elements/loading-the-media-resource/resource-selection-remove-source.html.ini deleted file mode 100644 index d827b0ad685..00000000000 --- a/tests/wpt/metadata/html/semantics/embedded-content/media-elements/loading-the-media-resource/resource-selection-remove-source.html.ini +++ /dev/null @@ -1,5 +0,0 @@ -[resource-selection-remove-source.html] - type: testharness - [Changes to networkState when inserting and removing a ] - expected: FAIL -