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 http://mozilla.org/MPL/2.0/. */ 4 5 use audio_video_metadata; 6 use document_loader::{LoadBlocker, LoadType}; 7 use dom::attr::Attr; 8 use dom::bindings::cell::DomRefCell; 9 use dom::bindings::codegen::Bindings::AttrBinding::AttrMethods; 10 use dom::bindings::codegen::Bindings::HTMLMediaElementBinding::CanPlayTypeResult; 11 use dom::bindings::codegen::Bindings::HTMLMediaElementBinding::HTMLMediaElementConstants; 12 use dom::bindings::codegen::Bindings::HTMLMediaElementBinding::HTMLMediaElementMethods; 13 use dom::bindings::codegen::Bindings::MediaErrorBinding::MediaErrorConstants::*; 14 use dom::bindings::codegen::Bindings::MediaErrorBinding::MediaErrorMethods; 15 use dom::bindings::codegen::InheritTypes::{ElementTypeId, HTMLElementTypeId}; 16 use dom::bindings::codegen::InheritTypes::{HTMLMediaElementTypeId, NodeTypeId}; 17 use dom::bindings::error::{Error, ErrorResult}; 18 use dom::bindings::inheritance::Castable; 19 use dom::bindings::refcounted::Trusted; 20 use dom::bindings::reflector::DomObject; 21 use dom::bindings::root::{DomRoot, MutNullableDom}; 22 use dom::bindings::str::DOMString; 23 use dom::blob::Blob; 24 use dom::document::Document; 25 use dom::element::{Element, AttributeMutation}; 26 use dom::eventtarget::EventTarget; 27 use dom::htmlelement::HTMLElement; 28 use dom::htmlsourceelement::HTMLSourceElement; 29 use dom::mediaerror::MediaError; 30 use dom::node::{window_from_node, document_from_node, Node, UnbindContext}; 31 use dom::promise::Promise; 32 use dom::virtualmethods::VirtualMethods; 33 use dom_struct::dom_struct; 34 use html5ever::{LocalName, Prefix}; 35 use ipc_channel::ipc; 36 use ipc_channel::router::ROUTER; 37 use microtask::{Microtask, MicrotaskRunnable}; 38 use mime::{Mime, SubLevel, TopLevel}; 39 use net_traits::{FetchResponseListener, FetchMetadata, Metadata, NetworkError}; 40 use net_traits::request::{CredentialsMode, Destination, RequestInit}; 41 use network_listener::{NetworkListener, PreInvoke}; 42 use script_thread::ScriptThread; 43 use servo_url::ServoUrl; 44 use std::cell::Cell; 45 use std::collections::VecDeque; 46 use std::mem; 47 use std::rc::Rc; 48 use std::sync::{Arc, Mutex}; 49 use task_source::TaskSource; 50 use time::{self, Timespec, Duration}; 51 52 #[dom_struct] 53 // FIXME(nox): A lot of tasks queued for this element should probably be in the 54 // media element event task source. 55 pub struct HTMLMediaElement { 56 htmlelement: HTMLElement, 57 /// <https://html.spec.whatwg.org/multipage/#dom-media-networkstate> 58 network_state: Cell<NetworkState>, 59 /// <https://html.spec.whatwg.org/multipage/#dom-media-readystate> 60 ready_state: Cell<ReadyState>, 61 /// <https://html.spec.whatwg.org/multipage/#dom-media-srcobject> 62 src_object: MutNullableDom<Blob>, 63 /// <https://html.spec.whatwg.org/multipage/#dom-media-currentsrc> 64 current_src: DomRefCell<String>, 65 /// Incremented whenever tasks associated with this element are cancelled. 66 generation_id: Cell<u32>, 67 /// <https://html.spec.whatwg.org/multipage/#fire-loadeddata> 68 /// 69 /// Reset to false every time the load algorithm is invoked. 70 fired_loadeddata_event: Cell<bool>, 71 /// <https://html.spec.whatwg.org/multipage/#dom-media-error> 72 error: MutNullableDom<MediaError>, 73 /// <https://html.spec.whatwg.org/multipage/#dom-media-paused> 74 paused: Cell<bool>, 75 /// <https://html.spec.whatwg.org/multipage/#attr-media-autoplay> 76 autoplaying: Cell<bool>, 77 /// <https://html.spec.whatwg.org/multipage/#delaying-the-load-event-flag> 78 delaying_the_load_event_flag: DomRefCell<Option<LoadBlocker>>, 79 /// <https://html.spec.whatwg.org/multipage/#list-of-pending-play-promises> 80 #[ignore_malloc_size_of = "promises are hard"] 81 pending_play_promises: DomRefCell<Vec<Rc<Promise>>>, 82 /// Play promises which are soon to be fulfilled by a queued task. 83 #[ignore_malloc_size_of = "promises are hard"] 84 in_flight_play_promises_queue: DomRefCell<VecDeque<(Box<[Rc<Promise>]>, ErrorResult)>>, 85 } 86 87 /// <https://html.spec.whatwg.org/multipage/#dom-media-networkstate> 88 #[derive(Clone, Copy, JSTraceable, MallocSizeOf, PartialEq)] 89 #[repr(u8)] 90 pub enum NetworkState { 91 Empty = HTMLMediaElementConstants::NETWORK_EMPTY as u8, 92 Idle = HTMLMediaElementConstants::NETWORK_IDLE as u8, 93 Loading = HTMLMediaElementConstants::NETWORK_LOADING as u8, 94 NoSource = HTMLMediaElementConstants::NETWORK_NO_SOURCE as u8, 95 } 96 97 /// <https://html.spec.whatwg.org/multipage/#dom-media-readystate> 98 #[derive(Clone, Copy, JSTraceable, MallocSizeOf, PartialEq, PartialOrd)] 99 #[repr(u8)] 100 enum ReadyState { 101 HaveNothing = HTMLMediaElementConstants::HAVE_NOTHING as u8, 102 HaveMetadata = HTMLMediaElementConstants::HAVE_METADATA as u8, 103 HaveCurrentData = HTMLMediaElementConstants::HAVE_CURRENT_DATA as u8, 104 HaveFutureData = HTMLMediaElementConstants::HAVE_FUTURE_DATA as u8, 105 HaveEnoughData = HTMLMediaElementConstants::HAVE_ENOUGH_DATA as u8, 106 } 107 108 impl HTMLMediaElement { new_inherited( tag_name: LocalName, prefix: Option<Prefix>, document: &Document, ) -> Self109 pub fn new_inherited( 110 tag_name: LocalName, 111 prefix: Option<Prefix>, 112 document: &Document, 113 ) -> Self { 114 Self { 115 htmlelement: HTMLElement::new_inherited(tag_name, prefix, document), 116 network_state: Cell::new(NetworkState::Empty), 117 ready_state: Cell::new(ReadyState::HaveNothing), 118 src_object: Default::default(), 119 current_src: DomRefCell::new("".to_owned()), 120 generation_id: Cell::new(0), 121 fired_loadeddata_event: Cell::new(false), 122 error: Default::default(), 123 paused: Cell::new(true), 124 // FIXME(nox): Why is this initialised to true? 125 autoplaying: Cell::new(true), 126 delaying_the_load_event_flag: Default::default(), 127 pending_play_promises: Default::default(), 128 in_flight_play_promises_queue: Default::default(), 129 } 130 } 131 media_type_id(&self) -> HTMLMediaElementTypeId132 fn media_type_id(&self) -> HTMLMediaElementTypeId { 133 match self.upcast::<Node>().type_id() { 134 NodeTypeId::Element(ElementTypeId::HTMLElement( 135 HTMLElementTypeId::HTMLMediaElement(media_type_id), 136 )) => { 137 media_type_id 138 }, 139 _ => unreachable!(), 140 } 141 } 142 143 /// Marks that element as delaying the load event or not. 144 /// 145 /// Nothing happens if the element was already delaying the load event and 146 /// we pass true to that method again. 147 /// 148 /// <https://html.spec.whatwg.org/multipage/#delaying-the-load-event-flag> delay_load_event(&self, delay: bool)149 fn delay_load_event(&self, delay: bool) { 150 let mut blocker = self.delaying_the_load_event_flag.borrow_mut(); 151 if delay && blocker.is_none() { 152 *blocker = Some(LoadBlocker::new(&document_from_node(self), LoadType::Media)); 153 } else if !delay && blocker.is_some() { 154 LoadBlocker::terminate(&mut *blocker); 155 } 156 } 157 158 /// <https://html.spec.whatwg.org/multipage/#dom-media-play> 159 // FIXME(nox): Move this back to HTMLMediaElementMethods::Play once 160 // Rc<Promise> doesn't require #[allow(unrooted_must_root)] anymore. play(&self, promise: &Rc<Promise>)161 fn play(&self, promise: &Rc<Promise>) { 162 // Step 1. 163 // FIXME(nox): Reject promise if not allowed to play. 164 165 // Step 2. 166 if self.error.get().map_or(false, |e| e.Code() == MEDIA_ERR_SRC_NOT_SUPPORTED) { 167 promise.reject_error(Error::NotSupported); 168 return; 169 } 170 171 // Step 3. 172 self.push_pending_play_promise(promise); 173 174 // Step 4. 175 if self.network_state.get() == NetworkState::Empty { 176 self.invoke_resource_selection_algorithm(); 177 } 178 179 // Step 5. 180 // FIXME(nox): Seek to earliest possible position if playback has ended 181 // and direction of playback is forwards. 182 183 let state = self.ready_state.get(); 184 185 let window = window_from_node(self); 186 let task_source = window.dom_manipulation_task_source(); 187 if self.Paused() { 188 // Step 6.1. 189 self.paused.set(false); 190 191 // Step 6.2. 192 // FIXME(nox): Set show poster flag to false and run time marches on 193 // steps if show poster flag is true. 194 195 // Step 6.3. 196 task_source.queue_simple_event(self.upcast(), atom!("play"), &window); 197 198 // Step 6.4. 199 match state { 200 ReadyState::HaveNothing | 201 ReadyState::HaveMetadata | 202 ReadyState::HaveCurrentData => { 203 task_source.queue_simple_event( 204 self.upcast(), 205 atom!("waiting"), 206 &window, 207 ); 208 }, 209 ReadyState::HaveFutureData | 210 ReadyState::HaveEnoughData => { 211 self.notify_about_playing(); 212 } 213 } 214 } else if state == ReadyState::HaveFutureData || state == ReadyState::HaveEnoughData { 215 // Step 7. 216 self.take_pending_play_promises(Ok(())); 217 let this = Trusted::new(self); 218 let generation_id = self.generation_id.get(); 219 task_source.queue( 220 task!(resolve_pending_play_promises: move || { 221 let this = this.root(); 222 if generation_id != this.generation_id.get() { 223 return; 224 } 225 226 this.fulfill_in_flight_play_promises(|| ()); 227 }), 228 window.upcast(), 229 ).unwrap(); 230 } 231 232 // Step 8. 233 self.autoplaying.set(false); 234 235 // Step 9. 236 // Not applicable here, the promise is returned from Play. 237 } 238 239 /// <https://html.spec.whatwg.org/multipage/#internal-pause-steps> internal_pause_steps(&self)240 fn internal_pause_steps(&self) { 241 // Step 1. 242 self.autoplaying.set(false); 243 244 // Step 2. 245 if !self.Paused() { 246 // Step 2.1. 247 self.paused.set(true); 248 249 // Step 2.2. 250 self.take_pending_play_promises(Err(Error::Abort)); 251 252 // Step 2.3. 253 let window = window_from_node(self); 254 let this = Trusted::new(self); 255 let generation_id = self.generation_id.get(); 256 // FIXME(nox): Why are errors silenced here? 257 // FIXME(nox): Media element event task source should be used here. 258 let _ = window.dom_manipulation_task_source().queue( 259 task!(internal_pause_steps: move || { 260 let this = this.root(); 261 if generation_id != this.generation_id.get() { 262 return; 263 } 264 265 this.fulfill_in_flight_play_promises(|| { 266 // Step 2.3.1. 267 this.upcast::<EventTarget>().fire_event(atom!("timeupdate")); 268 269 // Step 2.3.2. 270 this.upcast::<EventTarget>().fire_event(atom!("pause")); 271 272 // Step 2.3.3. 273 // Done after running this closure in 274 // `fulfill_in_flight_play_promises`. 275 }); 276 }), 277 window.upcast(), 278 ); 279 280 // Step 2.4. 281 // FIXME(nox): Set the official playback position to the current 282 // playback position. 283 } 284 } 285 286 // https://html.spec.whatwg.org/multipage/#notify-about-playing notify_about_playing(&self)287 fn notify_about_playing(&self) { 288 // Step 1. 289 self.take_pending_play_promises(Ok(())); 290 291 // Step 2. 292 let window = window_from_node(self); 293 let this = Trusted::new(self); 294 let generation_id = self.generation_id.get(); 295 // FIXME(nox): Why are errors silenced here? 296 // FIXME(nox): Media element event task source should be used here. 297 let _ = window.dom_manipulation_task_source().queue( 298 task!(notify_about_playing: move || { 299 let this = this.root(); 300 if generation_id != this.generation_id.get() { 301 return; 302 } 303 304 this.fulfill_in_flight_play_promises(|| { 305 // Step 2.1. 306 this.upcast::<EventTarget>().fire_event(atom!("playing")); 307 308 // Step 2.2. 309 // Done after running this closure in 310 // `fulfill_in_flight_play_promises`. 311 }); 312 313 }), 314 window.upcast(), 315 ); 316 } 317 318 // https://html.spec.whatwg.org/multipage/#ready-states change_ready_state(&self, ready_state: ReadyState)319 fn change_ready_state(&self, ready_state: ReadyState) { 320 let old_ready_state = self.ready_state.get(); 321 self.ready_state.set(ready_state); 322 323 if self.network_state.get() == NetworkState::Empty { 324 return; 325 } 326 327 let window = window_from_node(self); 328 let task_source = window.dom_manipulation_task_source(); 329 330 // Step 1. 331 match (old_ready_state, ready_state) { 332 (ReadyState::HaveNothing, ReadyState::HaveMetadata) => { 333 task_source.queue_simple_event( 334 self.upcast(), 335 atom!("loadedmetadata"), 336 &window, 337 ); 338 339 // No other steps are applicable in this case. 340 return; 341 }, 342 (ReadyState::HaveMetadata, new) if new >= ReadyState::HaveCurrentData => { 343 if !self.fired_loadeddata_event.get() { 344 self.fired_loadeddata_event.set(true); 345 let this = Trusted::new(self); 346 // FIXME(nox): Why are errors silenced here? 347 let _ = task_source.queue( 348 task!(media_reached_current_data: move || { 349 let this = this.root(); 350 this.upcast::<EventTarget>().fire_event(atom!("loadeddata")); 351 this.delay_load_event(false); 352 }), 353 window.upcast(), 354 ); 355 } 356 357 // Steps for the transition from HaveMetadata to HaveCurrentData 358 // or HaveFutureData also apply here, as per the next match 359 // expression. 360 }, 361 (ReadyState::HaveFutureData, new) if new <= ReadyState::HaveCurrentData => { 362 // FIXME(nox): Queue a task to fire timeupdate and waiting 363 // events if the conditions call from the spec are met. 364 365 // No other steps are applicable in this case. 366 return; 367 }, 368 369 _ => (), 370 } 371 372 if old_ready_state <= ReadyState::HaveCurrentData && ready_state >= ReadyState::HaveFutureData { 373 task_source.queue_simple_event( 374 self.upcast(), 375 atom!("canplay"), 376 &window, 377 ); 378 379 if !self.Paused() { 380 self.notify_about_playing(); 381 } 382 } 383 384 if ready_state == ReadyState::HaveEnoughData { 385 // TODO: Check sandboxed automatic features browsing context flag. 386 // FIXME(nox): I have no idea what this TODO is about. 387 388 // FIXME(nox): Review this block. 389 if self.autoplaying.get() && 390 self.Paused() && 391 self.Autoplay() { 392 // Step 1 393 self.paused.set(false); 394 // TODO step 2: show poster 395 // Step 3 396 task_source.queue_simple_event( 397 self.upcast(), 398 atom!("play"), 399 &window, 400 ); 401 // Step 4 402 self.notify_about_playing(); 403 // Step 5 404 self.autoplaying.set(false); 405 } 406 407 // FIXME(nox): According to the spec, this should come *before* the 408 // "play" event. 409 task_source.queue_simple_event( 410 self.upcast(), 411 atom!("canplaythrough"), 412 &window, 413 ); 414 } 415 } 416 417 // https://html.spec.whatwg.org/multipage/#concept-media-load-algorithm invoke_resource_selection_algorithm(&self)418 fn invoke_resource_selection_algorithm(&self) { 419 // Step 1. 420 self.network_state.set(NetworkState::NoSource); 421 422 // Step 2. 423 // FIXME(nox): Set show poster flag to true. 424 425 // Step 3. 426 self.delay_load_event(true); 427 428 // Step 4. 429 // If the resource selection mode in the synchronous section is 430 // "attribute", the URL of the resource to fetch is relative to the 431 // media element's node document when the src attribute was last 432 // changed, which is why we need to pass the base URL in the task 433 // right here. 434 let doc = document_from_node(self); 435 let task = MediaElementMicrotask::ResourceSelectionTask { 436 elem: DomRoot::from_ref(self), 437 generation_id: self.generation_id.get(), 438 base_url: doc.base_url() 439 }; 440 441 // FIXME(nox): This will later call the resource_selection_algorith_sync 442 // method from below, if microtasks were trait objects, we would be able 443 // to put the code directly in this method, without the boilerplate 444 // indirections. 445 ScriptThread::await_stable_state(Microtask::MediaElement(task)); 446 } 447 448 // https://html.spec.whatwg.org/multipage/#concept-media-load-algorithm resource_selection_algorithm_sync(&self, base_url: ServoUrl)449 fn resource_selection_algorithm_sync(&self, base_url: ServoUrl) { 450 // Step 5. 451 // FIXME(nox): Maybe populate the list of pending text tracks. 452 453 // Step 6. 454 enum Mode { 455 Object, 456 Attribute(String), 457 Children(DomRoot<HTMLSourceElement>), 458 } 459 fn mode(media: &HTMLMediaElement) -> Option<Mode> { 460 if media.src_object.get().is_some() { 461 return Some(Mode::Object); 462 } 463 if let Some(attr) = media.upcast::<Element>().get_attribute(&ns!(), &local_name!("src")) { 464 return Some(Mode::Attribute(attr.Value().into())); 465 } 466 let source_child_element = media.upcast::<Node>() 467 .children() 468 .filter_map(DomRoot::downcast::<HTMLSourceElement>) 469 .next(); 470 if let Some(element) = source_child_element { 471 return Some(Mode::Children(element)); 472 } 473 None 474 } 475 let mode = if let Some(mode) = mode(self) { 476 mode 477 } else { 478 self.network_state.set(NetworkState::Empty); 479 // https://github.com/whatwg/html/issues/3065 480 self.delay_load_event(false); 481 return; 482 }; 483 484 // Step 7. 485 self.network_state.set(NetworkState::Loading); 486 487 // Step 8. 488 let window = window_from_node(self); 489 window.dom_manipulation_task_source().queue_simple_event( 490 self.upcast(), 491 atom!("loadstart"), 492 &window, 493 ); 494 495 // Step 9. 496 match mode { 497 // Step 9.obj. 498 Mode::Object => { 499 // Step 9.obj.1. 500 *self.current_src.borrow_mut() = "".to_owned(); 501 502 // Step 9.obj.2. 503 // FIXME(nox): The rest of the steps should be ran in parallel. 504 505 // Step 9.obj.3. 506 // Note that the resource fetch algorithm itself takes care 507 // of the cleanup in case of failure itself. 508 self.resource_fetch_algorithm(Resource::Object); 509 }, 510 Mode::Attribute(src) => { 511 // Step 9.attr.1. 512 if src.is_empty() { 513 self.queue_dedicated_media_source_failure_steps(); 514 return; 515 } 516 517 // Step 9.attr.2. 518 let url_record = match base_url.join(&src) { 519 Ok(url) => url, 520 Err(_) => { 521 self.queue_dedicated_media_source_failure_steps(); 522 return; 523 } 524 }; 525 526 // Step 9.attr.3. 527 *self.current_src.borrow_mut() = url_record.as_str().into(); 528 529 // Step 9.attr.4. 530 // Note that the resource fetch algorithm itself takes care 531 // of the cleanup in case of failure itself. 532 self.resource_fetch_algorithm(Resource::Url(url_record)); 533 }, 534 Mode::Children(_source) => { 535 // Step 9.children. 536 self.queue_dedicated_media_source_failure_steps() 537 }, 538 } 539 } 540 541 // https://html.spec.whatwg.org/multipage/#concept-media-load-resource resource_fetch_algorithm(&self, resource: Resource)542 fn resource_fetch_algorithm(&self, resource: Resource) { 543 // Steps 1-2. 544 // Unapplicable, the `resource` variable already conveys which mode 545 // is in use. 546 547 // Step 3. 548 // FIXME(nox): Remove all media-resource-specific text tracks. 549 550 // Step 4. 551 match resource { 552 Resource::Url(url) => { 553 // Step 4.remote.1. 554 if self.Preload() == "none" && !self.autoplaying.get() { 555 // Step 4.remote.1.1. 556 self.network_state.set(NetworkState::Idle); 557 558 // Step 4.remote.1.2. 559 let window = window_from_node(self); 560 window.dom_manipulation_task_source().queue_simple_event( 561 self.upcast(), 562 atom!("suspend"), 563 &window, 564 ); 565 566 // Step 4.remote.1.3. 567 let this = Trusted::new(self); 568 window.dom_manipulation_task_source().queue( 569 task!(set_media_delay_load_event_flag_to_false: move || { 570 this.root().delay_load_event(false); 571 }), 572 window.upcast(), 573 ).unwrap(); 574 575 // Steps 4.remote.1.4. 576 // FIXME(nox): Somehow we should wait for the task from previous 577 // step to be ran before continuing. 578 579 // Steps 4.remote.1.5-4.remote.1.7. 580 // FIXME(nox): Wait for an implementation-defined event and 581 // then continue with the normal set of steps instead of just 582 // returning. 583 return; 584 } 585 586 // Step 4.remote.2. 587 // FIXME(nox): Handle CORS setting from crossorigin attribute. 588 let document = document_from_node(self); 589 let destination = match self.media_type_id() { 590 HTMLMediaElementTypeId::HTMLAudioElement => Destination::Audio, 591 HTMLMediaElementTypeId::HTMLVideoElement => Destination::Video, 592 }; 593 let request = RequestInit { 594 url, 595 destination, 596 credentials_mode: CredentialsMode::Include, 597 use_url_credentials: true, 598 origin: document.origin().immutable().clone(), 599 pipeline_id: Some(self.global().pipeline_id()), 600 referrer_url: Some(document.url()), 601 referrer_policy: document.get_referrer_policy(), 602 .. RequestInit::default() 603 }; 604 605 let context = Arc::new(Mutex::new(HTMLMediaElementContext::new(self))); 606 let (action_sender, action_receiver) = ipc::channel().unwrap(); 607 let window = window_from_node(self); 608 let listener = NetworkListener { 609 context: context, 610 task_source: window.networking_task_source(), 611 canceller: Some(window.task_canceller()) 612 }; 613 ROUTER.add_route(action_receiver.to_opaque(), Box::new(move |message| { 614 listener.notify_fetch(message.to().unwrap()); 615 })); 616 document.loader().fetch_async_background(request, action_sender); 617 }, 618 Resource::Object => { 619 // FIXME(nox): Actually do something with the object. 620 self.queue_dedicated_media_source_failure_steps(); 621 }, 622 } 623 } 624 625 /// Queues a task to run the [dedicated media source failure steps][steps]. 626 /// 627 /// [steps]: https://html.spec.whatwg.org/multipage/#dedicated-media-source-failure-steps queue_dedicated_media_source_failure_steps(&self)628 fn queue_dedicated_media_source_failure_steps(&self) { 629 let window = window_from_node(self); 630 let this = Trusted::new(self); 631 let generation_id = self.generation_id.get(); 632 self.take_pending_play_promises(Err(Error::NotSupported)); 633 // FIXME(nox): Why are errors silenced here? 634 // FIXME(nox): Media element event task source should be used here. 635 let _ = window.dom_manipulation_task_source().queue( 636 task!(dedicated_media_source_failure_steps: move || { 637 let this = this.root(); 638 if generation_id != this.generation_id.get() { 639 return; 640 } 641 642 this.fulfill_in_flight_play_promises(|| { 643 // Step 1. 644 this.error.set(Some(&*MediaError::new( 645 &window_from_node(&*this), 646 MEDIA_ERR_SRC_NOT_SUPPORTED, 647 ))); 648 649 // Step 2. 650 // FIXME(nox): Forget the media-resource-specific tracks. 651 652 // Step 3. 653 this.network_state.set(NetworkState::NoSource); 654 655 // Step 4. 656 // FIXME(nox): Set show poster flag to true. 657 658 // Step 5. 659 this.upcast::<EventTarget>().fire_event(atom!("error")); 660 661 // Step 6. 662 // Done after running this closure in 663 // `fulfill_in_flight_play_promises`. 664 }); 665 666 // Step 7. 667 this.delay_load_event(false); 668 }), 669 window.upcast(), 670 ); 671 } 672 673 // https://html.spec.whatwg.org/multipage/#media-element-load-algorithm media_element_load_algorithm(&self)674 fn media_element_load_algorithm(&self) { 675 // Reset the flag that signals whether loadeddata was ever fired for 676 // this invokation of the load algorithm. 677 self.fired_loadeddata_event.set(false); 678 679 // Step 1-2. 680 self.generation_id.set(self.generation_id.get() + 1); 681 682 // Steps 3-4. 683 while !self.in_flight_play_promises_queue.borrow().is_empty() { 684 self.fulfill_in_flight_play_promises(|| ()); 685 } 686 687 let window = window_from_node(self); 688 let task_source = window.dom_manipulation_task_source(); 689 690 // Step 5. 691 let network_state = self.network_state.get(); 692 if network_state == NetworkState::Loading || network_state == NetworkState::Idle { 693 task_source.queue_simple_event(self.upcast(), atom!("abort"), &window); 694 } 695 696 // Step 6. 697 if network_state != NetworkState::Empty { 698 // Step 6.1. 699 task_source.queue_simple_event(self.upcast(), atom!("emptied"), &window); 700 701 // Step 6.2. 702 // FIXME(nox): Abort in-progress fetching process. 703 704 // Step 6.3. 705 // FIXME(nox): Detach MediaSource media provider object. 706 707 // Step 6.4. 708 // FIXME(nox): Forget the media-resource-specific tracks. 709 710 // Step 6.5. 711 if self.ready_state.get() != ReadyState::HaveNothing { 712 self.change_ready_state(ReadyState::HaveNothing); 713 } 714 715 // Step 6.6. 716 if !self.Paused() { 717 // Step 6.6.1. 718 self.paused.set(true); 719 720 // Step 6.6.2. 721 self.take_pending_play_promises(Err(Error::Abort)); 722 self.fulfill_in_flight_play_promises(|| ()); 723 } 724 725 // Step 6.7. 726 // FIXME(nox): If seeking is true, set it to false. 727 728 // Step 6.8. 729 // FIXME(nox): Set current and official playback position to 0 and 730 // maybe queue a task to fire a timeupdate event. 731 732 // Step 6.9. 733 // FIXME(nox): Set timeline offset to NaN. 734 735 // Step 6.10. 736 // FIXME(nox): Set duration to NaN. 737 } 738 739 // Step 7. 740 // FIXME(nox): Set playbackRate to defaultPlaybackRate. 741 742 // Step 8. 743 self.error.set(None); 744 self.autoplaying.set(true); 745 746 // Step 9. 747 self.invoke_resource_selection_algorithm(); 748 749 // Step 10. 750 // FIXME(nox): Stop playback of any previously running media resource. 751 } 752 753 /// Appends a promise to the list of pending play promises. 754 #[allow(unrooted_must_root)] push_pending_play_promise(&self, promise: &Rc<Promise>)755 fn push_pending_play_promise(&self, promise: &Rc<Promise>) { 756 self.pending_play_promises.borrow_mut().push(promise.clone()); 757 } 758 759 /// Takes the pending play promises. 760 /// 761 /// The result with which these promises will be fulfilled is passed here 762 /// and this method returns nothing because we actually just move the 763 /// current list of pending play promises to the 764 /// `in_flight_play_promises_queue` field. 765 /// 766 /// Each call to this method must be followed by a call to 767 /// `fulfill_in_flight_play_promises`, to actually fulfill the promises 768 /// which were taken and moved to the in-flight queue. 769 #[allow(unrooted_must_root)] take_pending_play_promises(&self, result: ErrorResult)770 fn take_pending_play_promises(&self, result: ErrorResult) { 771 let pending_play_promises = mem::replace( 772 &mut *self.pending_play_promises.borrow_mut(), 773 vec![], 774 ); 775 self.in_flight_play_promises_queue.borrow_mut().push_back(( 776 pending_play_promises.into(), 777 result, 778 )); 779 } 780 781 /// Fulfills the next in-flight play promises queue after running a closure. 782 /// 783 /// See the comment on `take_pending_play_promises` for why this method 784 /// does not take a list of promises to fulfill. Callers cannot just pop 785 /// the front list off of `in_flight_play_promises_queue` and later fulfill 786 /// the promises because that would mean putting 787 /// `#[allow(unrooted_must_root)]` on even more functions, potentially 788 /// hiding actual safety bugs. 789 #[allow(unrooted_must_root)] fulfill_in_flight_play_promises<F>(&self, f: F) where F: FnOnce(),790 fn fulfill_in_flight_play_promises<F>(&self, f: F) 791 where 792 F: FnOnce(), 793 { 794 let (promises, result) = self.in_flight_play_promises_queue 795 .borrow_mut() 796 .pop_front() 797 .expect("there should be at least one list of in flight play promises"); 798 f(); 799 for promise in &*promises { 800 match result { 801 Ok(ref value) => promise.resolve_native(value), 802 Err(ref error) => promise.reject_error(error.clone()), 803 } 804 } 805 } 806 807 /// Handles insertion of `source` children. 808 /// 809 /// <https://html.spec.whatwg.org/multipage/#the-source-element:nodes-are-inserted> handle_source_child_insertion(&self)810 pub fn handle_source_child_insertion(&self) { 811 if self.upcast::<Element>().has_attribute(&local_name!("src")) { 812 return; 813 } 814 if self.network_state.get() != NetworkState::Empty { 815 return; 816 } 817 self.media_element_load_algorithm(); 818 } 819 } 820 821 impl HTMLMediaElementMethods for HTMLMediaElement { 822 // https://html.spec.whatwg.org/multipage/#dom-media-networkstate NetworkState(&self) -> u16823 fn NetworkState(&self) -> u16 { 824 self.network_state.get() as u16 825 } 826 827 // https://html.spec.whatwg.org/multipage/#dom-media-readystate ReadyState(&self) -> u16828 fn ReadyState(&self) -> u16 { 829 self.ready_state.get() as u16 830 } 831 832 // https://html.spec.whatwg.org/multipage/#dom-media-autoplay 833 make_bool_getter!(Autoplay, "autoplay"); 834 // https://html.spec.whatwg.org/multipage/#dom-media-autoplay 835 make_bool_setter!(SetAutoplay, "autoplay"); 836 837 // https://html.spec.whatwg.org/multipage/#dom-media-src 838 make_url_getter!(Src, "src"); 839 840 // https://html.spec.whatwg.org/multipage/#dom-media-src 841 make_setter!(SetSrc, "src"); 842 843 // https://html.spec.whatwg.org/multipage/#dom-media-srcobject GetSrcObject(&self) -> Option<DomRoot<Blob>>844 fn GetSrcObject(&self) -> Option<DomRoot<Blob>> { 845 self.src_object.get() 846 } 847 848 // https://html.spec.whatwg.org/multipage/#dom-media-srcobject SetSrcObject(&self, value: Option<&Blob>)849 fn SetSrcObject(&self, value: Option<&Blob>) { 850 self.src_object.set(value); 851 self.media_element_load_algorithm(); 852 } 853 854 // https://html.spec.whatwg.org/multipage/#attr-media-preload 855 // Missing value default is user-agent defined. 856 make_enumerated_getter!(Preload, "preload", "", "none" | "metadata" | "auto"); 857 // https://html.spec.whatwg.org/multipage/#attr-media-preload 858 make_setter!(SetPreload, "preload"); 859 860 // https://html.spec.whatwg.org/multipage/#dom-media-currentsrc CurrentSrc(&self) -> DOMString861 fn CurrentSrc(&self) -> DOMString { 862 DOMString::from(self.current_src.borrow().clone()) 863 } 864 865 // https://html.spec.whatwg.org/multipage/#dom-media-load Load(&self)866 fn Load(&self) { 867 self.media_element_load_algorithm(); 868 } 869 870 // https://html.spec.whatwg.org/multipage/#dom-navigator-canplaytype CanPlayType(&self, type_: DOMString) -> CanPlayTypeResult871 fn CanPlayType(&self, type_: DOMString) -> CanPlayTypeResult { 872 match type_.parse::<Mime>() { 873 Ok(Mime(TopLevel::Application, SubLevel::OctetStream, _)) | 874 Err(_) => { 875 CanPlayTypeResult::_empty 876 }, 877 _ => CanPlayTypeResult::Maybe 878 } 879 } 880 881 // https://html.spec.whatwg.org/multipage/#dom-media-error GetError(&self) -> Option<DomRoot<MediaError>>882 fn GetError(&self) -> Option<DomRoot<MediaError>> { 883 self.error.get() 884 } 885 886 // https://html.spec.whatwg.org/multipage/#dom-media-play 887 #[allow(unrooted_must_root)] Play(&self) -> Rc<Promise>888 fn Play(&self) -> Rc<Promise> { 889 let promise = Promise::new(&self.global()); 890 self.play(&promise); 891 promise 892 } 893 894 // https://html.spec.whatwg.org/multipage/#dom-media-pause Pause(&self)895 fn Pause(&self) { 896 // Step 1 897 if self.network_state.get() == NetworkState::Empty { 898 self.invoke_resource_selection_algorithm(); 899 } 900 901 // Step 2 902 self.internal_pause_steps(); 903 } 904 905 // https://html.spec.whatwg.org/multipage/#dom-media-paused Paused(&self) -> bool906 fn Paused(&self) -> bool { 907 self.paused.get() 908 } 909 } 910 911 impl VirtualMethods for HTMLMediaElement { super_type(&self) -> Option<&VirtualMethods>912 fn super_type(&self) -> Option<&VirtualMethods> { 913 Some(self.upcast::<HTMLElement>() as &VirtualMethods) 914 } 915 attribute_mutated(&self, attr: &Attr, mutation: AttributeMutation)916 fn attribute_mutated(&self, attr: &Attr, mutation: AttributeMutation) { 917 self.super_type().unwrap().attribute_mutated(attr, mutation); 918 919 match attr.local_name() { 920 &local_name!("src") => { 921 if mutation.new_value(attr).is_some() { 922 self.media_element_load_algorithm(); 923 } 924 } 925 _ => (), 926 }; 927 } 928 929 // https://html.spec.whatwg.org/multipage/#playing-the-media-resource:remove-an-element-from-a-document unbind_from_tree(&self, context: &UnbindContext)930 fn unbind_from_tree(&self, context: &UnbindContext) { 931 self.super_type().unwrap().unbind_from_tree(context); 932 933 if context.tree_in_doc { 934 let task = MediaElementMicrotask::PauseIfNotInDocumentTask { 935 elem: DomRoot::from_ref(self) 936 }; 937 ScriptThread::await_stable_state(Microtask::MediaElement(task)); 938 } 939 } 940 } 941 942 #[derive(JSTraceable, MallocSizeOf)] 943 pub enum MediaElementMicrotask { 944 ResourceSelectionTask { 945 elem: DomRoot<HTMLMediaElement>, 946 generation_id: u32, 947 base_url: ServoUrl, 948 }, 949 PauseIfNotInDocumentTask { 950 elem: DomRoot<HTMLMediaElement>, 951 } 952 } 953 954 impl MicrotaskRunnable for MediaElementMicrotask { handler(&self)955 fn handler(&self) { 956 match self { 957 &MediaElementMicrotask::ResourceSelectionTask { ref elem, generation_id, ref base_url } => { 958 if generation_id == elem.generation_id.get() { 959 elem.resource_selection_algorithm_sync(base_url.clone()); 960 } 961 }, 962 &MediaElementMicrotask::PauseIfNotInDocumentTask { ref elem } => { 963 if !elem.upcast::<Node>().is_in_doc() { 964 elem.internal_pause_steps(); 965 } 966 }, 967 } 968 } 969 } 970 971 enum Resource { 972 Object, 973 Url(ServoUrl), 974 } 975 976 struct HTMLMediaElementContext { 977 /// The element that initiated the request. 978 elem: Trusted<HTMLMediaElement>, 979 /// The response body received to date. 980 data: Vec<u8>, 981 /// The response metadata received to date. 982 metadata: Option<Metadata>, 983 /// The generation of the media element when this fetch started. 984 generation_id: u32, 985 /// Time of last progress notification. 986 next_progress_event: Timespec, 987 /// Whether the media metadata has been completely received. 988 have_metadata: bool, 989 /// True if this response is invalid and should be ignored. 990 ignore_response: bool, 991 } 992 993 // https://html.spec.whatwg.org/multipage/#media-data-processing-steps-list 994 impl FetchResponseListener for HTMLMediaElementContext { process_request_body(&mut self)995 fn process_request_body(&mut self) {} 996 process_request_eof(&mut self)997 fn process_request_eof(&mut self) {} 998 process_response(&mut self, metadata: Result<FetchMetadata, NetworkError>)999 fn process_response(&mut self, metadata: Result<FetchMetadata, NetworkError>) { 1000 self.metadata = metadata.ok().map(|m| { 1001 match m { 1002 FetchMetadata::Unfiltered(m) => m, 1003 FetchMetadata::Filtered { unsafe_, .. } => unsafe_ 1004 } 1005 }); 1006 1007 let status_is_ok = self.metadata.as_ref() 1008 .and_then(|m| m.status.as_ref()) 1009 .map_or(true, |s| s.0 >= 200 && s.0 < 300); 1010 1011 // => "If the media data cannot be fetched at all..." 1012 if !status_is_ok { 1013 // Ensure that the element doesn't receive any further notifications 1014 // of the aborted fetch. 1015 self.ignore_response = true; 1016 self.elem.root().queue_dedicated_media_source_failure_steps(); 1017 } 1018 } 1019 process_response_chunk(&mut self, mut payload: Vec<u8>)1020 fn process_response_chunk(&mut self, mut payload: Vec<u8>) { 1021 if self.ignore_response { 1022 // An error was received previously, skip processing the payload. 1023 return; 1024 } 1025 1026 self.data.append(&mut payload); 1027 1028 let elem = self.elem.root(); 1029 1030 // https://html.spec.whatwg.org/multipage/#media-data-processing-steps-list 1031 // => "Once enough of the media data has been fetched to determine the duration..." 1032 if !self.have_metadata { 1033 self.check_metadata(&elem); 1034 } else { 1035 elem.change_ready_state(ReadyState::HaveCurrentData); 1036 } 1037 1038 // https://html.spec.whatwg.org/multipage/#concept-media-load-resource step 4, 1039 // => "If mode is remote" step 2 1040 if time::get_time() > self.next_progress_event { 1041 let window = window_from_node(&*elem); 1042 window.dom_manipulation_task_source().queue_simple_event( 1043 elem.upcast(), 1044 atom!("progress"), 1045 &window, 1046 ); 1047 self.next_progress_event = time::get_time() + Duration::milliseconds(350); 1048 } 1049 } 1050 1051 // https://html.spec.whatwg.org/multipage/#media-data-processing-steps-list process_response_eof(&mut self, status: Result<(), NetworkError>)1052 fn process_response_eof(&mut self, status: Result<(), NetworkError>) { 1053 if self.ignore_response { 1054 // An error was received previously, skip processing the payload. 1055 return; 1056 } 1057 let elem = self.elem.root(); 1058 1059 // => "If the media data can be fetched but is found by inspection to be in an unsupported 1060 // format, or can otherwise not be rendered at all" 1061 if !self.have_metadata { 1062 elem.queue_dedicated_media_source_failure_steps(); 1063 } 1064 // => "Once the entire media resource has been fetched..." 1065 else if status.is_ok() { 1066 elem.change_ready_state(ReadyState::HaveEnoughData); 1067 1068 elem.upcast::<EventTarget>().fire_event(atom!("progress")); 1069 1070 elem.network_state.set(NetworkState::Idle); 1071 1072 elem.upcast::<EventTarget>().fire_event(atom!("suspend")); 1073 } 1074 // => "If the connection is interrupted after some media data has been received..." 1075 else if elem.ready_state.get() != ReadyState::HaveNothing { 1076 // Step 2 1077 elem.error.set(Some(&*MediaError::new(&*window_from_node(&*elem), 1078 MEDIA_ERR_NETWORK))); 1079 1080 // Step 3 1081 elem.network_state.set(NetworkState::Idle); 1082 1083 // Step 4. 1084 elem.delay_load_event(false); 1085 1086 // Step 5 1087 elem.upcast::<EventTarget>().fire_event(atom!("error")); 1088 } else { 1089 // => "If the media data cannot be fetched at all..." 1090 elem.queue_dedicated_media_source_failure_steps(); 1091 } 1092 } 1093 } 1094 1095 impl PreInvoke for HTMLMediaElementContext { should_invoke(&self) -> bool1096 fn should_invoke(&self) -> bool { 1097 //TODO: finish_load needs to run at some point if the generation changes. 1098 self.elem.root().generation_id.get() == self.generation_id 1099 } 1100 } 1101 1102 impl HTMLMediaElementContext { new(elem: &HTMLMediaElement) -> HTMLMediaElementContext1103 fn new(elem: &HTMLMediaElement) -> HTMLMediaElementContext { 1104 HTMLMediaElementContext { 1105 elem: Trusted::new(elem), 1106 data: vec![], 1107 metadata: None, 1108 generation_id: elem.generation_id.get(), 1109 next_progress_event: time::get_time() + Duration::milliseconds(350), 1110 have_metadata: false, 1111 ignore_response: false, 1112 } 1113 } 1114 check_metadata(&mut self, elem: &HTMLMediaElement)1115 fn check_metadata(&mut self, elem: &HTMLMediaElement) { 1116 if audio_video_metadata::get_format_from_slice(&self.data).is_ok() { 1117 // Step 6. 1118 elem.change_ready_state(ReadyState::HaveMetadata); 1119 self.have_metadata = true; 1120 } 1121 } 1122 } 1123