script/dom/html/
htmlmediaelement.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5use std::cell::{Cell, RefCell};
6use std::collections::VecDeque;
7use std::rc::Rc;
8use std::sync::{Arc, Mutex, Weak};
9use std::time::{Duration, Instant};
10use std::{f64, mem};
11
12use compositing_traits::{CrossProcessCompositorApi, ImageUpdate, SerializableImageData};
13use content_security_policy::sandboxing_directive::SandboxingFlagSet;
14use dom_struct::dom_struct;
15use embedder_traits::{MediaPositionState, MediaSessionEvent, MediaSessionPlaybackState};
16use euclid::default::Size2D;
17use headers::{ContentLength, ContentRange, HeaderMapExt};
18use html5ever::{LocalName, Prefix, QualName, local_name, ns};
19use http::StatusCode;
20use http::header::{self, HeaderMap, HeaderValue};
21use ipc_channel::ipc::{self, IpcSharedMemory};
22use ipc_channel::router::ROUTER;
23use js::jsapi::JSAutoRealm;
24use layout_api::MediaFrame;
25use media::{GLPlayerMsg, GLPlayerMsgForward, WindowGLContext};
26use net_traits::request::{Destination, RequestId};
27use net_traits::{
28    CoreResourceThread, FetchMetadata, FilteredMetadata, NetworkError, ResourceFetchTiming,
29    ResourceTimingType,
30};
31use pixels::RasterImage;
32use script_bindings::codegen::GenericBindings::TimeRangesBinding::TimeRangesMethods;
33use script_bindings::codegen::InheritTypes::{
34    ElementTypeId, HTMLElementTypeId, HTMLMediaElementTypeId, NodeTypeId,
35};
36use servo_config::pref;
37use servo_media::player::audio::AudioRenderer;
38use servo_media::player::video::{VideoFrame, VideoFrameRenderer};
39use servo_media::player::{PlaybackState, Player, PlayerError, PlayerEvent, SeekLock, StreamType};
40use servo_media::{ClientContextId, ServoMedia, SupportsMediaType};
41use servo_url::ServoUrl;
42use stylo_atoms::Atom;
43use uuid::Uuid;
44use webrender_api::{
45    ExternalImageData, ExternalImageId, ExternalImageType, ImageBufferKind, ImageDescriptor,
46    ImageDescriptorFlags, ImageFormat, ImageKey,
47};
48
49use crate::document_loader::{LoadBlocker, LoadType};
50use crate::dom::attr::Attr;
51use crate::dom::audio::audiotrack::AudioTrack;
52use crate::dom::audio::audiotracklist::AudioTrackList;
53use crate::dom::bindings::cell::DomRefCell;
54use crate::dom::bindings::codegen::Bindings::HTMLMediaElementBinding::{
55    CanPlayTypeResult, HTMLMediaElementConstants, HTMLMediaElementMethods,
56};
57use crate::dom::bindings::codegen::Bindings::MediaErrorBinding::MediaErrorConstants::*;
58use crate::dom::bindings::codegen::Bindings::MediaErrorBinding::MediaErrorMethods;
59use crate::dom::bindings::codegen::Bindings::NavigatorBinding::Navigator_Binding::NavigatorMethods;
60use crate::dom::bindings::codegen::Bindings::NodeBinding::Node_Binding::NodeMethods;
61use crate::dom::bindings::codegen::Bindings::TextTrackBinding::{TextTrackKind, TextTrackMode};
62use crate::dom::bindings::codegen::Bindings::URLBinding::URLMethods;
63use crate::dom::bindings::codegen::Bindings::WindowBinding::Window_Binding::WindowMethods;
64use crate::dom::bindings::codegen::UnionTypes::{
65    MediaStreamOrBlob, VideoTrackOrAudioTrackOrTextTrack,
66};
67use crate::dom::bindings::error::{Error, ErrorResult, Fallible};
68use crate::dom::bindings::inheritance::Castable;
69use crate::dom::bindings::num::Finite;
70use crate::dom::bindings::refcounted::Trusted;
71use crate::dom::bindings::reflector::DomGlobal;
72use crate::dom::bindings::root::{Dom, DomRoot, MutNullableDom};
73use crate::dom::bindings::str::{DOMString, USVString};
74use crate::dom::blob::Blob;
75use crate::dom::csp::{GlobalCspReporting, Violation};
76use crate::dom::document::Document;
77use crate::dom::element::{
78    AttributeMutation, CustomElementCreationMode, Element, ElementCreator,
79    cors_setting_for_element, reflect_cross_origin_attribute, set_cross_origin_attribute,
80};
81use crate::dom::event::Event;
82use crate::dom::eventtarget::EventTarget;
83use crate::dom::globalscope::GlobalScope;
84use crate::dom::html::htmlelement::HTMLElement;
85use crate::dom::html::htmlsourceelement::HTMLSourceElement;
86use crate::dom::html::htmlvideoelement::HTMLVideoElement;
87use crate::dom::mediaerror::MediaError;
88use crate::dom::mediafragmentparser::MediaFragmentParser;
89use crate::dom::medialist::MediaList;
90use crate::dom::mediastream::MediaStream;
91use crate::dom::node::{Node, NodeDamage, NodeTraits, UnbindContext};
92use crate::dom::performance::performanceresourcetiming::InitiatorType;
93use crate::dom::promise::Promise;
94use crate::dom::texttrack::TextTrack;
95use crate::dom::texttracklist::TextTrackList;
96use crate::dom::timeranges::{TimeRanges, TimeRangesContainer};
97use crate::dom::trackevent::TrackEvent;
98use crate::dom::url::URL;
99use crate::dom::videotrack::VideoTrack;
100use crate::dom::videotracklist::VideoTrackList;
101use crate::dom::virtualmethods::VirtualMethods;
102use crate::fetch::{FetchCanceller, create_a_potential_cors_request};
103use crate::microtask::{Microtask, MicrotaskRunnable};
104use crate::network_listener::{self, FetchResponseListener, ResourceTimingListener};
105use crate::realms::{InRealm, enter_realm};
106use crate::script_runtime::CanGc;
107use crate::script_thread::ScriptThread;
108use crate::task_source::SendableTaskSource;
109
110/// A CSS file to style the media controls.
111static MEDIA_CONTROL_CSS: &str = include_str!("../../resources/media-controls.css");
112
113/// A JS file to control the media controls.
114static MEDIA_CONTROL_JS: &str = include_str!("../../resources/media-controls.js");
115
116#[derive(MallocSizeOf, PartialEq)]
117enum FrameStatus {
118    Locked,
119    Unlocked,
120}
121
122#[derive(MallocSizeOf)]
123struct FrameHolder(
124    FrameStatus,
125    #[ignore_malloc_size_of = "defined in servo-media"] VideoFrame,
126);
127
128impl FrameHolder {
129    fn new(frame: VideoFrame) -> FrameHolder {
130        FrameHolder(FrameStatus::Unlocked, frame)
131    }
132
133    fn lock(&mut self) {
134        if self.0 == FrameStatus::Unlocked {
135            self.0 = FrameStatus::Locked;
136        };
137    }
138
139    fn unlock(&mut self) {
140        if self.0 == FrameStatus::Locked {
141            self.0 = FrameStatus::Unlocked;
142        };
143    }
144
145    fn set(&mut self, new_frame: VideoFrame) {
146        if self.0 == FrameStatus::Unlocked {
147            self.1 = new_frame
148        };
149    }
150
151    fn get(&self) -> (u32, Size2D<i32>, usize) {
152        if self.0 == FrameStatus::Locked {
153            (
154                self.1.get_texture_id(),
155                Size2D::new(self.1.get_width(), self.1.get_height()),
156                0,
157            )
158        } else {
159            unreachable!();
160        }
161    }
162
163    fn get_frame(&self) -> VideoFrame {
164        self.1.clone()
165    }
166}
167
168#[derive(MallocSizeOf)]
169pub(crate) struct MediaFrameRenderer {
170    player_id: Option<usize>,
171    glplayer_id: Option<u64>,
172    compositor_api: CrossProcessCompositorApi,
173    #[ignore_malloc_size_of = "Defined in other crates"]
174    player_context: WindowGLContext,
175    current_frame: Option<MediaFrame>,
176    old_frame: Option<ImageKey>,
177    very_old_frame: Option<ImageKey>,
178    current_frame_holder: Option<FrameHolder>,
179    /// <https://html.spec.whatwg.org/multipage/#poster-frame>
180    poster_frame: Option<MediaFrame>,
181}
182
183impl MediaFrameRenderer {
184    fn new(compositor_api: CrossProcessCompositorApi, player_context: WindowGLContext) -> Self {
185        Self {
186            player_id: None,
187            glplayer_id: None,
188            compositor_api,
189            player_context,
190            current_frame: None,
191            old_frame: None,
192            very_old_frame: None,
193            current_frame_holder: None,
194            poster_frame: None,
195        }
196    }
197
198    fn setup(
199        &mut self,
200        player_id: usize,
201        task_source: SendableTaskSource,
202        weak_video_renderer: Weak<Mutex<MediaFrameRenderer>>,
203    ) {
204        self.player_id = Some(player_id);
205
206        let (glplayer_id, image_receiver) = self
207            .player_context
208            .glplayer_thread_sender
209            .as_ref()
210            .map(|sender| {
211                let (image_sender, image_receiver) = ipc::channel::<GLPlayerMsgForward>().unwrap();
212                sender
213                    .send(GLPlayerMsg::RegisterPlayer(image_sender))
214                    .unwrap();
215                match image_receiver.recv().unwrap() {
216                    GLPlayerMsgForward::PlayerId(id) => (Some(id), Some(image_receiver)),
217                    _ => unreachable!(),
218                }
219            })
220            .unwrap_or((None, None));
221
222        self.glplayer_id = glplayer_id;
223
224        let Some(image_receiver) = image_receiver else {
225            return;
226        };
227
228        ROUTER.add_typed_route(
229            image_receiver,
230            Box::new(move |message| {
231                let message = message.unwrap();
232                let weak_video_renderer = weak_video_renderer.clone();
233
234                task_source.queue(task!(handle_glplayer_message: move || {
235                    trace!("GLPlayer message {:?}", message);
236
237                    let Some(video_renderer) = weak_video_renderer.upgrade() else {
238                        return;
239                    };
240
241                    match message {
242                        GLPlayerMsgForward::Lock(sender) => {
243                            if let Some(holder) = video_renderer
244                                .lock()
245                                .unwrap()
246                                .current_frame_holder
247                                .as_mut() {
248                                    holder.lock();
249                                    sender.send(holder.get()).unwrap();
250                                };
251                        },
252                        GLPlayerMsgForward::Unlock() => {
253                            if let Some(holder) = video_renderer
254                                .lock()
255                                .unwrap()
256                                .current_frame_holder
257                                .as_mut() { holder.unlock() }
258                        },
259                        _ => (),
260                    }
261                }));
262            }),
263        );
264    }
265
266    fn reset(&mut self) {
267        self.player_id = None;
268
269        if let Some(glplayer_id) = self.glplayer_id.take() {
270            self.player_context
271                .send(GLPlayerMsg::UnregisterPlayer(glplayer_id));
272        }
273
274        self.current_frame_holder = None;
275
276        let mut updates = smallvec::smallvec![];
277
278        if let Some(current_frame) = self.current_frame.take() {
279            updates.push(ImageUpdate::DeleteImage(current_frame.image_key));
280        }
281
282        if let Some(old_image_key) = self.old_frame.take() {
283            updates.push(ImageUpdate::DeleteImage(old_image_key));
284        }
285
286        if let Some(very_old_image_key) = self.very_old_frame.take() {
287            updates.push(ImageUpdate::DeleteImage(very_old_image_key));
288        }
289
290        if !updates.is_empty() {
291            self.compositor_api.update_images(updates);
292        }
293    }
294
295    fn set_poster_frame(&mut self, image: Option<Arc<RasterImage>>) {
296        self.poster_frame = image.and_then(|image| {
297            image.id.map(|image_key| MediaFrame {
298                image_key,
299                width: image.metadata.width as i32,
300                height: image.metadata.height as i32,
301            })
302        });
303    }
304}
305
306impl Drop for MediaFrameRenderer {
307    fn drop(&mut self) {
308        self.reset();
309    }
310}
311
312impl VideoFrameRenderer for MediaFrameRenderer {
313    fn render(&mut self, frame: VideoFrame) {
314        if self.player_id.is_none() || (frame.is_gl_texture() && self.glplayer_id.is_none()) {
315            return;
316        }
317
318        let mut updates = smallvec::smallvec![];
319
320        if let Some(old_image_key) = mem::replace(&mut self.very_old_frame, self.old_frame.take()) {
321            updates.push(ImageUpdate::DeleteImage(old_image_key));
322        }
323
324        let descriptor = ImageDescriptor::new(
325            frame.get_width(),
326            frame.get_height(),
327            ImageFormat::BGRA8,
328            ImageDescriptorFlags::empty(),
329        );
330
331        match &mut self.current_frame {
332            Some(current_frame)
333                if current_frame.width == frame.get_width() &&
334                    current_frame.height == frame.get_height() =>
335            {
336                if !frame.is_gl_texture() {
337                    updates.push(ImageUpdate::UpdateImage(
338                        current_frame.image_key,
339                        descriptor,
340                        SerializableImageData::Raw(IpcSharedMemory::from_bytes(&frame.get_data())),
341                        None,
342                    ));
343                }
344
345                self.current_frame_holder
346                    .get_or_insert_with(|| FrameHolder::new(frame.clone()))
347                    .set(frame);
348
349                if let Some(old_image_key) = self.old_frame.take() {
350                    updates.push(ImageUpdate::DeleteImage(old_image_key));
351                }
352            },
353            Some(current_frame) => {
354                self.old_frame = Some(current_frame.image_key);
355
356                let Some(new_image_key) = self.compositor_api.generate_image_key_blocking() else {
357                    return;
358                };
359
360                /* update current_frame */
361                current_frame.image_key = new_image_key;
362                current_frame.width = frame.get_width();
363                current_frame.height = frame.get_height();
364
365                let image_data = if frame.is_gl_texture() && self.glplayer_id.is_some() {
366                    let texture_target = if frame.is_external_oes() {
367                        ImageBufferKind::TextureExternal
368                    } else {
369                        ImageBufferKind::Texture2D
370                    };
371
372                    SerializableImageData::External(ExternalImageData {
373                        id: ExternalImageId(self.glplayer_id.unwrap()),
374                        channel_index: 0,
375                        image_type: ExternalImageType::TextureHandle(texture_target),
376                        normalized_uvs: false,
377                    })
378                } else {
379                    SerializableImageData::Raw(IpcSharedMemory::from_bytes(&frame.get_data()))
380                };
381
382                self.current_frame_holder
383                    .get_or_insert_with(|| FrameHolder::new(frame.clone()))
384                    .set(frame);
385
386                updates.push(ImageUpdate::AddImage(new_image_key, descriptor, image_data));
387            },
388            None => {
389                let Some(image_key) = self.compositor_api.generate_image_key_blocking() else {
390                    return;
391                };
392
393                self.current_frame = Some(MediaFrame {
394                    image_key,
395                    width: frame.get_width(),
396                    height: frame.get_height(),
397                });
398
399                let image_data = if frame.is_gl_texture() && self.glplayer_id.is_some() {
400                    let texture_target = if frame.is_external_oes() {
401                        ImageBufferKind::TextureExternal
402                    } else {
403                        ImageBufferKind::Texture2D
404                    };
405
406                    SerializableImageData::External(ExternalImageData {
407                        id: ExternalImageId(self.glplayer_id.unwrap()),
408                        channel_index: 0,
409                        image_type: ExternalImageType::TextureHandle(texture_target),
410                        normalized_uvs: false,
411                    })
412                } else {
413                    SerializableImageData::Raw(IpcSharedMemory::from_bytes(&frame.get_data()))
414                };
415
416                self.current_frame_holder = Some(FrameHolder::new(frame));
417
418                updates.push(ImageUpdate::AddImage(image_key, descriptor, image_data));
419            },
420        }
421        self.compositor_api.update_images(updates);
422    }
423}
424
425#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
426#[derive(JSTraceable, MallocSizeOf)]
427enum SrcObject {
428    MediaStream(Dom<MediaStream>),
429    Blob(Dom<Blob>),
430}
431
432impl From<MediaStreamOrBlob> for SrcObject {
433    #[cfg_attr(crown, allow(crown::unrooted_must_root))]
434    fn from(src_object: MediaStreamOrBlob) -> SrcObject {
435        match src_object {
436            MediaStreamOrBlob::Blob(blob) => SrcObject::Blob(Dom::from_ref(&*blob)),
437            MediaStreamOrBlob::MediaStream(stream) => {
438                SrcObject::MediaStream(Dom::from_ref(&*stream))
439            },
440        }
441    }
442}
443
444#[derive(Clone, Copy, Debug, JSTraceable, MallocSizeOf, PartialEq)]
445enum LoadState {
446    NotLoaded,
447    LoadingFromSrcObject,
448    LoadingFromSrcAttribute,
449    LoadingFromSourceChild,
450    WaitingForSource,
451}
452
453/// <https://html.spec.whatwg.org/multipage/#loading-the-media-resource:media-element-29>
454#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
455#[derive(JSTraceable, MallocSizeOf)]
456struct SourceChildrenPointer {
457    source_before_pointer: Dom<HTMLSourceElement>,
458    inclusive: bool,
459}
460
461impl SourceChildrenPointer {
462    fn new(source_before_pointer: DomRoot<HTMLSourceElement>, inclusive: bool) -> Self {
463        Self {
464            source_before_pointer: source_before_pointer.as_traced(),
465            inclusive,
466        }
467    }
468}
469
470#[dom_struct]
471#[allow(non_snake_case)]
472pub(crate) struct HTMLMediaElement {
473    htmlelement: HTMLElement,
474    /// <https://html.spec.whatwg.org/multipage/#dom-media-networkstate>
475    network_state: Cell<NetworkState>,
476    /// <https://html.spec.whatwg.org/multipage/#dom-media-readystate>
477    ready_state: Cell<ReadyState>,
478    /// <https://html.spec.whatwg.org/multipage/#dom-media-srcobject>
479    src_object: DomRefCell<Option<SrcObject>>,
480    /// <https://html.spec.whatwg.org/multipage/#dom-media-currentsrc>
481    current_src: DomRefCell<String>,
482    /// Incremented whenever tasks associated with this element are cancelled.
483    generation_id: Cell<u32>,
484    /// <https://html.spec.whatwg.org/multipage/#fire-loadeddata>
485    ///
486    /// Reset to false every time the load algorithm is invoked.
487    fired_loadeddata_event: Cell<bool>,
488    /// <https://html.spec.whatwg.org/multipage/#dom-media-error>
489    error: MutNullableDom<MediaError>,
490    /// <https://html.spec.whatwg.org/multipage/#dom-media-paused>
491    paused: Cell<bool>,
492    /// <https://html.spec.whatwg.org/multipage/#dom-media-defaultplaybackrate>
493    defaultPlaybackRate: Cell<f64>,
494    /// <https://html.spec.whatwg.org/multipage/#dom-media-playbackrate>
495    playbackRate: Cell<f64>,
496    /// <https://html.spec.whatwg.org/multipage/#attr-media-autoplay>
497    autoplaying: Cell<bool>,
498    /// <https://html.spec.whatwg.org/multipage/#delaying-the-load-event-flag>
499    delaying_the_load_event_flag: DomRefCell<Option<LoadBlocker>>,
500    /// <https://html.spec.whatwg.org/multipage/#list-of-pending-play-promises>
501    #[conditional_malloc_size_of]
502    pending_play_promises: DomRefCell<Vec<Rc<Promise>>>,
503    /// Play promises which are soon to be fulfilled by a queued task.
504    #[allow(clippy::type_complexity)]
505    #[conditional_malloc_size_of]
506    in_flight_play_promises_queue: DomRefCell<VecDeque<(Box<[Rc<Promise>]>, ErrorResult)>>,
507    #[ignore_malloc_size_of = "servo_media"]
508    #[no_trace]
509    player: DomRefCell<Option<Arc<Mutex<dyn Player>>>>,
510    #[conditional_malloc_size_of]
511    #[no_trace]
512    video_renderer: Arc<Mutex<MediaFrameRenderer>>,
513    #[ignore_malloc_size_of = "servo_media"]
514    #[no_trace]
515    audio_renderer: DomRefCell<Option<Arc<Mutex<dyn AudioRenderer>>>>,
516    /// <https://html.spec.whatwg.org/multipage/#show-poster-flag>
517    show_poster: Cell<bool>,
518    /// <https://html.spec.whatwg.org/multipage/#dom-media-duration>
519    duration: Cell<f64>,
520    /// <https://html.spec.whatwg.org/multipage/#official-playback-position>
521    playback_position: Cell<f64>,
522    /// <https://html.spec.whatwg.org/multipage/#default-playback-start-position>
523    default_playback_start_position: Cell<f64>,
524    /// <https://html.spec.whatwg.org/multipage/#dom-media-volume>
525    volume: Cell<f64>,
526    /// <https://html.spec.whatwg.org/multipage/#dom-media-seeking>
527    seeking: Cell<bool>,
528    /// <https://html.spec.whatwg.org/multipage/#dom-media-muted>
529    muted: Cell<bool>,
530    /// Loading state from source, if any.
531    load_state: Cell<LoadState>,
532    source_children_pointer: DomRefCell<Option<SourceChildrenPointer>>,
533    current_source_child: MutNullableDom<HTMLSourceElement>,
534    /// URL of the media resource, if any.
535    #[no_trace]
536    resource_url: DomRefCell<Option<ServoUrl>>,
537    /// URL of the media resource, if the resource is set through the src_object attribute and it
538    /// is a blob.
539    #[no_trace]
540    blob_url: DomRefCell<Option<ServoUrl>>,
541    /// <https://html.spec.whatwg.org/multipage/#dom-media-played>
542    played: DomRefCell<TimeRangesContainer>,
543    // https://html.spec.whatwg.org/multipage/#dom-media-audiotracks
544    audio_tracks_list: MutNullableDom<AudioTrackList>,
545    // https://html.spec.whatwg.org/multipage/#dom-media-videotracks
546    video_tracks_list: MutNullableDom<VideoTrackList>,
547    /// <https://html.spec.whatwg.org/multipage/#dom-media-texttracks>
548    text_tracks_list: MutNullableDom<TextTrackList>,
549    /// Time of last timeupdate notification.
550    #[ignore_malloc_size_of = "Defined in std::time"]
551    next_timeupdate_event: Cell<Instant>,
552    /// Latest fetch request context.
553    current_fetch_context: RefCell<Option<HTMLMediaElementFetchContext>>,
554    /// Media controls id.
555    /// In order to workaround the lack of privileged JS context, we secure the
556    /// the access to the "privileged" document.servoGetMediaControls(id) API by
557    /// keeping a whitelist of media controls identifiers.
558    media_controls_id: DomRefCell<Option<String>>,
559}
560
561/// <https://html.spec.whatwg.org/multipage/#dom-media-networkstate>
562#[derive(Clone, Copy, JSTraceable, MallocSizeOf, PartialEq)]
563#[repr(u8)]
564pub(crate) enum NetworkState {
565    Empty = HTMLMediaElementConstants::NETWORK_EMPTY as u8,
566    Idle = HTMLMediaElementConstants::NETWORK_IDLE as u8,
567    Loading = HTMLMediaElementConstants::NETWORK_LOADING as u8,
568    NoSource = HTMLMediaElementConstants::NETWORK_NO_SOURCE as u8,
569}
570
571/// <https://html.spec.whatwg.org/multipage/#dom-media-readystate>
572#[derive(Clone, Copy, Debug, JSTraceable, MallocSizeOf, PartialEq, PartialOrd)]
573#[repr(u8)]
574#[allow(clippy::enum_variant_names)] // Clippy warning silenced here because these names are from the specification.
575pub(crate) enum ReadyState {
576    HaveNothing = HTMLMediaElementConstants::HAVE_NOTHING as u8,
577    HaveMetadata = HTMLMediaElementConstants::HAVE_METADATA as u8,
578    HaveCurrentData = HTMLMediaElementConstants::HAVE_CURRENT_DATA as u8,
579    HaveFutureData = HTMLMediaElementConstants::HAVE_FUTURE_DATA as u8,
580    HaveEnoughData = HTMLMediaElementConstants::HAVE_ENOUGH_DATA as u8,
581}
582
583impl HTMLMediaElement {
584    pub(crate) fn new_inherited(
585        tag_name: LocalName,
586        prefix: Option<Prefix>,
587        document: &Document,
588    ) -> Self {
589        Self {
590            htmlelement: HTMLElement::new_inherited(tag_name, prefix, document),
591            network_state: Cell::new(NetworkState::Empty),
592            ready_state: Cell::new(ReadyState::HaveNothing),
593            src_object: Default::default(),
594            current_src: DomRefCell::new("".to_owned()),
595            generation_id: Cell::new(0),
596            fired_loadeddata_event: Cell::new(false),
597            error: Default::default(),
598            paused: Cell::new(true),
599            defaultPlaybackRate: Cell::new(1.0),
600            playbackRate: Cell::new(1.0),
601            muted: Cell::new(false),
602            load_state: Cell::new(LoadState::NotLoaded),
603            source_children_pointer: DomRefCell::new(None),
604            current_source_child: Default::default(),
605            // FIXME(nox): Why is this initialised to true?
606            autoplaying: Cell::new(true),
607            delaying_the_load_event_flag: Default::default(),
608            pending_play_promises: Default::default(),
609            in_flight_play_promises_queue: Default::default(),
610            player: Default::default(),
611            video_renderer: Arc::new(Mutex::new(MediaFrameRenderer::new(
612                document.window().compositor_api().clone(),
613                document.window().get_player_context(),
614            ))),
615            audio_renderer: Default::default(),
616            show_poster: Cell::new(true),
617            duration: Cell::new(f64::NAN),
618            playback_position: Cell::new(0.),
619            default_playback_start_position: Cell::new(0.),
620            volume: Cell::new(1.0),
621            seeking: Cell::new(false),
622            resource_url: DomRefCell::new(None),
623            blob_url: DomRefCell::new(None),
624            played: DomRefCell::new(TimeRangesContainer::default()),
625            audio_tracks_list: Default::default(),
626            video_tracks_list: Default::default(),
627            text_tracks_list: Default::default(),
628            next_timeupdate_event: Cell::new(Instant::now() + Duration::from_millis(250)),
629            current_fetch_context: RefCell::new(None),
630            media_controls_id: DomRefCell::new(None),
631        }
632    }
633
634    pub(crate) fn network_state(&self) -> NetworkState {
635        self.network_state.get()
636    }
637
638    pub(crate) fn get_ready_state(&self) -> ReadyState {
639        self.ready_state.get()
640    }
641
642    fn media_type_id(&self) -> HTMLMediaElementTypeId {
643        match self.upcast::<Node>().type_id() {
644            NodeTypeId::Element(ElementTypeId::HTMLElement(
645                HTMLElementTypeId::HTMLMediaElement(media_type_id),
646            )) => media_type_id,
647            _ => unreachable!(),
648        }
649    }
650
651    fn play_media(&self) {
652        if let Some(ref player) = *self.player.borrow() {
653            if let Err(e) = player.lock().unwrap().set_rate(self.playbackRate.get()) {
654                warn!("Could not set the playback rate {:?}", e);
655            }
656            if let Err(e) = player.lock().unwrap().play() {
657                warn!("Could not play media {:?}", e);
658            }
659        }
660    }
661
662    /// Marks that element as delaying the load event or not.
663    ///
664    /// Nothing happens if the element was already delaying the load event and
665    /// we pass true to that method again.
666    ///
667    /// <https://html.spec.whatwg.org/multipage/#delaying-the-load-event-flag>
668    pub(crate) fn delay_load_event(&self, delay: bool, can_gc: CanGc) {
669        let blocker = &self.delaying_the_load_event_flag;
670        if delay && blocker.borrow().is_none() {
671            *blocker.borrow_mut() = Some(LoadBlocker::new(&self.owner_document(), LoadType::Media));
672        } else if !delay && blocker.borrow().is_some() {
673            LoadBlocker::terminate(blocker, can_gc);
674        }
675    }
676
677    /// <https://html.spec.whatwg.org/multipage/#time-marches-on>
678    fn time_marches_on(&self) {
679        // Step 6.
680        if Instant::now() > self.next_timeupdate_event.get() {
681            self.owner_global()
682                .task_manager()
683                .media_element_task_source()
684                .queue_simple_event(self.upcast(), atom!("timeupdate"));
685            self.next_timeupdate_event
686                .set(Instant::now() + Duration::from_millis(350));
687        }
688    }
689
690    /// <https://html.spec.whatwg.org/multipage/#internal-pause-steps>
691    fn internal_pause_steps(&self) {
692        // Step 1.
693        self.autoplaying.set(false);
694
695        // Step 2.
696        if !self.Paused() {
697            // Step 2.1.
698            self.paused.set(true);
699
700            // Step 2.2.
701            self.take_pending_play_promises(Err(Error::Abort));
702
703            // Step 2.3.
704            let this = Trusted::new(self);
705            let generation_id = self.generation_id.get();
706            self.owner_global()
707                .task_manager()
708                .media_element_task_source()
709                .queue(task!(internal_pause_steps: move || {
710                    let this = this.root();
711                    if generation_id != this.generation_id.get() {
712                        return;
713                    }
714
715                    this.fulfill_in_flight_play_promises(|| {
716                        // Step 2.3.1.
717                        this.upcast::<EventTarget>().fire_event(atom!("timeupdate"), CanGc::note());
718
719                        // Step 2.3.2.
720                        this.upcast::<EventTarget>().fire_event(atom!("pause"), CanGc::note());
721
722                        if let Some(ref player) = *this.player.borrow() {
723                            if let Err(e) = player.lock().unwrap().pause() {
724                                error!("Could not pause player {:?}", e);
725                            }
726                        }
727
728                        // Step 2.3.3.
729                        // Done after running this closure in
730                        // `fulfill_in_flight_play_promises`.
731                    });
732                }));
733
734            // Step 2.4.
735            // FIXME(nox): Set the official playback position to the current
736            // playback position.
737        }
738    }
739    /// <https://html.spec.whatwg.org/multipage/#allowed-to-play>
740    fn is_allowed_to_play(&self) -> bool {
741        true
742    }
743
744    /// <https://html.spec.whatwg.org/multipage/#notify-about-playing>
745    fn notify_about_playing(&self) {
746        // Step 1.
747        self.take_pending_play_promises(Ok(()));
748
749        // Step 2.
750        let this = Trusted::new(self);
751        let generation_id = self.generation_id.get();
752        self.owner_global()
753            .task_manager()
754            .media_element_task_source()
755            .queue(task!(notify_about_playing: move || {
756                let this = this.root();
757                if generation_id != this.generation_id.get() {
758                    return;
759                }
760
761                this.fulfill_in_flight_play_promises(|| {
762                    // Step 2.1.
763                    this.upcast::<EventTarget>().fire_event(atom!("playing"), CanGc::note());
764                    this.play_media();
765
766                    // Step 2.2.
767                    // Done after running this closure in
768                    // `fulfill_in_flight_play_promises`.
769                });
770
771            }));
772    }
773
774    /// <https://html.spec.whatwg.org/multipage/#ready-states>
775    fn change_ready_state(&self, ready_state: ReadyState) {
776        let old_ready_state = self.ready_state.get();
777        self.ready_state.set(ready_state);
778
779        if self.network_state.get() == NetworkState::Empty {
780            return;
781        }
782
783        if old_ready_state == ready_state {
784            return;
785        }
786
787        let owner_global = self.owner_global();
788        let task_manager = owner_global.task_manager();
789        let task_source = task_manager.media_element_task_source();
790
791        // Step 1.
792        match (old_ready_state, ready_state) {
793            (ReadyState::HaveNothing, ReadyState::HaveMetadata) => {
794                task_source.queue_simple_event(self.upcast(), atom!("loadedmetadata"));
795                // No other steps are applicable in this case.
796                return;
797            },
798            (ReadyState::HaveMetadata, new) if new >= ReadyState::HaveCurrentData => {
799                if !self.fired_loadeddata_event.get() {
800                    self.fired_loadeddata_event.set(true);
801                    let this = Trusted::new(self);
802                    task_source.queue(task!(media_reached_current_data: move || {
803                        let this = this.root();
804                        this.upcast::<EventTarget>().fire_event(atom!("loadeddata"), CanGc::note());
805                        this.delay_load_event(false, CanGc::note());
806                    }));
807                }
808
809                // Steps for the transition from HaveMetadata to HaveCurrentData
810                // or HaveFutureData also apply here, as per the next match
811                // expression.
812            },
813            (ReadyState::HaveFutureData, new) if new <= ReadyState::HaveCurrentData => {
814                // FIXME(nox): Queue a task to fire timeupdate and waiting
815                // events if the conditions call from the spec are met.
816
817                // No other steps are applicable in this case.
818                return;
819            },
820
821            _ => (),
822        }
823
824        if old_ready_state <= ReadyState::HaveCurrentData &&
825            ready_state >= ReadyState::HaveFutureData
826        {
827            task_source.queue_simple_event(self.upcast(), atom!("canplay"));
828
829            if !self.Paused() {
830                self.notify_about_playing();
831            }
832        }
833
834        if ready_state == ReadyState::HaveEnoughData {
835            // FIXME(nox): Review this block.
836            if self.eligible_for_autoplay() {
837                // Step 1
838                self.paused.set(false);
839                // Step 2
840                if self.show_poster.get() {
841                    self.show_poster.set(false);
842                    self.time_marches_on();
843                }
844                // Step 3
845                task_source.queue_simple_event(self.upcast(), atom!("play"));
846                // Step 4
847                self.notify_about_playing();
848                // Step 5
849                self.autoplaying.set(false);
850            }
851
852            // FIXME(nox): According to the spec, this should come *before* the
853            // "play" event.
854            task_source.queue_simple_event(self.upcast(), atom!("canplaythrough"));
855        }
856    }
857
858    /// <https://html.spec.whatwg.org/multipage/#concept-media-load-algorithm>
859    fn invoke_resource_selection_algorithm(&self, can_gc: CanGc) {
860        // Step 1. Set the element's networkState attribute to the NETWORK_NO_SOURCE value.
861        self.network_state.set(NetworkState::NoSource);
862
863        // Step 2. Set the element's show poster flag to true.
864        self.show_poster.set(true);
865
866        // Step 3. Set the media element's delaying-the-load-event flag to true (this delays the
867        // load event).
868        self.delay_load_event(true, can_gc);
869
870        // Step 4. Await a stable state, allowing the task that invoked this algorithm to continue.
871        // If the resource selection mode in the synchronous section is
872        // "attribute", the URL of the resource to fetch is relative to the
873        // media element's node document when the src attribute was last
874        // changed, which is why we need to pass the base URL in the task
875        // right here.
876        let task = MediaElementMicrotask::ResourceSelection {
877            elem: DomRoot::from_ref(self),
878            generation_id: self.generation_id.get(),
879            base_url: self.owner_document().base_url(),
880        };
881
882        // FIXME(nox): This will later call the resource_selection_algorithm_sync
883        // method from below, if microtasks were trait objects, we would be able
884        // to put the code directly in this method, without the boilerplate
885        // indirections.
886        ScriptThread::await_stable_state(Microtask::MediaElement(task));
887    }
888
889    /// <https://html.spec.whatwg.org/multipage/#concept-media-load-algorithm>
890    fn resource_selection_algorithm_sync(&self, base_url: ServoUrl, can_gc: CanGc) {
891        // TODO Step 5. If the media element's blocked-on-parser flag is false, then populate the
892        // list of pending text tracks.
893        // FIXME(ferjm): Implement blocked_on_parser logic
894        // https://html.spec.whatwg.org/multipage/#blocked-on-parser
895        // FIXME(nox): Maybe populate the list of pending text tracks.
896
897        enum Mode {
898            Object,
899            Attribute(String),
900            Children(DomRoot<HTMLSourceElement>),
901        }
902
903        // Step 6.
904        let mode = if self.src_object.borrow().is_some() {
905            // If the media element has an assigned media provider object, then let mode be object.
906            Mode::Object
907        } else if let Some(attribute) = self
908            .upcast::<Element>()
909            .get_attribute(&ns!(), &local_name!("src"))
910        {
911            // Otherwise, if the media element has no assigned media provider object but has a src
912            // attribute, then let mode be attribute.
913            Mode::Attribute((**attribute.value()).to_owned())
914        } else if let Some(source) = self
915            .upcast::<Node>()
916            .children()
917            .filter_map(DomRoot::downcast::<HTMLSourceElement>)
918            .next()
919        {
920            // Otherwise, if the media element does not have an assigned media provider object and
921            // does not have a src attribute, but does have a source element child, then let mode be
922            // children and let candidate be the first such source element child in tree order.
923            Mode::Children(source)
924        } else {
925            // Otherwise, the media element has no assigned media provider object and has neither a
926            // src attribute nor a source element child:
927            self.load_state.set(LoadState::NotLoaded);
928
929            // Step 6.none.1. Set the networkState to NETWORK_EMPTY.
930            self.network_state.set(NetworkState::Empty);
931
932            // Step 6.none.2. Set the element's delaying-the-load-event flag to false. This stops
933            // delaying the load event.
934            self.delay_load_event(false, can_gc);
935
936            // Step 6.none.3. End the synchronous section and return.
937            return;
938        };
939
940        // Step 7. Set the media element's networkState to NETWORK_LOADING.
941        self.network_state.set(NetworkState::Loading);
942
943        // Step 8. Queue a media element task given the media element to fire an event named
944        // loadstart at the media element.
945        self.queue_media_element_task_to_fire_event(atom!("loadstart"));
946
947        // Step 9. Run the appropriate steps from the following list:
948        match mode {
949            Mode::Object => {
950                // => "If mode is object"
951                self.load_from_src_object();
952            },
953            Mode::Attribute(src) => {
954                // => "If mode is attribute"
955                self.load_from_src_attribute(base_url, &src);
956            },
957            Mode::Children(source) => {
958                // => "Otherwise (mode is children)""
959                self.load_from_source_child(&source);
960            },
961        }
962    }
963
964    /// <https://html.spec.whatwg.org/multipage/#concept-media-load-algorithm>
965    fn load_from_src_object(&self) {
966        self.load_state.set(LoadState::LoadingFromSrcObject);
967
968        // Step 9.object.1. Set the currentSrc attribute to the empty string.
969        "".clone_into(&mut self.current_src.borrow_mut());
970
971        // Step 9.object.3. Run the resource fetch algorithm with the assigned media
972        // provider object. If that algorithm returns without aborting this one, then the
973        // load failed.
974        // Note that the resource fetch algorithm itself takes care of the cleanup in case
975        // of failure itself.
976        self.resource_fetch_algorithm(Resource::Object);
977    }
978
979    /// <https://html.spec.whatwg.org/multipage/#concept-media-load-algorithm>
980    fn load_from_src_attribute(&self, base_url: ServoUrl, src: &str) {
981        self.load_state.set(LoadState::LoadingFromSrcAttribute);
982
983        // Step 9.attribute.1. If the src attribute's value is the empty string, then end
984        // the synchronous section, and jump down to the failed with attribute step below.
985        if src.is_empty() {
986            self.queue_dedicated_media_source_failure_steps();
987            return;
988        }
989
990        // Step 9.attribute.2. Let urlRecord be the result of encoding-parsing a URL given
991        // the src attribute's value, relative to the media element's node document when the
992        // src attribute was last changed.
993        let Ok(url_record) = base_url.join(src) else {
994            self.queue_dedicated_media_source_failure_steps();
995            return;
996        };
997
998        // Step 9.attribute.3. If urlRecord is not failure, then set the currentSrc
999        // attribute to the result of applying the URL serializer to urlRecord.
1000        *self.current_src.borrow_mut() = url_record.as_str().into();
1001
1002        // Step 9.attribute.5. If urlRecord is not failure, then run the resource fetch
1003        // algorithm with urlRecord. If that algorithm returns without aborting this one,
1004        // then the load failed.
1005        // Note that the resource fetch algorithm itself takes care
1006        // of the cleanup in case of failure itself.
1007        self.resource_fetch_algorithm(Resource::Url(url_record));
1008    }
1009
1010    /// <https://html.spec.whatwg.org/multipage/#concept-media-load-algorithm>
1011    fn load_from_source_child(&self, source: &HTMLSourceElement) {
1012        self.load_state.set(LoadState::LoadingFromSourceChild);
1013
1014        // Step 9.children.1. Let pointer be a position defined by two adjacent nodes in the media
1015        // element's child list, treating the start of the list (before the first child in the list,
1016        // if any) and end of the list (after the last child in the list, if any) as nodes in their
1017        // own right. One node is the node before pointer, and the other node is the node after
1018        // pointer. Initially, let pointer be the position between the candidate node and the next
1019        // node, if there are any, or the end of the list, if it is the last node.
1020        *self.source_children_pointer.borrow_mut() =
1021            Some(SourceChildrenPointer::new(DomRoot::from_ref(source), false));
1022
1023        let element = source.upcast::<Element>();
1024
1025        // Step 9.children.2. Process candidate: If candidate does not have a src attribute, or if
1026        // its src attribute's value is the empty string, then end the synchronous section, and jump
1027        // down to the failed with elements step below.
1028        let Some(src) = element
1029            .get_attribute(&ns!(), &local_name!("src"))
1030            .filter(|attribute| !attribute.value().is_empty())
1031        else {
1032            self.load_from_source_child_failure_steps(source);
1033            return;
1034        };
1035
1036        // Step 9.children.3. If candidate has a media attribute whose value does not match the
1037        // environment, then end the synchronous section, and jump down to the failed with elements
1038        // step below.
1039        if let Some(media) = element.get_attribute(&ns!(), &local_name!("media")) {
1040            if !MediaList::matches_environment(&element.owner_document(), &media.value()) {
1041                self.load_from_source_child_failure_steps(source);
1042                return;
1043            }
1044        }
1045
1046        // Step 9.children.4. Let urlRecord be the result of encoding-parsing a URL given
1047        // candidate's src attribute's value, relative to candidate's node document when the src
1048        // attribute was last changed.
1049        let Ok(url_record) = source.owner_document().base_url().join(&src.value()) else {
1050            // Step 9.children.5. If urlRecord is failure, then end the synchronous section,
1051            // and jump down to the failed with elements step below.
1052            self.load_from_source_child_failure_steps(source);
1053            return;
1054        };
1055
1056        // Step 9.children.6. If candidate has a type attribute whose value, when parsed as a MIME
1057        // type (including any codecs described by the codecs parameter, for types that define that
1058        // parameter), represents a type that the user agent knows it cannot render, then end the
1059        // synchronous section, and jump down to the failed with elements step below.
1060        if let Some(type_) = element.get_attribute(&ns!(), &local_name!("type")) {
1061            if ServoMedia::get().can_play_type(&type_.value()) == SupportsMediaType::No {
1062                self.load_from_source_child_failure_steps(source);
1063                return;
1064            }
1065        }
1066
1067        // Reset the media player before loading the next source child.
1068        self.reset_media_player();
1069
1070        self.current_source_child.set(Some(source));
1071
1072        // Step 9.children.7. Set the currentSrc attribute to the result of applying the URL
1073        // serializer to urlRecord.
1074        *self.current_src.borrow_mut() = url_record.as_str().into();
1075
1076        // Step 9.children.9. Run the resource fetch algorithm with urlRecord. If that
1077        // algorithm returns without aborting this one, then the load failed.
1078        // Note that the resource fetch algorithm itself takes care
1079        // of the cleanup in case of failure itself.
1080        self.resource_fetch_algorithm(Resource::Url(url_record));
1081    }
1082
1083    /// <https://html.spec.whatwg.org/multipage/#concept-media-load-algorithm>
1084    fn load_from_source_child_failure_steps(&self, source: &HTMLSourceElement) {
1085        // Step 9.children.10. Failed with elements: Queue a media element task given the media
1086        // element to fire an event named error at candidate.
1087        let trusted_this = Trusted::new(self);
1088        let trusted_source = Trusted::new(source);
1089        let generation_id = self.generation_id.get();
1090
1091        self.owner_global()
1092            .task_manager()
1093            .media_element_task_source()
1094            .queue(task!(queue_error_event: move || {
1095                let this = trusted_this.root();
1096                if generation_id != this.generation_id.get() {
1097                    return;
1098                }
1099
1100                let source = trusted_source.root();
1101                source.upcast::<EventTarget>().fire_event(atom!("error"), CanGc::note());
1102            }));
1103
1104        // Step 9.children.11. Await a stable state.
1105        let task = MediaElementMicrotask::SelectNextSourceChild {
1106            elem: DomRoot::from_ref(self),
1107            generation_id: self.generation_id.get(),
1108        };
1109
1110        ScriptThread::await_stable_state(Microtask::MediaElement(task));
1111    }
1112
1113    /// <https://html.spec.whatwg.org/multipage/#concept-media-load-algorithm>
1114    fn select_next_source_child(&self, can_gc: CanGc) {
1115        // Step 9.children.12. Forget the media element's media-resource-specific tracks.
1116        self.AudioTracks(can_gc).clear();
1117        self.VideoTracks(can_gc).clear();
1118
1119        // Step 9.children.13. Find next candidate: Let candidate be null.
1120        let mut source_candidate = None;
1121
1122        // Step 9.children.14. Search loop: If the node after pointer is the end of the list, then
1123        // jump to the waiting step below.
1124        // Step 9.children.15. If the node after pointer is a source element, let candidate be that
1125        // element.
1126        // Step 9.children.16. Advance pointer so that the node before pointer is now the node that
1127        // was after pointer, and the node after pointer is the node after the node that used to be
1128        // after pointer, if any.
1129        if let Some(ref source_children_pointer) = *self.source_children_pointer.borrow() {
1130            // Note that shared implementation between opaque types from
1131            // `inclusively_following_siblings` and `following_siblings` if not possible due to
1132            // precise capturing.
1133            if source_children_pointer.inclusive {
1134                for next_sibling in source_children_pointer
1135                    .source_before_pointer
1136                    .upcast::<Node>()
1137                    .inclusively_following_siblings()
1138                {
1139                    if let Some(next_source) = DomRoot::downcast::<HTMLSourceElement>(next_sibling)
1140                    {
1141                        source_candidate = Some(next_source);
1142                        break;
1143                    }
1144                }
1145            } else {
1146                for next_sibling in source_children_pointer
1147                    .source_before_pointer
1148                    .upcast::<Node>()
1149                    .following_siblings()
1150                {
1151                    if let Some(next_source) = DomRoot::downcast::<HTMLSourceElement>(next_sibling)
1152                    {
1153                        source_candidate = Some(next_source);
1154                        break;
1155                    }
1156                }
1157            };
1158        }
1159
1160        // Step 9.children.17. If candidate is null, jump back to the search loop step. Otherwise,
1161        // jump back to the process candidate step.
1162        if let Some(source_candidate) = source_candidate {
1163            self.load_from_source_child(&source_candidate);
1164            return;
1165        }
1166
1167        self.load_state.set(LoadState::WaitingForSource);
1168
1169        *self.source_children_pointer.borrow_mut() = None;
1170
1171        // Step 9.children.18. Waiting: Set the element's networkState attribute to the
1172        // NETWORK_NO_SOURCE value.
1173        self.network_state.set(NetworkState::NoSource);
1174
1175        // Step 9.children.19. Set the element's show poster flag to true.
1176        self.show_poster.set(true);
1177
1178        // Step 9.children.20. Queue a media element task given the media element to set the
1179        // element's delaying-the-load-event flag to false. This stops delaying the load event.
1180        let this = Trusted::new(self);
1181        let generation_id = self.generation_id.get();
1182
1183        self.owner_global()
1184            .task_manager()
1185            .media_element_task_source()
1186            .queue(task!(queue_delay_load_event: move || {
1187                let this = this.root();
1188                if generation_id != this.generation_id.get() {
1189                    return;
1190                }
1191
1192                this.delay_load_event(false, CanGc::note());
1193            }));
1194
1195        // Step 9.children.22. Wait until the node after pointer is a node other than the end of the
1196        // list. (This step might wait forever.)
1197    }
1198
1199    /// <https://html.spec.whatwg.org/multipage/#concept-media-load-algorithm>
1200    fn resource_selection_algorithm_failure_steps(&self) {
1201        match self.load_state.get() {
1202            LoadState::LoadingFromSrcObject => {
1203                // Step 9.object.4. Failed with media provider: Reaching this step indicates that
1204                // the media resource failed to load. Take pending play promises and queue a media
1205                // element task given the media element to run the dedicated media source failure
1206                // steps with the result.
1207                self.queue_dedicated_media_source_failure_steps();
1208            },
1209            LoadState::LoadingFromSrcAttribute => {
1210                // Step 9.attribute.6. Failed with attribute: Reaching this step indicates that the
1211                // media resource failed to load or that urlRecord is failure. Take pending play
1212                // promises and queue a media element task given the media element to run the
1213                // dedicated media source failure steps with the result.
1214                self.queue_dedicated_media_source_failure_steps();
1215            },
1216            LoadState::LoadingFromSourceChild => {
1217                // Step 9.children.10. Failed with elements: Queue a media element task given the
1218                // media element to fire an event named error at candidate.
1219                if let Some(source) = self.current_source_child.take() {
1220                    self.load_from_source_child_failure_steps(&source);
1221                }
1222            },
1223            _ => {},
1224        }
1225    }
1226
1227    fn fetch_request(&self, offset: Option<u64>, seek_lock: Option<SeekLock>) {
1228        if self.resource_url.borrow().is_none() && self.blob_url.borrow().is_none() {
1229            error!("Missing request url");
1230            if let Some(seek_lock) = seek_lock {
1231                seek_lock.unlock(/* successful seek */ false);
1232            }
1233            self.resource_selection_algorithm_failure_steps();
1234            return;
1235        }
1236
1237        let document = self.owner_document();
1238        let destination = match self.media_type_id() {
1239            HTMLMediaElementTypeId::HTMLAudioElement => Destination::Audio,
1240            HTMLMediaElementTypeId::HTMLVideoElement => Destination::Video,
1241        };
1242        let mut headers = HeaderMap::new();
1243        // FIXME(eijebong): Use typed headers once we have a constructor for the range header
1244        headers.insert(
1245            header::RANGE,
1246            HeaderValue::from_str(&format!("bytes={}-", offset.unwrap_or(0))).unwrap(),
1247        );
1248        let url = match self.resource_url.borrow().as_ref() {
1249            Some(url) => url.clone(),
1250            None => self.blob_url.borrow().as_ref().unwrap().clone(),
1251        };
1252
1253        let cors_setting = cors_setting_for_element(self.upcast());
1254        let global = self.global();
1255        let request = create_a_potential_cors_request(
1256            Some(document.webview_id()),
1257            url.clone(),
1258            destination,
1259            cors_setting,
1260            None,
1261            global.get_referrer(),
1262            document.insecure_requests_policy(),
1263            document.has_trustworthy_ancestor_or_current_origin(),
1264            global.policy_container(),
1265        )
1266        .headers(headers)
1267        .origin(document.origin().immutable().clone())
1268        .pipeline_id(Some(self.global().pipeline_id()))
1269        .referrer_policy(document.get_referrer_policy());
1270
1271        let mut current_fetch_context = self.current_fetch_context.borrow_mut();
1272        if let Some(ref mut current_fetch_context) = *current_fetch_context {
1273            current_fetch_context.cancel(CancelReason::Abort);
1274        }
1275
1276        *current_fetch_context = Some(HTMLMediaElementFetchContext::new(
1277            request.id,
1278            global.core_resource_thread(),
1279        ));
1280        let listener =
1281            HTMLMediaElementFetchListener::new(self, request.id, url.clone(), offset.unwrap_or(0));
1282
1283        self.owner_document().fetch_background(request, listener);
1284
1285        // Since we cancelled the previous fetch, from now on the media element
1286        // will only receive response data from the new fetch that's been
1287        // initiated. This means the player can resume operation, since all subsequent data
1288        // pushes will originate from the new seek offset.
1289        if let Some(seek_lock) = seek_lock {
1290            seek_lock.unlock(/* successful seek */ true);
1291        }
1292    }
1293
1294    /// <https://html.spec.whatwg.org/multipage/#eligible-for-autoplay>
1295    fn eligible_for_autoplay(&self) -> bool {
1296        // its can autoplay flag is true;
1297        self.autoplaying.get() &&
1298
1299        // its paused attribute is true;
1300        self.Paused() &&
1301
1302        // it has an autoplay attribute specified;
1303        self.Autoplay() &&
1304
1305        // its node document's active sandboxing flag set does not have the sandboxed automatic
1306        // features browsing context flag set; and
1307        {
1308            let document = self.owner_document();
1309
1310            !document.has_active_sandboxing_flag(
1311                SandboxingFlagSet::SANDBOXED_AUTOMATIC_FEATURES_BROWSING_CONTEXT_FLAG,
1312            )
1313        }
1314
1315        // its node document is allowed to use the "autoplay" feature.
1316        // TODO: Feature policy: https://html.spec.whatwg.org/iframe-embed-object.html#allowed-to-use
1317    }
1318
1319    /// <https://html.spec.whatwg.org/multipage/#concept-media-load-resource>
1320    fn resource_fetch_algorithm(&self, resource: Resource) {
1321        if let Err(e) = self.create_media_player(&resource) {
1322            error!("Create media player error {:?}", e);
1323            self.resource_selection_algorithm_failure_steps();
1324            return;
1325        }
1326
1327        // Steps 1-2.
1328        // Unapplicable, the `resource` variable already conveys which mode
1329        // is in use.
1330
1331        // Step 3.
1332        // FIXME(nox): Remove all media-resource-specific text tracks.
1333
1334        // Step 5. Run the appropriate steps from the following list:
1335        match resource {
1336            Resource::Url(url) => {
1337                // Step 5.remote.1. Optionally, run the following substeps. This is the expected
1338                // behavior if the user agent intends to not attempt to fetch the resource until the
1339                // user requests it explicitly (e.g. as a way to implement the preload attribute's
1340                // none keyword).
1341                if self.Preload() == "none" && !self.autoplaying.get() {
1342                    // Step 5.remote.1.1. Set the networkState to NETWORK_IDLE.
1343                    self.network_state.set(NetworkState::Idle);
1344
1345                    // Step 5.remote.1.2. Queue a media element task given the media element to fire
1346                    // an event named suspend at the element.
1347                    self.queue_media_element_task_to_fire_event(atom!("suspend"));
1348
1349                    // Step 5.remote.1.3. Queue a media element task given the media element to set
1350                    // the element's delaying-the-load-event flag to false. This stops delaying the
1351                    // load event.
1352                    let this = Trusted::new(self);
1353                    let generation_id = self.generation_id.get();
1354
1355                    self.owner_global()
1356                        .task_manager()
1357                        .media_element_task_source()
1358                        .queue(task!(queue_delay_load_event: move || {
1359                            let this = this.root();
1360                            if generation_id != this.generation_id.get() {
1361                                return;
1362                            }
1363
1364                            this.delay_load_event(false, CanGc::note());
1365                        }));
1366
1367                    // TODO Steps 5.remote.1.4. Wait for the task to be run.
1368                    // FIXME(nox): Somehow we should wait for the task from previous
1369                    // step to be ran before continuing.
1370
1371                    // TODO Steps 5.remote.1.5-5.remote.1.7.
1372                    // FIXME(nox): Wait for an implementation-defined event and
1373                    // then continue with the normal set of steps instead of just
1374                    // returning.
1375                    return;
1376                }
1377
1378                *self.resource_url.borrow_mut() = Some(url);
1379
1380                // Steps 5.remote.2-5.remote.8
1381                self.fetch_request(None, None);
1382            },
1383            Resource::Object => {
1384                if let Some(ref src_object) = *self.src_object.borrow() {
1385                    match src_object {
1386                        SrcObject::Blob(blob) => {
1387                            let blob_url = URL::CreateObjectURL(&self.global(), blob);
1388                            *self.blob_url.borrow_mut() =
1389                                Some(ServoUrl::parse(&blob_url.str()).expect("infallible"));
1390                            self.fetch_request(None, None);
1391                        },
1392                        SrcObject::MediaStream(stream) => {
1393                            let tracks = &*stream.get_tracks();
1394                            for (pos, track) in tracks.iter().enumerate() {
1395                                if self
1396                                    .player
1397                                    .borrow()
1398                                    .as_ref()
1399                                    .unwrap()
1400                                    .lock()
1401                                    .unwrap()
1402                                    .set_stream(&track.id(), pos == tracks.len() - 1)
1403                                    .is_err()
1404                                {
1405                                    self.resource_selection_algorithm_failure_steps();
1406                                }
1407                            }
1408                        },
1409                    }
1410                }
1411            },
1412        }
1413    }
1414
1415    /// Queues a task to run the [dedicated media source failure steps][steps].
1416    ///
1417    /// [steps]: https://html.spec.whatwg.org/multipage/#dedicated-media-source-failure-steps
1418    fn queue_dedicated_media_source_failure_steps(&self) {
1419        let this = Trusted::new(self);
1420        let generation_id = self.generation_id.get();
1421        self.take_pending_play_promises(Err(Error::NotSupported));
1422        self.owner_global()
1423            .task_manager()
1424            .media_element_task_source()
1425            .queue(task!(dedicated_media_source_failure_steps: move || {
1426                let this = this.root();
1427                if generation_id != this.generation_id.get() {
1428                    return;
1429                }
1430
1431                this.fulfill_in_flight_play_promises(|| {
1432                    // Step 1. Set the error attribute to the result of creating a MediaError with
1433                    // MEDIA_ERR_SRC_NOT_SUPPORTED.
1434                    this.error.set(Some(&*MediaError::new(
1435                        &this.owner_window(),
1436                        MEDIA_ERR_SRC_NOT_SUPPORTED, CanGc::note())));
1437
1438                    // Step 2. Forget the media element's media-resource-specific tracks.
1439                    this.AudioTracks(CanGc::note()).clear();
1440                    this.VideoTracks(CanGc::note()).clear();
1441
1442                    // Step 3. Set the element's networkState attribute to the NETWORK_NO_SOURCE
1443                    // value.
1444                    this.network_state.set(NetworkState::NoSource);
1445
1446                    // Step 4. Set the element's show poster flag to true.
1447                    this.show_poster.set(true);
1448
1449                    // Step 5. Fire an event named error at the media element.
1450                    this.upcast::<EventTarget>().fire_event(atom!("error"), CanGc::note());
1451
1452                    if let Some(ref player) = *this.player.borrow() {
1453                        if let Err(e) = player.lock().unwrap().stop() {
1454                            error!("Could not stop player {:?}", e);
1455                        }
1456                    }
1457
1458                    // Step 6. Reject pending play promises with promises and a "NotSupportedError"
1459                    // DOMException.
1460                    // Done after running this closure in `fulfill_in_flight_play_promises`.
1461                });
1462
1463                // Step 7. Set the element's delaying-the-load-event flag to false. This stops
1464                // delaying the load event.
1465                this.delay_load_event(false, CanGc::note());
1466            }));
1467    }
1468
1469    fn in_error_state(&self) -> bool {
1470        self.error.get().is_some()
1471    }
1472
1473    /// <https://html.spec.whatwg.org/multipage/#potentially-playing>
1474    fn is_potentially_playing(&self) -> bool {
1475        !self.paused.get() &&
1476            !self.Ended() &&
1477            self.error.get().is_none() &&
1478            !self.is_blocked_media_element()
1479    }
1480
1481    /// <https://html.spec.whatwg.org/multipage/#blocked-media-element>
1482    fn is_blocked_media_element(&self) -> bool {
1483        self.ready_state.get() <= ReadyState::HaveCurrentData ||
1484            self.is_paused_for_user_interaction() ||
1485            self.is_paused_for_in_band_content()
1486    }
1487
1488    /// <https://html.spec.whatwg.org/multipage/#paused-for-user-interaction>
1489    fn is_paused_for_user_interaction(&self) -> bool {
1490        // FIXME: we will likely be able to fill this placeholder once (if) we
1491        //        implement the MediaSession API.
1492        false
1493    }
1494
1495    /// <https://html.spec.whatwg.org/multipage/#paused-for-in-band-content>
1496    fn is_paused_for_in_band_content(&self) -> bool {
1497        // FIXME: we will likely be able to fill this placeholder once (if) we
1498        //        implement https://github.com/servo/servo/issues/22314
1499        false
1500    }
1501
1502    /// <https://html.spec.whatwg.org/multipage/#media-element-load-algorithm>
1503    fn media_element_load_algorithm(&self, can_gc: CanGc) {
1504        // Reset the flag that signals whether loadeddata was ever fired for
1505        // this invokation of the load algorithm.
1506        self.fired_loadeddata_event.set(false);
1507
1508        // TODO Step 1. Set this element's is currently stalled to false.
1509
1510        // Step 2. Abort any already-running instance of the resource selection algorithm for this
1511        // element.
1512        self.generation_id.set(self.generation_id.get() + 1);
1513
1514        self.load_state.set(LoadState::NotLoaded);
1515        *self.source_children_pointer.borrow_mut() = None;
1516        self.current_source_child.set(None);
1517
1518        // Step 3. Let pending tasks be a list of all tasks from the media element's media element
1519        // event task source in one of the task queues.
1520
1521        // Step 4. For each task in pending tasks that would resolve pending play promises or reject
1522        // pending play promises, immediately resolve or reject those promises in the order the
1523        // corresponding tasks were queued.
1524        while !self.in_flight_play_promises_queue.borrow().is_empty() {
1525            self.fulfill_in_flight_play_promises(|| ());
1526        }
1527
1528        // Step 5. Remove each task in pending tasks from its task queue.
1529        // Note that each media element's pending event and callback is scheduled with associated
1530        // generation id and will be aborted eventually (from Step 2).
1531
1532        let network_state = self.network_state.get();
1533
1534        // Step 6. If the media element's networkState is set to NETWORK_LOADING or NETWORK_IDLE,
1535        // queue a media element task given the media element to fire an event named abort at the
1536        // media element.
1537        if network_state == NetworkState::Loading || network_state == NetworkState::Idle {
1538            self.queue_media_element_task_to_fire_event(atom!("abort"));
1539        }
1540
1541        // Reset the media player for any previously playing media resource (see Step 11).
1542        self.reset_media_player();
1543
1544        // Step 7. If the media element's networkState is not set to NETWORK_EMPTY, then:
1545        if network_state != NetworkState::Empty {
1546            // Step 7.1. Queue a media element task given the media element to fire an event named
1547            // emptied at the media element.
1548            self.queue_media_element_task_to_fire_event(atom!("emptied"));
1549
1550            // Step 7.2. If a fetching process is in progress for the media element, the user agent
1551            // should stop it.
1552            if let Some(ref mut current_fetch_context) = *self.current_fetch_context.borrow_mut() {
1553                current_fetch_context.cancel(CancelReason::Abort);
1554            }
1555
1556            // TODO Step 7.3. If the media element's assigned media provider object is a MediaSource
1557            // object, then detach it.
1558
1559            // Step 7.4. Forget the media element's media-resource-specific tracks.
1560            self.AudioTracks(can_gc).clear();
1561            self.VideoTracks(can_gc).clear();
1562
1563            // Step 7.5. If readyState is not set to HAVE_NOTHING, then set it to that state.
1564            if self.ready_state.get() != ReadyState::HaveNothing {
1565                self.change_ready_state(ReadyState::HaveNothing);
1566            }
1567
1568            // Step 7.6. If the paused attribute is false, then:
1569            if !self.Paused() {
1570                // Step 7.6.1. Set the paused attribute to true.
1571                self.paused.set(true);
1572
1573                // Step 7.6.2. Take pending play promises and reject pending play promises with the
1574                // result and an "AbortError" DOMException.
1575                self.take_pending_play_promises(Err(Error::Abort));
1576                self.fulfill_in_flight_play_promises(|| ());
1577            }
1578
1579            // Step 7.7. If seeking is true, set it to false.
1580            self.seeking.set(false);
1581
1582            // Step 7.8. Set the current playback position to 0.
1583            // Set the official playback position to 0.
1584            // If this changed the official playback position, then queue a media element task given
1585            // the media element to fire an event named timeupdate at the media element.
1586            if self.playback_position.get() != 0. {
1587                self.queue_media_element_task_to_fire_event(atom!("timeupdate"));
1588            }
1589            self.playback_position.set(0.);
1590
1591            // TODO Step 7.9. Set the timeline offset to Not-a-Number (NaN).
1592
1593            // Step 7.10. Update the duration attribute to Not-a-Number (NaN).
1594            self.duration.set(f64::NAN);
1595        }
1596
1597        // Step 8. Set the playbackRate attribute to the value of the defaultPlaybackRate attribute.
1598        self.playbackRate.set(self.defaultPlaybackRate.get());
1599
1600        // Step 9. Set the error attribute to null and the can autoplay flag to true.
1601        self.error.set(None);
1602        self.autoplaying.set(true);
1603
1604        // Step 10. Invoke the media element's resource selection algorithm.
1605        self.invoke_resource_selection_algorithm(can_gc);
1606
1607        // Step 11. Note: Playback of any previously playing media resource for this element stops.
1608    }
1609
1610    /// Queue a media element task given the media element to fire an event at the media element.
1611    /// <https://html.spec.whatwg.org/multipage/#queue-a-media-element-task>
1612    fn queue_media_element_task_to_fire_event(&self, name: Atom) {
1613        let this = Trusted::new(self);
1614        let generation_id = self.generation_id.get();
1615
1616        self.owner_global()
1617            .task_manager()
1618            .media_element_task_source()
1619            .queue(task!(queue_event: move || {
1620                let this = this.root();
1621                if generation_id != this.generation_id.get() {
1622                    return;
1623                }
1624
1625                this.upcast::<EventTarget>().fire_event(name, CanGc::note());
1626            }));
1627    }
1628
1629    /// Appends a promise to the list of pending play promises.
1630    fn push_pending_play_promise(&self, promise: &Rc<Promise>) {
1631        self.pending_play_promises
1632            .borrow_mut()
1633            .push(promise.clone());
1634    }
1635
1636    /// Takes the pending play promises.
1637    ///
1638    /// The result with which these promises will be fulfilled is passed here
1639    /// and this method returns nothing because we actually just move the
1640    /// current list of pending play promises to the
1641    /// `in_flight_play_promises_queue` field.
1642    ///
1643    /// Each call to this method must be followed by a call to
1644    /// `fulfill_in_flight_play_promises`, to actually fulfill the promises
1645    /// which were taken and moved to the in-flight queue.
1646    fn take_pending_play_promises(&self, result: ErrorResult) {
1647        let pending_play_promises = std::mem::take(&mut *self.pending_play_promises.borrow_mut());
1648        self.in_flight_play_promises_queue
1649            .borrow_mut()
1650            .push_back((pending_play_promises.into(), result));
1651    }
1652
1653    /// Fulfills the next in-flight play promises queue after running a closure.
1654    ///
1655    /// See the comment on `take_pending_play_promises` for why this method
1656    /// does not take a list of promises to fulfill. Callers cannot just pop
1657    /// the front list off of `in_flight_play_promises_queue` and later fulfill
1658    /// the promises because that would mean putting
1659    /// `#[cfg_attr(crown, allow(crown::unrooted_must_root))]` on even more functions, potentially
1660    /// hiding actual safety bugs.
1661    #[cfg_attr(crown, allow(crown::unrooted_must_root))]
1662    fn fulfill_in_flight_play_promises<F>(&self, f: F)
1663    where
1664        F: FnOnce(),
1665    {
1666        let (promises, result) = self
1667            .in_flight_play_promises_queue
1668            .borrow_mut()
1669            .pop_front()
1670            .expect("there should be at least one list of in flight play promises");
1671        f();
1672        for promise in &*promises {
1673            match result {
1674                Ok(ref value) => promise.resolve_native(value, CanGc::note()),
1675                Err(ref error) => promise.reject_error(error.clone(), CanGc::note()),
1676            }
1677        }
1678    }
1679
1680    pub(crate) fn handle_source_child_insertion(&self, source: &HTMLSourceElement, can_gc: CanGc) {
1681        // <https://html.spec.whatwg.org/multipage/#the-source-element:html-element-insertion-steps>
1682        // Step 2. If parent is a media element that has no src attribute and whose networkState has
1683        // the value NETWORK_EMPTY, then invoke that media element's resource selection algorithm.
1684        if self.upcast::<Element>().has_attribute(&local_name!("src")) {
1685            return;
1686        }
1687
1688        if self.network_state.get() == NetworkState::Empty {
1689            self.invoke_resource_selection_algorithm(can_gc);
1690            return;
1691        }
1692
1693        // <https://html.spec.whatwg.org/multipage/#concept-media-load-algorithm>
1694        // Step 9.children.22. Wait until the node after pointer is a node other than the end of the
1695        // list. (This step might wait forever.)
1696        if self.load_state.get() != LoadState::WaitingForSource {
1697            return;
1698        }
1699
1700        self.load_state.set(LoadState::LoadingFromSourceChild);
1701
1702        *self.source_children_pointer.borrow_mut() =
1703            Some(SourceChildrenPointer::new(DomRoot::from_ref(source), true));
1704
1705        // Step 9.children.23. Await a stable state.
1706        let task = MediaElementMicrotask::SelectNextSourceChildAfterWait {
1707            elem: DomRoot::from_ref(self),
1708            generation_id: self.generation_id.get(),
1709        };
1710
1711        ScriptThread::await_stable_state(Microtask::MediaElement(task));
1712    }
1713
1714    /// <https://html.spec.whatwg.org/multipage/#concept-media-load-algorithm>
1715    fn select_next_source_child_after_wait(&self, can_gc: CanGc) {
1716        // Step 9.children.24. Set the element's delaying-the-load-event flag back to true (this
1717        // delays the load event again, in case it hasn't been fired yet).
1718        self.delay_load_event(true, can_gc);
1719
1720        // Step 9.children.25. Set the networkState back to NETWORK_LOADING.
1721        self.network_state.set(NetworkState::Loading);
1722
1723        // Step 9.children.26. Jump back to the find next candidate step above.
1724        self.select_next_source_child(can_gc);
1725    }
1726
1727    /// <https://html.spec.whatwg.org/multipage/#media-data-processing-steps-list>
1728    /// => "If the media data cannot be fetched at all, due to network errors..."
1729    /// => "If the media data can be fetched but is found by inspection to be in an unsupported
1730    /// format, or can otherwise not be rendered at all"
1731    fn media_data_processing_failure_steps(&self) {
1732        // Step 1. The user agent should cancel the fetching process.
1733        if let Some(ref mut current_fetch_context) = *self.current_fetch_context.borrow_mut() {
1734            current_fetch_context.cancel(CancelReason::Error);
1735        }
1736
1737        // Step 2. Abort this subalgorithm, returning to the resource selection algorithm.
1738        self.resource_selection_algorithm_failure_steps();
1739    }
1740
1741    /// <https://html.spec.whatwg.org/multipage/#media-data-processing-steps-list>
1742    /// => "If the connection is interrupted after some media data has been received..."
1743    /// => "If the media data is corrupted"
1744    fn media_data_processing_fatal_steps(&self, error: u16, can_gc: CanGc) {
1745        *self.source_children_pointer.borrow_mut() = None;
1746        self.current_source_child.set(None);
1747
1748        // Step 1. The user agent should cancel the fetching process.
1749        if let Some(ref mut current_fetch_context) = *self.current_fetch_context.borrow_mut() {
1750            current_fetch_context.cancel(CancelReason::Error);
1751        }
1752
1753        // Step 2. Set the error attribute to the result of creating a MediaError with
1754        // MEDIA_ERR_NETWORK/MEDIA_ERR_DECODE.
1755        self.error
1756            .set(Some(&*MediaError::new(&self.owner_window(), error, can_gc)));
1757
1758        // Step 3. Set the element's networkState attribute to the NETWORK_IDLE value.
1759        self.network_state.set(NetworkState::Idle);
1760
1761        // Step 4. Set the element's delaying-the-load-event flag to false. This stops delaying
1762        // the load event.
1763        self.delay_load_event(false, can_gc);
1764
1765        // Step 5. Fire an event named error at the media element.
1766        self.upcast::<EventTarget>()
1767            .fire_event(atom!("error"), can_gc);
1768
1769        // Step 6. Abort the overall resource selection algorithm.
1770    }
1771
1772    /// <https://html.spec.whatwg.org/multipage/#dom-media-seek>
1773    fn seek(&self, time: f64, _approximate_for_speed: bool, can_gc: CanGc) {
1774        // Step 1.
1775        self.show_poster.set(false);
1776
1777        // Step 2.
1778        if self.ready_state.get() == ReadyState::HaveNothing {
1779            return;
1780        }
1781
1782        // Step 3.
1783        // The fetch request associated with this seek already takes
1784        // care of cancelling any previous requests.
1785
1786        // Step 4.
1787        // The flag will be cleared when the media engine tells us the seek was done.
1788        self.seeking.set(true);
1789
1790        // Step 5.
1791        // XXX(ferjm) The rest of the steps should be run in parallel, so seeking cancelation
1792        //            can be done properly. No other browser does it yet anyway.
1793
1794        // Step 6.
1795        let time = f64::min(time, self.Duration());
1796
1797        // Step 7.
1798        let time = f64::max(time, 0.);
1799
1800        // Step 8.
1801        let seekable = self.Seekable(can_gc);
1802        if seekable.Length() == 0 {
1803            self.seeking.set(false);
1804            return;
1805        }
1806        let mut nearest_seekable_position = 0.0;
1807        let mut in_seekable_range = false;
1808        let mut nearest_seekable_distance = f64::MAX;
1809        for i in 0..seekable.Length() {
1810            let start = seekable.Start(i).unwrap().abs();
1811            let end = seekable.End(i).unwrap().abs();
1812            if time >= start && time <= end {
1813                nearest_seekable_position = time;
1814                in_seekable_range = true;
1815                break;
1816            } else if time < start {
1817                let distance = start - time;
1818                if distance < nearest_seekable_distance {
1819                    nearest_seekable_distance = distance;
1820                    nearest_seekable_position = start;
1821                }
1822            } else {
1823                let distance = time - end;
1824                if distance < nearest_seekable_distance {
1825                    nearest_seekable_distance = distance;
1826                    nearest_seekable_position = end;
1827                }
1828            }
1829        }
1830        let time = if in_seekable_range {
1831            time
1832        } else {
1833            nearest_seekable_position
1834        };
1835
1836        // Step 9.
1837        // servo-media with gstreamer does not support inaccurate seeking for now.
1838
1839        // Step 10.
1840        self.owner_global()
1841            .task_manager()
1842            .media_element_task_source()
1843            .queue_simple_event(self.upcast(), atom!("seeking"));
1844
1845        // Step 11.
1846        if let Some(ref player) = *self.player.borrow() {
1847            if let Err(e) = player.lock().unwrap().seek(time) {
1848                error!("Seek error {:?}", e);
1849            }
1850        }
1851
1852        // The rest of the steps are handled when the media engine signals a
1853        // ready state change or otherwise satisfies seek completion and signals
1854        // a position change.
1855    }
1856
1857    /// <https://html.spec.whatwg.org/multipage/#dom-media-seek>
1858    fn seek_end(&self) {
1859        // Step 14.
1860        self.seeking.set(false);
1861
1862        // Step 15.
1863        self.time_marches_on();
1864
1865        // Step 16.
1866        let global = self.owner_global();
1867        let task_manager = global.task_manager();
1868        let task_source = task_manager.media_element_task_source();
1869        task_source.queue_simple_event(self.upcast(), atom!("timeupdate"));
1870
1871        // Step 17.
1872        task_source.queue_simple_event(self.upcast(), atom!("seeked"));
1873    }
1874
1875    /// <https://html.spec.whatwg.org/multipage/#poster-frame>
1876    pub(crate) fn set_poster_frame(&self, image: Option<Arc<RasterImage>>) {
1877        let queue_postershown_event = pref!(media_testing_enabled) && image.is_some();
1878
1879        self.video_renderer.lock().unwrap().set_poster_frame(image);
1880
1881        self.upcast::<Node>().dirty(NodeDamage::Other);
1882
1883        if queue_postershown_event {
1884            self.owner_global()
1885                .task_manager()
1886                .media_element_task_source()
1887                .queue_simple_event(self.upcast(), atom!("postershown"));
1888        }
1889    }
1890
1891    fn create_media_player(&self, resource: &Resource) -> Result<(), ()> {
1892        let stream_type = match *resource {
1893            Resource::Object => {
1894                if let Some(ref src_object) = *self.src_object.borrow() {
1895                    match src_object {
1896                        SrcObject::MediaStream(_) => StreamType::Stream,
1897                        _ => StreamType::Seekable,
1898                    }
1899                } else {
1900                    return Err(());
1901                }
1902            },
1903            _ => StreamType::Seekable,
1904        };
1905
1906        let window = self.owner_window();
1907        let (action_sender, action_receiver) = ipc::channel::<PlayerEvent>().unwrap();
1908        let video_renderer: Option<Arc<Mutex<dyn VideoFrameRenderer>>> = match self.media_type_id()
1909        {
1910            HTMLMediaElementTypeId::HTMLAudioElement => None,
1911            HTMLMediaElementTypeId::HTMLVideoElement => Some(self.video_renderer.clone()),
1912        };
1913
1914        let audio_renderer = self.audio_renderer.borrow().as_ref().cloned();
1915
1916        let pipeline_id = window.pipeline_id();
1917        let client_context_id =
1918            ClientContextId::build(pipeline_id.namespace_id.0, pipeline_id.index.0.get());
1919        let player = ServoMedia::get().create_player(
1920            &client_context_id,
1921            stream_type,
1922            action_sender,
1923            video_renderer,
1924            audio_renderer,
1925            Box::new(window.get_player_context()),
1926        );
1927        let player_id = {
1928            let player_guard = player.lock().unwrap();
1929
1930            if let Err(e) = player_guard.set_mute(self.muted.get()) {
1931                log::warn!("Could not set mute state: {:?}", e);
1932            }
1933
1934            player_guard.get_id()
1935        };
1936
1937        *self.player.borrow_mut() = Some(player);
1938
1939        let trusted_node = Trusted::new(self);
1940        let task_source = self
1941            .owner_global()
1942            .task_manager()
1943            .media_element_task_source()
1944            .to_sendable();
1945        ROUTER.add_typed_route(
1946            action_receiver,
1947            Box::new(move |message| {
1948                let event = message.unwrap();
1949                trace!("Player event {:?}", event);
1950                let this = trusted_node.clone();
1951                task_source.queue(task!(handle_player_event: move || {
1952                    this.root().handle_player_event(player_id, &event, CanGc::note());
1953                }));
1954            }),
1955        );
1956
1957        let task_source = self
1958            .owner_global()
1959            .task_manager()
1960            .media_element_task_source()
1961            .to_sendable();
1962        let weak_video_renderer = Arc::downgrade(&self.video_renderer);
1963
1964        self.video_renderer
1965            .lock()
1966            .unwrap()
1967            .setup(player_id, task_source, weak_video_renderer);
1968
1969        Ok(())
1970    }
1971
1972    fn reset_media_player(&self) {
1973        if self.player.borrow().is_none() {
1974            return;
1975        }
1976
1977        if let Some(ref player) = *self.player.borrow() {
1978            if let Err(e) = player.lock().unwrap().stop() {
1979                error!("Could not stop player {:?}", e);
1980            }
1981        }
1982
1983        *self.player.borrow_mut() = None;
1984        self.video_renderer.lock().unwrap().reset();
1985        self.handle_resize(None, None);
1986    }
1987
1988    pub(crate) fn set_audio_track(&self, idx: usize, enabled: bool) {
1989        if let Some(ref player) = *self.player.borrow() {
1990            if let Err(err) = player.lock().unwrap().set_audio_track(idx as i32, enabled) {
1991                warn!("Could not set audio track {:#?}", err);
1992            }
1993        }
1994    }
1995
1996    pub(crate) fn set_video_track(&self, idx: usize, enabled: bool) {
1997        if let Some(ref player) = *self.player.borrow() {
1998            if let Err(err) = player.lock().unwrap().set_video_track(idx as i32, enabled) {
1999                warn!("Could not set video track {:#?}", err);
2000            }
2001        }
2002    }
2003
2004    fn end_of_playback_in_forwards_direction(&self, can_gc: CanGc) {
2005        // Step 1. If the media element has a loop attribute specified, then seek to the earliest
2006        // posible position of the media resource and return.
2007        if self.Loop() {
2008            self.seek(
2009                self.earliest_possible_position(),
2010                /* approximate_for_speed*/ false,
2011                can_gc,
2012            );
2013            return;
2014        }
2015        // Step 2. The ended IDL attribute starts returning true once the event loop returns to
2016        // step 1.
2017        // The **ended playback** condition is implemented inside of
2018        // the HTMLMediaElementMethods::Ended method
2019
2020        // Step 3. Queue a media element task given the media element and the following steps:
2021        let this = Trusted::new(self);
2022
2023        self.owner_global()
2024            .task_manager()
2025            .media_element_task_source()
2026            .queue(task!(reaches_the_end_steps: move || {
2027                let this = this.root();
2028                // Step 3.1. Fire an event named timeupdate at the media element
2029                this.upcast::<EventTarget>().fire_event(atom!("timeupdate"), CanGc::note());
2030
2031                // Step 3.2. If the media element has ended playback, the direction of playback is
2032                // forwards, and paused is false, then:
2033                if this.Ended() && !this.Paused() {
2034                    // Step 3.2.1. Set the paused attribute to true
2035                    this.paused.set(true);
2036
2037                    // Step 3.2.2. Fire an event named pause at the media element
2038                    this.upcast::<EventTarget>().fire_event(atom!("pause"), CanGc::note());
2039
2040                    // Step 3.2.3. Take pending play promises and reject pending play promises with
2041                    // the result and an "AbortError" DOMException
2042                    this.take_pending_play_promises(Err(Error::Abort));
2043                    this.fulfill_in_flight_play_promises(|| ());
2044                }
2045
2046                // Step 3.3. Fire an event named ended at the media element.
2047                this.upcast::<EventTarget>().fire_event(atom!("ended"), CanGc::note());
2048            }));
2049
2050        // https://html.spec.whatwg.org/multipage/#dom-media-have_current_data
2051        self.change_ready_state(ReadyState::HaveCurrentData);
2052    }
2053
2054    fn playback_end(&self, can_gc: CanGc) {
2055        // https://html.spec.whatwg.org/multipage/#reaches-the-end
2056        match self.direction_of_playback() {
2057            PlaybackDirection::Forwards => self.end_of_playback_in_forwards_direction(can_gc),
2058
2059            PlaybackDirection::Backwards => {
2060                if self.playback_position.get() <= self.earliest_possible_position() {
2061                    self.owner_global()
2062                        .task_manager()
2063                        .media_element_task_source()
2064                        .queue_simple_event(self.upcast(), atom!("ended"));
2065                }
2066            },
2067        }
2068    }
2069
2070    fn playback_error(&self, error: &str, can_gc: CanGc) {
2071        error!("Player error: {:?}", error);
2072
2073        // If we have already flagged an error condition while processing
2074        // the network response, we should silently skip any observable
2075        // errors originating while decoding the erroneous response.
2076        if self.in_error_state() {
2077            return;
2078        }
2079
2080        // <https://html.spec.whatwg.org/multipage/#media-data-processing-steps-list>
2081        if self.ready_state.get() == ReadyState::HaveNothing {
2082            // => "If the media data can be fetched but is found by inspection to be in an
2083            // unsupported format, or can otherwise not be rendered at all"
2084            self.media_data_processing_failure_steps();
2085        } else {
2086            // => "If the media data is corrupted"
2087            self.media_data_processing_fatal_steps(MEDIA_ERR_DECODE, can_gc);
2088        }
2089    }
2090
2091    fn playback_metadata_updated(
2092        &self,
2093        metadata: &servo_media::player::metadata::Metadata,
2094        can_gc: CanGc,
2095    ) {
2096        // https://html.spec.whatwg.org/multipage/#media-data-processing-steps-list
2097        // => If the media resource is found to have an audio track
2098        if !metadata.audio_tracks.is_empty() {
2099            for (i, _track) in metadata.audio_tracks.iter().enumerate() {
2100                // Step 1.
2101                let kind = match i {
2102                    0 => DOMString::from("main"),
2103                    _ => DOMString::new(),
2104                };
2105                let window = self.owner_window();
2106                let audio_track = AudioTrack::new(
2107                    &window,
2108                    DOMString::new(),
2109                    kind,
2110                    DOMString::new(),
2111                    DOMString::new(),
2112                    Some(&*self.AudioTracks(can_gc)),
2113                    can_gc,
2114                );
2115
2116                // Steps 2. & 3.
2117                self.AudioTracks(can_gc).add(&audio_track);
2118
2119                // Step 4
2120                if let Some(servo_url) = self.resource_url.borrow().as_ref() {
2121                    let fragment = MediaFragmentParser::from(servo_url);
2122                    if let Some(id) = fragment.id() {
2123                        if audio_track.id() == id {
2124                            self.AudioTracks(can_gc)
2125                                .set_enabled(self.AudioTracks(can_gc).len() - 1, true);
2126                        }
2127                    }
2128
2129                    if fragment.tracks().contains(&audio_track.kind().into()) {
2130                        self.AudioTracks(can_gc)
2131                            .set_enabled(self.AudioTracks(can_gc).len() - 1, true);
2132                    }
2133                }
2134
2135                // Step 5. & 6,
2136                if self.AudioTracks(can_gc).enabled_index().is_none() {
2137                    self.AudioTracks(can_gc)
2138                        .set_enabled(self.AudioTracks(can_gc).len() - 1, true);
2139                }
2140
2141                // Steps 7.
2142                let event = TrackEvent::new(
2143                    self.global().as_window(),
2144                    atom!("addtrack"),
2145                    false,
2146                    false,
2147                    &Some(VideoTrackOrAudioTrackOrTextTrack::AudioTrack(audio_track)),
2148                    can_gc,
2149                );
2150
2151                event
2152                    .upcast::<Event>()
2153                    .fire(self.upcast::<EventTarget>(), can_gc);
2154            }
2155        }
2156
2157        // => If the media resource is found to have a video track
2158        if !metadata.video_tracks.is_empty() {
2159            for (i, _track) in metadata.video_tracks.iter().enumerate() {
2160                // Step 1.
2161                let kind = match i {
2162                    0 => DOMString::from("main"),
2163                    _ => DOMString::new(),
2164                };
2165                let window = self.owner_window();
2166                let video_track = VideoTrack::new(
2167                    &window,
2168                    DOMString::new(),
2169                    kind,
2170                    DOMString::new(),
2171                    DOMString::new(),
2172                    Some(&*self.VideoTracks(can_gc)),
2173                    can_gc,
2174                );
2175
2176                // Steps 2. & 3.
2177                self.VideoTracks(can_gc).add(&video_track);
2178
2179                // Step 4.
2180                if let Some(track) = self.VideoTracks(can_gc).item(0) {
2181                    if let Some(servo_url) = self.resource_url.borrow().as_ref() {
2182                        let fragment = MediaFragmentParser::from(servo_url);
2183                        if let Some(id) = fragment.id() {
2184                            if track.id() == id {
2185                                self.VideoTracks(can_gc).set_selected(0, true);
2186                            }
2187                        } else if fragment.tracks().contains(&track.kind().into()) {
2188                            self.VideoTracks(can_gc).set_selected(0, true);
2189                        }
2190                    }
2191                }
2192
2193                // Step 5. & 6.
2194                if self.VideoTracks(can_gc).selected_index().is_none() {
2195                    self.VideoTracks(can_gc)
2196                        .set_selected(self.VideoTracks(can_gc).len() - 1, true);
2197                }
2198
2199                // Steps 7.
2200                let event = TrackEvent::new(
2201                    self.global().as_window(),
2202                    atom!("addtrack"),
2203                    false,
2204                    false,
2205                    &Some(VideoTrackOrAudioTrackOrTextTrack::VideoTrack(video_track)),
2206                    can_gc,
2207                );
2208
2209                event
2210                    .upcast::<Event>()
2211                    .fire(self.upcast::<EventTarget>(), can_gc);
2212            }
2213        }
2214
2215        // => "Once enough of the media data has been fetched to determine the duration..."
2216        // Step 1.
2217        // servo-media owns the media timeline.
2218
2219        // Step 2.
2220        // XXX(ferjm) Update the timeline offset.
2221
2222        // Step 3.
2223        self.playback_position.set(0.);
2224
2225        // Step 4.
2226        let previous_duration = self.duration.get();
2227        if let Some(duration) = metadata.duration {
2228            self.duration.set(duration.as_secs() as f64);
2229        } else {
2230            self.duration.set(f64::INFINITY);
2231        }
2232        if previous_duration != self.duration.get() {
2233            self.owner_global()
2234                .task_manager()
2235                .media_element_task_source()
2236                .queue_simple_event(self.upcast(), atom!("durationchange"));
2237        }
2238
2239        // Step 5.
2240        self.handle_resize(Some(metadata.width), Some(metadata.height));
2241
2242        // Step 6.
2243        self.change_ready_state(ReadyState::HaveMetadata);
2244
2245        // Step 7.
2246        let mut jumped = false;
2247
2248        // Step 8.
2249        if self.default_playback_start_position.get() > 0. {
2250            self.seek(
2251                self.default_playback_start_position.get(),
2252                /* approximate_for_speed*/ false,
2253                can_gc,
2254            );
2255            jumped = true;
2256        }
2257
2258        // Step 9.
2259        self.default_playback_start_position.set(0.);
2260
2261        // Steps 10 and 11.
2262        if let Some(servo_url) = self.resource_url.borrow().as_ref() {
2263            let fragment = MediaFragmentParser::from(servo_url);
2264            if let Some(start) = fragment.start() {
2265                if start > 0. && start < self.duration.get() {
2266                    self.playback_position.set(start);
2267                    if !jumped {
2268                        self.seek(self.playback_position.get(), false, can_gc)
2269                    }
2270                }
2271            }
2272        }
2273
2274        // Step 12 & 13 are already handled by the earlier media track processing.
2275
2276        let global = self.global();
2277        let window = global.as_window();
2278
2279        // Update the media session metadata title with the obtained metadata.
2280        window.Navigator().MediaSession().update_title(
2281            metadata
2282                .title
2283                .clone()
2284                .unwrap_or(window.get_url().into_string()),
2285        );
2286    }
2287
2288    fn playback_video_frame_updated(&self) {
2289        // Check if the frame was resized
2290        if let Some(frame) = self.video_renderer.lock().unwrap().current_frame {
2291            self.handle_resize(Some(frame.width as u32), Some(frame.height as u32));
2292        }
2293    }
2294
2295    fn playback_need_data(&self, can_gc: CanGc) {
2296        // The player needs more data.
2297        // If we already have a valid fetch request, we do nothing.
2298        // Otherwise, if we have no request and the previous request was
2299        // cancelled because we got an EnoughData event, we restart
2300        // fetching where we left.
2301        if let Some(ref current_fetch_context) = *self.current_fetch_context.borrow() {
2302            if let Some(reason) = current_fetch_context.cancel_reason() {
2303                // XXX(ferjm) Ideally we should just create a fetch request from
2304                // where we left. But keeping track of the exact next byte that the
2305                // media backend expects is not the easiest task, so I'm simply
2306                // seeking to the current playback position for now which will create
2307                // a new fetch request for the last rendered frame.
2308                if *reason == CancelReason::Backoff {
2309                    self.seek(self.playback_position.get(), false, can_gc);
2310                }
2311                return;
2312            }
2313        }
2314
2315        if let Some(ref mut current_fetch_context) = *self.current_fetch_context.borrow_mut() {
2316            if let Err(e) = {
2317                let mut data_source = current_fetch_context.data_source().borrow_mut();
2318                data_source.set_locked(false);
2319                data_source.process_into_player_from_queue(self.player.borrow().as_ref().unwrap())
2320            } {
2321                // If we are pushing too much data and we know that we can
2322                // restart the download later from where we left, we cancel
2323                // the current request. Otherwise, we continue the request
2324                // assuming that we may drop some frames.
2325                if e == PlayerError::EnoughData {
2326                    current_fetch_context.cancel(CancelReason::Backoff);
2327                }
2328            }
2329        }
2330    }
2331
2332    fn playback_enough_data(&self) {
2333        self.change_ready_state(ReadyState::HaveEnoughData);
2334
2335        // The player has enough data and it is asking us to stop pushing
2336        // bytes, so we cancel the ongoing fetch request iff we are able
2337        // to restart it from where we left. Otherwise, we continue the
2338        // current fetch request, assuming that some frames will be dropped.
2339        if let Some(ref mut current_fetch_context) = *self.current_fetch_context.borrow_mut() {
2340            if current_fetch_context.is_seekable() {
2341                current_fetch_context.cancel(CancelReason::Backoff);
2342            }
2343        }
2344    }
2345
2346    fn playback_position_changed(&self, position: u64) {
2347        let position = position as f64;
2348        let _ = self
2349            .played
2350            .borrow_mut()
2351            .add(self.playback_position.get(), position);
2352        self.playback_position.set(position);
2353        self.time_marches_on();
2354        let media_position_state =
2355            MediaPositionState::new(self.duration.get(), self.playbackRate.get(), position);
2356        debug!(
2357            "Sending media session event set position state {:?}",
2358            media_position_state
2359        );
2360        self.send_media_session_event(MediaSessionEvent::SetPositionState(media_position_state));
2361    }
2362
2363    fn playback_seek_done(&self) {
2364        // Continuation of
2365        // https://html.spec.whatwg.org/multipage/#dom-media-seek
2366
2367        // Step 13.
2368        let task = MediaElementMicrotask::Seeked {
2369            elem: DomRoot::from_ref(self),
2370            generation_id: self.generation_id.get(),
2371        };
2372        ScriptThread::await_stable_state(Microtask::MediaElement(task));
2373    }
2374
2375    fn playback_state_changed(&self, state: &PlaybackState) {
2376        let mut media_session_playback_state = MediaSessionPlaybackState::None_;
2377        match *state {
2378            PlaybackState::Paused => {
2379                media_session_playback_state = MediaSessionPlaybackState::Paused;
2380                if self.ready_state.get() == ReadyState::HaveMetadata {
2381                    self.change_ready_state(ReadyState::HaveEnoughData);
2382                }
2383            },
2384            PlaybackState::Playing => {
2385                media_session_playback_state = MediaSessionPlaybackState::Playing;
2386            },
2387            PlaybackState::Buffering => {
2388                // Do not send the media session playback state change event
2389                // in this case as a None_ state is expected to clean up the
2390                // session.
2391                return;
2392            },
2393            _ => {},
2394        };
2395        debug!(
2396            "Sending media session event playback state changed to {:?}",
2397            media_session_playback_state
2398        );
2399        self.send_media_session_event(MediaSessionEvent::PlaybackStateChange(
2400            media_session_playback_state,
2401        ));
2402    }
2403
2404    fn handle_player_event(&self, player_id: usize, event: &PlayerEvent, can_gc: CanGc) {
2405        // Ignore the asynchronous event from previous player.
2406        if self
2407            .player
2408            .borrow()
2409            .as_ref()
2410            .is_none_or(|player| player.lock().unwrap().get_id() != player_id)
2411        {
2412            return;
2413        }
2414
2415        match *event {
2416            PlayerEvent::EndOfStream => self.playback_end(can_gc),
2417            PlayerEvent::Error(ref error) => self.playback_error(error, can_gc),
2418            PlayerEvent::VideoFrameUpdated => self.playback_video_frame_updated(),
2419            PlayerEvent::MetadataUpdated(ref metadata) => {
2420                self.playback_metadata_updated(metadata, can_gc)
2421            },
2422            PlayerEvent::NeedData => self.playback_need_data(can_gc),
2423            PlayerEvent::EnoughData => self.playback_enough_data(),
2424            PlayerEvent::PositionChanged(position) => self.playback_position_changed(position),
2425            PlayerEvent::SeekData(p, ref seek_lock) => {
2426                self.fetch_request(Some(p), Some(seek_lock.clone()))
2427            },
2428            PlayerEvent::SeekDone(_) => self.playback_seek_done(),
2429            PlayerEvent::StateChanged(ref state) => self.playback_state_changed(state),
2430        }
2431    }
2432
2433    /// <https://html.spec.whatwg.org/multipage/#earliest-possible-position>
2434    fn earliest_possible_position(&self) -> f64 {
2435        self.played
2436            .borrow()
2437            .start(0)
2438            .unwrap_or_else(|_| self.playback_position.get())
2439    }
2440
2441    fn render_controls(&self, can_gc: CanGc) {
2442        if self.upcast::<Element>().is_shadow_host() {
2443            // Bail out if we are already showing the controls.
2444            return;
2445        }
2446
2447        // FIXME(stevennovaryo): Recheck styling of media element to avoid
2448        //                       reparsing styles.
2449        let shadow_root = self
2450            .upcast::<Element>()
2451            .attach_ua_shadow_root(false, can_gc);
2452        let document = self.owner_document();
2453        let script = Element::create(
2454            QualName::new(None, ns!(html), local_name!("script")),
2455            None,
2456            &document,
2457            ElementCreator::ScriptCreated,
2458            CustomElementCreationMode::Asynchronous,
2459            None,
2460            can_gc,
2461        );
2462        // This is our hacky way to temporarily workaround the lack of a privileged
2463        // JS context.
2464        // The media controls UI accesses the document.servoGetMediaControls(id) API
2465        // to get an instance to the media controls ShadowRoot.
2466        // `id` needs to match the internally generated UUID assigned to a media element.
2467        let id = Uuid::new_v4().to_string();
2468        document.register_media_controls(&id, &shadow_root);
2469        let media_controls_script = MEDIA_CONTROL_JS.replace("@@@id@@@", &id);
2470        *self.media_controls_id.borrow_mut() = Some(id);
2471        script
2472            .upcast::<Node>()
2473            .set_text_content_for_element(Some(DOMString::from(media_controls_script)), can_gc);
2474        if let Err(e) = shadow_root
2475            .upcast::<Node>()
2476            .AppendChild(script.upcast::<Node>(), can_gc)
2477        {
2478            warn!("Could not render media controls {:?}", e);
2479            return;
2480        }
2481
2482        let style = Element::create(
2483            QualName::new(None, ns!(html), local_name!("style")),
2484            None,
2485            &document,
2486            ElementCreator::ScriptCreated,
2487            CustomElementCreationMode::Asynchronous,
2488            None,
2489            can_gc,
2490        );
2491
2492        style
2493            .upcast::<Node>()
2494            .set_text_content_for_element(Some(DOMString::from(MEDIA_CONTROL_CSS)), can_gc);
2495
2496        if let Err(e) = shadow_root
2497            .upcast::<Node>()
2498            .AppendChild(style.upcast::<Node>(), can_gc)
2499        {
2500            warn!("Could not render media controls {:?}", e);
2501        }
2502
2503        self.upcast::<Node>().dirty(NodeDamage::Other);
2504    }
2505
2506    fn remove_controls(&self) {
2507        if let Some(id) = self.media_controls_id.borrow_mut().take() {
2508            self.owner_document().unregister_media_controls(&id);
2509        }
2510    }
2511
2512    /// Gets the video frame at the current playback position.
2513    pub(crate) fn get_current_frame(&self) -> Option<VideoFrame> {
2514        self.video_renderer
2515            .lock()
2516            .unwrap()
2517            .current_frame_holder
2518            .as_ref()
2519            .map(|holder| holder.get_frame())
2520    }
2521
2522    /// Gets the current frame of the video element to present, if any.
2523    /// <https://html.spec.whatwg.org/multipage/#the-video-element:the-video-element-7>
2524    pub(crate) fn get_current_frame_to_present(&self) -> Option<MediaFrame> {
2525        let (current_frame, poster_frame) = {
2526            let renderer = self.video_renderer.lock().unwrap();
2527            (renderer.current_frame, renderer.poster_frame)
2528        };
2529
2530        // If the show poster flag is set (or there is no current video frame to
2531        // present) AND there is a poster frame, present that.
2532        if (self.show_poster.get() || current_frame.is_none()) && poster_frame.is_some() {
2533            return poster_frame;
2534        }
2535
2536        current_frame
2537    }
2538
2539    fn handle_resize(&self, width: Option<u32>, height: Option<u32>) {
2540        if let Some(video_elem) = self.downcast::<HTMLVideoElement>() {
2541            video_elem.resize(width, height);
2542            self.upcast::<Node>().dirty(NodeDamage::Other);
2543        }
2544    }
2545
2546    /// By default the audio is rendered through the audio sink automatically
2547    /// selected by the servo-media Player instance. However, in some cases, like
2548    /// the WebAudio MediaElementAudioSourceNode, we need to set a custom audio
2549    /// renderer.
2550    pub(crate) fn set_audio_renderer(
2551        &self,
2552        audio_renderer: Arc<Mutex<dyn AudioRenderer>>,
2553        can_gc: CanGc,
2554    ) {
2555        *self.audio_renderer.borrow_mut() = Some(audio_renderer);
2556        if let Some(ref player) = *self.player.borrow() {
2557            if let Err(e) = player.lock().unwrap().stop() {
2558                error!("Could not stop player {:?}", e);
2559            }
2560            self.media_element_load_algorithm(can_gc);
2561        }
2562    }
2563
2564    fn send_media_session_event(&self, event: MediaSessionEvent) {
2565        let global = self.global();
2566        let media_session = global.as_window().Navigator().MediaSession();
2567
2568        media_session.register_media_instance(self);
2569
2570        media_session.send_event(event);
2571    }
2572
2573    pub(crate) fn set_duration(&self, duration: f64) {
2574        self.duration.set(duration);
2575    }
2576
2577    pub(crate) fn reset(&self) {
2578        if let Some(ref player) = *self.player.borrow() {
2579            if let Err(e) = player.lock().unwrap().stop() {
2580                error!("Could not stop player {:?}", e);
2581            }
2582        }
2583    }
2584
2585    /// <https://html.spec.whatwg.org/multipage/#concept-media-load-resource>
2586    pub(crate) fn origin_is_clean(&self) -> bool {
2587        // Step 5.local (media provider object).
2588        if self.src_object.borrow().is_some() {
2589            // The resource described by the current media resource, if any,
2590            // contains the media data. It is CORS-same-origin.
2591            return true;
2592        }
2593
2594        // Step 5.remote (URL record).
2595        if self.resource_url.borrow().is_some() {
2596            // Update the media data with the contents
2597            // of response's unsafe response obtained in this fashion.
2598            // Response can be CORS-same-origin or CORS-cross-origin;
2599            if let Some(ref current_fetch_context) = *self.current_fetch_context.borrow() {
2600                return current_fetch_context.origin_is_clean();
2601            }
2602        }
2603
2604        true
2605    }
2606}
2607
2608// XXX Placeholder for [https://github.com/servo/servo/issues/22293]
2609#[derive(Clone, Copy, JSTraceable, MallocSizeOf, PartialEq)]
2610enum PlaybackDirection {
2611    Forwards,
2612    #[expect(dead_code)]
2613    Backwards,
2614}
2615
2616// XXX Placeholder implementations for:
2617//
2618// - https://github.com/servo/servo/issues/22293
2619impl HTMLMediaElement {
2620    /// <https://github.com/servo/servo/issues/22293>
2621    fn direction_of_playback(&self) -> PlaybackDirection {
2622        PlaybackDirection::Forwards
2623    }
2624}
2625
2626impl HTMLMediaElementMethods<crate::DomTypeHolder> for HTMLMediaElement {
2627    /// <https://html.spec.whatwg.org/multipage/#dom-media-networkstate>
2628    fn NetworkState(&self) -> u16 {
2629        self.network_state.get() as u16
2630    }
2631
2632    /// <https://html.spec.whatwg.org/multipage/#dom-media-readystate>
2633    fn ReadyState(&self) -> u16 {
2634        self.ready_state.get() as u16
2635    }
2636
2637    // https://html.spec.whatwg.org/multipage/#dom-media-autoplay
2638    make_bool_getter!(Autoplay, "autoplay");
2639    // https://html.spec.whatwg.org/multipage/#dom-media-autoplay
2640    make_bool_setter!(SetAutoplay, "autoplay");
2641
2642    // https://html.spec.whatwg.org/multipage/#attr-media-loop
2643    make_bool_getter!(Loop, "loop");
2644    // https://html.spec.whatwg.org/multipage/#attr-media-loop
2645    make_bool_setter!(SetLoop, "loop");
2646
2647    // https://html.spec.whatwg.org/multipage/#dom-media-defaultmuted
2648    make_bool_getter!(DefaultMuted, "muted");
2649    // https://html.spec.whatwg.org/multipage/#dom-media-defaultmuted
2650    make_bool_setter!(SetDefaultMuted, "muted");
2651
2652    // https://html.spec.whatwg.org/multipage/#dom-media-controls
2653    make_bool_getter!(Controls, "controls");
2654    // https://html.spec.whatwg.org/multipage/#dom-media-controls
2655    make_bool_setter!(SetControls, "controls");
2656
2657    // https://html.spec.whatwg.org/multipage/#dom-media-src
2658    make_url_getter!(Src, "src");
2659
2660    // https://html.spec.whatwg.org/multipage/#dom-media-src
2661    make_url_setter!(SetSrc, "src");
2662
2663    /// <https://html.spec.whatwg.org/multipage/#dom-media-crossOrigin>
2664    fn GetCrossOrigin(&self) -> Option<DOMString> {
2665        reflect_cross_origin_attribute(self.upcast::<Element>())
2666    }
2667    /// <https://html.spec.whatwg.org/multipage/#dom-media-crossOrigin>
2668    fn SetCrossOrigin(&self, value: Option<DOMString>, can_gc: CanGc) {
2669        set_cross_origin_attribute(self.upcast::<Element>(), value, can_gc);
2670    }
2671
2672    /// <https://html.spec.whatwg.org/multipage/#dom-media-muted>
2673    fn Muted(&self) -> bool {
2674        self.muted.get()
2675    }
2676
2677    /// <https://html.spec.whatwg.org/multipage/#dom-media-muted>
2678    fn SetMuted(&self, value: bool) {
2679        if self.muted.get() == value {
2680            return;
2681        }
2682
2683        if let Some(ref player) = *self.player.borrow() {
2684            let _ = player.lock().unwrap().set_mute(value);
2685        }
2686
2687        self.muted.set(value);
2688        self.owner_global()
2689            .task_manager()
2690            .media_element_task_source()
2691            .queue_simple_event(self.upcast(), atom!("volumechange"));
2692        if !self.is_allowed_to_play() {
2693            self.internal_pause_steps();
2694        }
2695    }
2696
2697    /// <https://html.spec.whatwg.org/multipage/#dom-media-srcobject>
2698    fn GetSrcObject(&self) -> Option<MediaStreamOrBlob> {
2699        (*self.src_object.borrow())
2700            .as_ref()
2701            .map(|src_object| match src_object {
2702                SrcObject::Blob(blob) => MediaStreamOrBlob::Blob(DomRoot::from_ref(blob)),
2703                SrcObject::MediaStream(stream) => {
2704                    MediaStreamOrBlob::MediaStream(DomRoot::from_ref(stream))
2705                },
2706            })
2707    }
2708
2709    /// <https://html.spec.whatwg.org/multipage/#dom-media-srcobject>
2710    fn SetSrcObject(&self, value: Option<MediaStreamOrBlob>, can_gc: CanGc) {
2711        *self.src_object.borrow_mut() = value.map(|value| value.into());
2712        self.media_element_load_algorithm(can_gc);
2713    }
2714
2715    // https://html.spec.whatwg.org/multipage/#attr-media-preload
2716    // Missing/Invalid values are user-agent defined.
2717    make_enumerated_getter!(
2718        Preload,
2719        "preload",
2720        "none" | "metadata" | "auto",
2721        missing => "auto",
2722        invalid => "auto"
2723    );
2724
2725    // https://html.spec.whatwg.org/multipage/#attr-media-preload
2726    make_setter!(SetPreload, "preload");
2727
2728    /// <https://html.spec.whatwg.org/multipage/#dom-media-currentsrc>
2729    fn CurrentSrc(&self) -> USVString {
2730        USVString(self.current_src.borrow().clone())
2731    }
2732
2733    /// <https://html.spec.whatwg.org/multipage/#dom-media-load>
2734    fn Load(&self, can_gc: CanGc) {
2735        self.media_element_load_algorithm(can_gc);
2736    }
2737
2738    /// <https://html.spec.whatwg.org/multipage/#dom-navigator-canplaytype>
2739    fn CanPlayType(&self, type_: DOMString) -> CanPlayTypeResult {
2740        match ServoMedia::get().can_play_type(&type_.str()) {
2741            SupportsMediaType::No => CanPlayTypeResult::_empty,
2742            SupportsMediaType::Maybe => CanPlayTypeResult::Maybe,
2743            SupportsMediaType::Probably => CanPlayTypeResult::Probably,
2744        }
2745    }
2746
2747    /// <https://html.spec.whatwg.org/multipage/#dom-media-error>
2748    fn GetError(&self) -> Option<DomRoot<MediaError>> {
2749        self.error.get()
2750    }
2751
2752    /// <https://html.spec.whatwg.org/multipage/#dom-media-play>
2753    fn Play(&self, comp: InRealm, can_gc: CanGc) -> Rc<Promise> {
2754        let promise = Promise::new_in_current_realm(comp, can_gc);
2755        // Step 1.
2756        // FIXME(nox): Reject promise if not allowed to play.
2757
2758        // Step 2.
2759        if self
2760            .error
2761            .get()
2762            .is_some_and(|e| e.Code() == MEDIA_ERR_SRC_NOT_SUPPORTED)
2763        {
2764            promise.reject_error(Error::NotSupported, can_gc);
2765            return promise;
2766        }
2767
2768        // Step 3.
2769        self.push_pending_play_promise(&promise);
2770
2771        // Step 4.
2772        if self.network_state.get() == NetworkState::Empty {
2773            self.invoke_resource_selection_algorithm(can_gc);
2774        }
2775
2776        // Step 5.
2777        if self.Ended() && self.direction_of_playback() == PlaybackDirection::Forwards {
2778            self.seek(
2779                self.earliest_possible_position(),
2780                /* approximate_for_speed */ false,
2781                can_gc,
2782            );
2783        }
2784
2785        let state = self.ready_state.get();
2786
2787        let global = self.owner_global();
2788        let task_manager = global.task_manager();
2789        let task_source = task_manager.media_element_task_source();
2790        if self.Paused() {
2791            // Step 6.1.
2792            self.paused.set(false);
2793
2794            // Step 6.2.
2795            if self.show_poster.get() {
2796                self.show_poster.set(false);
2797                self.time_marches_on();
2798            }
2799
2800            // Step 6.3.
2801            task_source.queue_simple_event(self.upcast(), atom!("play"));
2802
2803            // Step 6.4.
2804            match state {
2805                ReadyState::HaveNothing |
2806                ReadyState::HaveMetadata |
2807                ReadyState::HaveCurrentData => {
2808                    task_source.queue_simple_event(self.upcast(), atom!("waiting"));
2809                },
2810                ReadyState::HaveFutureData | ReadyState::HaveEnoughData => {
2811                    self.notify_about_playing();
2812                },
2813            }
2814        } else if state == ReadyState::HaveFutureData || state == ReadyState::HaveEnoughData {
2815            // Step 7.
2816            self.take_pending_play_promises(Ok(()));
2817            let this = Trusted::new(self);
2818            let generation_id = self.generation_id.get();
2819            task_source.queue(task!(resolve_pending_play_promises: move || {
2820                let this = this.root();
2821                if generation_id != this.generation_id.get() {
2822                    return;
2823                }
2824
2825                this.fulfill_in_flight_play_promises(|| {
2826                    this.play_media();
2827                });
2828            }));
2829        }
2830
2831        // Step 8.
2832        self.autoplaying.set(false);
2833
2834        // Step 9.
2835        promise
2836    }
2837
2838    /// <https://html.spec.whatwg.org/multipage/#dom-media-pause>
2839    fn Pause(&self, can_gc: CanGc) {
2840        // Step 1
2841        if self.network_state.get() == NetworkState::Empty {
2842            self.invoke_resource_selection_algorithm(can_gc);
2843        }
2844
2845        // Step 2
2846        self.internal_pause_steps();
2847    }
2848
2849    /// <https://html.spec.whatwg.org/multipage/#dom-media-paused>
2850    fn Paused(&self) -> bool {
2851        self.paused.get()
2852    }
2853
2854    /// <https://html.spec.whatwg.org/multipage/#dom-media-defaultplaybackrate>
2855    fn GetDefaultPlaybackRate(&self) -> Fallible<Finite<f64>> {
2856        Ok(Finite::wrap(self.defaultPlaybackRate.get()))
2857    }
2858
2859    /// <https://html.spec.whatwg.org/multipage/#dom-media-defaultplaybackrate>
2860    fn SetDefaultPlaybackRate(&self, value: Finite<f64>) -> ErrorResult {
2861        let min_allowed = -64.0;
2862        let max_allowed = 64.0;
2863        if *value < min_allowed || *value > max_allowed {
2864            return Err(Error::NotSupported);
2865        }
2866
2867        if *value != self.defaultPlaybackRate.get() {
2868            self.defaultPlaybackRate.set(*value);
2869            self.queue_media_element_task_to_fire_event(atom!("ratechange"));
2870        }
2871
2872        Ok(())
2873    }
2874
2875    /// <https://html.spec.whatwg.org/multipage/#dom-media-playbackrate>
2876    fn GetPlaybackRate(&self) -> Fallible<Finite<f64>> {
2877        Ok(Finite::wrap(self.playbackRate.get()))
2878    }
2879
2880    /// <https://html.spec.whatwg.org/multipage/#dom-media-playbackrate>
2881    fn SetPlaybackRate(&self, value: Finite<f64>) -> ErrorResult {
2882        let min_allowed = -64.0;
2883        let max_allowed = 64.0;
2884        if *value < min_allowed || *value > max_allowed {
2885            return Err(Error::NotSupported);
2886        }
2887
2888        if *value != self.playbackRate.get() {
2889            self.playbackRate.set(*value);
2890            self.queue_media_element_task_to_fire_event(atom!("ratechange"));
2891            if self.is_potentially_playing() {
2892                if let Some(ref player) = *self.player.borrow() {
2893                    if let Err(e) = player.lock().unwrap().set_rate(*value) {
2894                        warn!("Could not set the playback rate {:?}", e);
2895                    }
2896                }
2897            }
2898        }
2899
2900        Ok(())
2901    }
2902
2903    /// <https://html.spec.whatwg.org/multipage/#dom-media-duration>
2904    fn Duration(&self) -> f64 {
2905        self.duration.get()
2906    }
2907
2908    /// <https://html.spec.whatwg.org/multipage/#dom-media-currenttime>
2909    fn CurrentTime(&self) -> Finite<f64> {
2910        Finite::wrap(if self.default_playback_start_position.get() != 0. {
2911            self.default_playback_start_position.get()
2912        } else {
2913            self.playback_position.get()
2914        })
2915    }
2916
2917    /// <https://html.spec.whatwg.org/multipage/#dom-media-currenttime>
2918    fn SetCurrentTime(&self, time: Finite<f64>, can_gc: CanGc) {
2919        if self.ready_state.get() == ReadyState::HaveNothing {
2920            self.default_playback_start_position.set(*time);
2921        } else {
2922            self.playback_position.set(*time);
2923            self.seek(*time, /* approximate_for_speed */ false, can_gc);
2924        }
2925    }
2926
2927    /// <https://html.spec.whatwg.org/multipage/#dom-media-seeking>
2928    fn Seeking(&self) -> bool {
2929        self.seeking.get()
2930    }
2931
2932    /// <https://html.spec.whatwg.org/multipage/#ended-playback>
2933    fn Ended(&self) -> bool {
2934        if self.ready_state.get() < ReadyState::HaveMetadata {
2935            return false;
2936        }
2937
2938        let playback_pos = self.playback_position.get();
2939
2940        match self.direction_of_playback() {
2941            PlaybackDirection::Forwards => playback_pos >= self.Duration() && !self.Loop(),
2942            PlaybackDirection::Backwards => playback_pos <= self.earliest_possible_position(),
2943        }
2944    }
2945
2946    /// <https://html.spec.whatwg.org/multipage/#dom-media-fastseek>
2947    fn FastSeek(&self, time: Finite<f64>, can_gc: CanGc) {
2948        self.seek(*time, /* approximate_for_speed */ true, can_gc);
2949    }
2950
2951    /// <https://html.spec.whatwg.org/multipage/#dom-media-played>
2952    fn Played(&self, can_gc: CanGc) -> DomRoot<TimeRanges> {
2953        TimeRanges::new(
2954            self.global().as_window(),
2955            self.played.borrow().clone(),
2956            can_gc,
2957        )
2958    }
2959
2960    /// <https://html.spec.whatwg.org/multipage/#dom-media-seekable>
2961    fn Seekable(&self, can_gc: CanGc) -> DomRoot<TimeRanges> {
2962        let mut seekable = TimeRangesContainer::default();
2963        if let Some(ref player) = *self.player.borrow() {
2964            if let Ok(ranges) = player.lock().unwrap().seekable() {
2965                for range in ranges {
2966                    let _ = seekable.add(range.start, range.end);
2967                }
2968            }
2969        }
2970        TimeRanges::new(self.global().as_window(), seekable, can_gc)
2971    }
2972
2973    /// <https://html.spec.whatwg.org/multipage/#dom-media-buffered>
2974    fn Buffered(&self, can_gc: CanGc) -> DomRoot<TimeRanges> {
2975        let mut buffered = TimeRangesContainer::default();
2976        if let Some(ref player) = *self.player.borrow() {
2977            if let Ok(ranges) = player.lock().unwrap().buffered() {
2978                for range in ranges {
2979                    let _ = buffered.add(range.start, range.end);
2980                }
2981            }
2982        }
2983        TimeRanges::new(self.global().as_window(), buffered, can_gc)
2984    }
2985
2986    /// <https://html.spec.whatwg.org/multipage/#dom-media-audiotracks>
2987    fn AudioTracks(&self, can_gc: CanGc) -> DomRoot<AudioTrackList> {
2988        let window = self.owner_window();
2989        self.audio_tracks_list
2990            .or_init(|| AudioTrackList::new(&window, &[], Some(self), can_gc))
2991    }
2992
2993    /// <https://html.spec.whatwg.org/multipage/#dom-media-videotracks>
2994    fn VideoTracks(&self, can_gc: CanGc) -> DomRoot<VideoTrackList> {
2995        let window = self.owner_window();
2996        self.video_tracks_list
2997            .or_init(|| VideoTrackList::new(&window, &[], Some(self), can_gc))
2998    }
2999
3000    /// <https://html.spec.whatwg.org/multipage/#dom-media-texttracks>
3001    fn TextTracks(&self, can_gc: CanGc) -> DomRoot<TextTrackList> {
3002        let window = self.owner_window();
3003        self.text_tracks_list
3004            .or_init(|| TextTrackList::new(&window, &[], can_gc))
3005    }
3006
3007    /// <https://html.spec.whatwg.org/multipage/#dom-media-addtexttrack>
3008    fn AddTextTrack(
3009        &self,
3010        kind: TextTrackKind,
3011        label: DOMString,
3012        language: DOMString,
3013        can_gc: CanGc,
3014    ) -> DomRoot<TextTrack> {
3015        let window = self.owner_window();
3016        // Step 1 & 2
3017        // FIXME(#22314, dlrobertson) set the ready state to Loaded
3018        let track = TextTrack::new(
3019            &window,
3020            "".into(),
3021            kind,
3022            label,
3023            language,
3024            TextTrackMode::Hidden,
3025            None,
3026            can_gc,
3027        );
3028        // Step 3 & 4
3029        self.TextTracks(can_gc).add(&track);
3030        // Step 5
3031        DomRoot::from_ref(&track)
3032    }
3033
3034    /// <https://html.spec.whatwg.org/multipage/#dom-media-volume>
3035    fn GetVolume(&self) -> Fallible<Finite<f64>> {
3036        Ok(Finite::wrap(self.volume.get()))
3037    }
3038
3039    /// <https://html.spec.whatwg.org/multipage/#dom-media-volume>
3040    fn SetVolume(&self, value: Finite<f64>) -> ErrorResult {
3041        let minimum_volume = 0.0;
3042        let maximum_volume = 1.0;
3043        if *value < minimum_volume || *value > maximum_volume {
3044            return Err(Error::IndexSize(None));
3045        }
3046
3047        if *value != self.volume.get() {
3048            self.volume.set(*value);
3049            if let Some(player) = self.player.borrow().as_ref() {
3050                let _ = player.lock().unwrap().set_volume(*value);
3051            }
3052            self.owner_global()
3053                .task_manager()
3054                .media_element_task_source()
3055                .queue_simple_event(self.upcast(), atom!("volumechange"));
3056            if !self.is_allowed_to_play() {
3057                self.internal_pause_steps();
3058            }
3059        }
3060
3061        Ok(())
3062    }
3063}
3064
3065impl VirtualMethods for HTMLMediaElement {
3066    fn super_type(&self) -> Option<&dyn VirtualMethods> {
3067        Some(self.upcast::<HTMLElement>() as &dyn VirtualMethods)
3068    }
3069
3070    fn attribute_mutated(&self, attr: &Attr, mutation: AttributeMutation, can_gc: CanGc) {
3071        self.super_type()
3072            .unwrap()
3073            .attribute_mutated(attr, mutation, can_gc);
3074
3075        match *attr.local_name() {
3076            local_name!("muted") => {
3077                self.SetMuted(mutation.new_value(attr).is_some());
3078            },
3079            local_name!("src") => {
3080                // <https://html.spec.whatwg.org/multipage/#location-of-the-media-resource>
3081                // If a src attribute of a media element is set or changed, the user agent must invoke
3082                // the media element's media element load algorithm (Removing the src attribute does
3083                // not do this, even if there are source elements present).
3084                if !mutation.is_removal() {
3085                    self.media_element_load_algorithm(can_gc);
3086                }
3087            },
3088            local_name!("controls") => {
3089                if mutation.new_value(attr).is_some() {
3090                    self.render_controls(can_gc);
3091                } else {
3092                    self.remove_controls();
3093                }
3094            },
3095            _ => (),
3096        };
3097    }
3098
3099    /// <https://html.spec.whatwg.org/multipage/#playing-the-media-resource:remove-an-element-from-a-document>
3100    fn unbind_from_tree(&self, context: &UnbindContext, can_gc: CanGc) {
3101        self.super_type().unwrap().unbind_from_tree(context, can_gc);
3102
3103        self.remove_controls();
3104
3105        if context.tree_connected {
3106            let task = MediaElementMicrotask::PauseIfNotInDocument {
3107                elem: DomRoot::from_ref(self),
3108            };
3109            ScriptThread::await_stable_state(Microtask::MediaElement(task));
3110        }
3111    }
3112
3113    fn adopting_steps(&self, old_doc: &Document, can_gc: CanGc) {
3114        self.super_type().unwrap().adopting_steps(old_doc, can_gc);
3115
3116        // Note that media control id should be adopting between documents so "privileged"
3117        // document.servoGetMediaControls(id) API is keeping access to the whitelist of media
3118        // controls identifiers.
3119        if let Some(id) = &*self.media_controls_id.borrow() {
3120            let Some(shadow_root) = self.upcast::<Element>().shadow_root() else {
3121                error!("Missing media controls shadow root");
3122                return;
3123            };
3124
3125            old_doc.unregister_media_controls(id);
3126            self.owner_document()
3127                .register_media_controls(id, &shadow_root);
3128        }
3129    }
3130}
3131
3132#[derive(JSTraceable, MallocSizeOf)]
3133pub(crate) enum MediaElementMicrotask {
3134    ResourceSelection {
3135        elem: DomRoot<HTMLMediaElement>,
3136        generation_id: u32,
3137        #[no_trace]
3138        base_url: ServoUrl,
3139    },
3140    PauseIfNotInDocument {
3141        elem: DomRoot<HTMLMediaElement>,
3142    },
3143    Seeked {
3144        elem: DomRoot<HTMLMediaElement>,
3145        generation_id: u32,
3146    },
3147    SelectNextSourceChild {
3148        elem: DomRoot<HTMLMediaElement>,
3149        generation_id: u32,
3150    },
3151    SelectNextSourceChildAfterWait {
3152        elem: DomRoot<HTMLMediaElement>,
3153        generation_id: u32,
3154    },
3155}
3156
3157impl MicrotaskRunnable for MediaElementMicrotask {
3158    fn handler(&self, can_gc: CanGc) {
3159        match self {
3160            &MediaElementMicrotask::ResourceSelection {
3161                ref elem,
3162                generation_id,
3163                ref base_url,
3164            } => {
3165                if generation_id == elem.generation_id.get() {
3166                    elem.resource_selection_algorithm_sync(base_url.clone(), can_gc);
3167                }
3168            },
3169            MediaElementMicrotask::PauseIfNotInDocument { elem } => {
3170                if !elem.upcast::<Node>().is_connected() {
3171                    elem.internal_pause_steps();
3172                }
3173            },
3174            &MediaElementMicrotask::Seeked {
3175                ref elem,
3176                generation_id,
3177            } => {
3178                if generation_id == elem.generation_id.get() {
3179                    elem.seek_end();
3180                }
3181            },
3182            &MediaElementMicrotask::SelectNextSourceChild {
3183                ref elem,
3184                generation_id,
3185            } => {
3186                if generation_id == elem.generation_id.get() {
3187                    elem.select_next_source_child(can_gc);
3188                }
3189            },
3190            &MediaElementMicrotask::SelectNextSourceChildAfterWait {
3191                ref elem,
3192                generation_id,
3193            } => {
3194                if generation_id == elem.generation_id.get() {
3195                    elem.select_next_source_child_after_wait(can_gc);
3196                }
3197            },
3198        }
3199    }
3200
3201    fn enter_realm(&self) -> JSAutoRealm {
3202        match self {
3203            &MediaElementMicrotask::ResourceSelection { ref elem, .. } |
3204            &MediaElementMicrotask::PauseIfNotInDocument { ref elem } |
3205            &MediaElementMicrotask::Seeked { ref elem, .. } |
3206            &MediaElementMicrotask::SelectNextSourceChild { ref elem, .. } |
3207            &MediaElementMicrotask::SelectNextSourceChildAfterWait { ref elem, .. } => {
3208                enter_realm(&**elem)
3209            },
3210        }
3211    }
3212}
3213
3214enum Resource {
3215    Object,
3216    Url(ServoUrl),
3217}
3218
3219#[derive(Debug, MallocSizeOf, PartialEq)]
3220enum DataBuffer {
3221    Payload(Vec<u8>),
3222    EndOfStream,
3223}
3224
3225#[derive(MallocSizeOf)]
3226struct BufferedDataSource {
3227    /// During initial setup and seeking (including clearing the buffer queue
3228    /// and resetting the end-of-stream state), the data source should be locked and
3229    /// any request for processing should be ignored until the media player informs us
3230    /// via the NeedData event that it is ready to accept incoming data.
3231    locked: Cell<bool>,
3232    /// Temporary storage for incoming data.
3233    buffers: VecDeque<DataBuffer>,
3234}
3235
3236impl BufferedDataSource {
3237    fn new() -> BufferedDataSource {
3238        BufferedDataSource {
3239            locked: Cell::new(true),
3240            buffers: VecDeque::default(),
3241        }
3242    }
3243
3244    fn set_locked(&self, locked: bool) {
3245        self.locked.set(locked)
3246    }
3247
3248    fn add_buffer_to_queue(&mut self, buffer: DataBuffer) {
3249        debug_assert_ne!(
3250            self.buffers.back(),
3251            Some(&DataBuffer::EndOfStream),
3252            "The media backend not expects any further data after end of stream"
3253        );
3254
3255        self.buffers.push_back(buffer);
3256    }
3257
3258    fn process_into_player_from_queue(
3259        &mut self,
3260        player: &Arc<Mutex<dyn Player>>,
3261    ) -> Result<(), PlayerError> {
3262        // Early out if any request for processing should be ignored.
3263        if self.locked.get() {
3264            return Ok(());
3265        }
3266
3267        while let Some(buffer) = self.buffers.pop_front() {
3268            match buffer {
3269                DataBuffer::Payload(payload) => {
3270                    if let Err(e) = player.lock().unwrap().push_data(payload) {
3271                        warn!("Could not push input data to player {:?}", e);
3272                        return Err(e);
3273                    }
3274                },
3275                DataBuffer::EndOfStream => {
3276                    if let Err(e) = player.lock().unwrap().end_of_stream() {
3277                        warn!("Could not signal EOS to player {:?}", e);
3278                        return Err(e);
3279                    }
3280                },
3281            }
3282        }
3283
3284        Ok(())
3285    }
3286
3287    fn reset(&mut self) {
3288        self.locked.set(true);
3289        self.buffers.clear();
3290    }
3291}
3292
3293/// Indicates the reason why a fetch request was cancelled.
3294#[derive(Debug, MallocSizeOf, PartialEq)]
3295enum CancelReason {
3296    /// We were asked to stop pushing data to the player.
3297    Backoff,
3298    /// An error ocurred while fetching the media data.
3299    Error,
3300    /// The fetching process is aborted by the user.
3301    Abort,
3302}
3303
3304#[derive(MallocSizeOf)]
3305pub(crate) struct HTMLMediaElementFetchContext {
3306    /// The fetch request id.
3307    request_id: RequestId,
3308    /// Some if the request has been cancelled.
3309    cancel_reason: Option<CancelReason>,
3310    /// Indicates whether the fetched stream is seekable.
3311    is_seekable: bool,
3312    /// Indicates whether the fetched stream is origin clean.
3313    origin_clean: bool,
3314    /// The buffered data source which to be processed by media backend.
3315    data_source: RefCell<BufferedDataSource>,
3316    /// Fetch canceller. Allows cancelling the current fetch request by
3317    /// manually calling its .cancel() method or automatically on Drop.
3318    fetch_canceller: FetchCanceller,
3319}
3320
3321impl HTMLMediaElementFetchContext {
3322    fn new(
3323        request_id: RequestId,
3324        core_resource_thread: CoreResourceThread,
3325    ) -> HTMLMediaElementFetchContext {
3326        HTMLMediaElementFetchContext {
3327            request_id,
3328            cancel_reason: None,
3329            is_seekable: false,
3330            origin_clean: true,
3331            data_source: RefCell::new(BufferedDataSource::new()),
3332            fetch_canceller: FetchCanceller::new(request_id, core_resource_thread.clone()),
3333        }
3334    }
3335
3336    fn request_id(&self) -> RequestId {
3337        self.request_id
3338    }
3339
3340    fn is_seekable(&self) -> bool {
3341        self.is_seekable
3342    }
3343
3344    fn set_seekable(&mut self, seekable: bool) {
3345        self.is_seekable = seekable;
3346    }
3347
3348    fn origin_is_clean(&self) -> bool {
3349        self.origin_clean
3350    }
3351
3352    fn set_origin_clean(&mut self, origin_clean: bool) {
3353        self.origin_clean = origin_clean;
3354    }
3355
3356    fn data_source(&self) -> &RefCell<BufferedDataSource> {
3357        &self.data_source
3358    }
3359
3360    fn cancel(&mut self, reason: CancelReason) {
3361        if self.cancel_reason.is_some() {
3362            return;
3363        }
3364        self.cancel_reason = Some(reason);
3365        self.data_source.borrow_mut().reset();
3366        self.fetch_canceller.cancel();
3367    }
3368
3369    fn cancel_reason(&self) -> &Option<CancelReason> {
3370        &self.cancel_reason
3371    }
3372}
3373
3374struct HTMLMediaElementFetchListener {
3375    /// The element that initiated the request.
3376    element: Trusted<HTMLMediaElement>,
3377    /// The generation of the media element when this fetch started.
3378    generation_id: u32,
3379    /// The fetch request id.
3380    request_id: RequestId,
3381    /// Time of last progress notification.
3382    next_progress_event: Instant,
3383    /// Timing data for this resource.
3384    resource_timing: ResourceFetchTiming,
3385    /// Url for the resource.
3386    url: ServoUrl,
3387    /// Expected content length of the media asset being fetched or played.
3388    expected_content_length: Option<u64>,
3389    /// Actual content length of the media asset was fetched.
3390    fetched_content_length: u64,
3391    /// Discarded content length from the network for the ongoing
3392    /// request if range requests are not supported. Seek requests set it
3393    /// to the required position (in bytes).
3394    content_length_to_discard: u64,
3395}
3396
3397impl FetchResponseListener for HTMLMediaElementFetchListener {
3398    fn process_request_body(&mut self, _: RequestId) {}
3399
3400    fn process_request_eof(&mut self, _: RequestId) {}
3401
3402    fn process_response(&mut self, _: RequestId, metadata: Result<FetchMetadata, NetworkError>) {
3403        let element = self.element.root();
3404
3405        let (metadata, origin_clean) = match metadata {
3406            Ok(fetch_metadata) => match fetch_metadata {
3407                FetchMetadata::Unfiltered(metadata) => (Some(metadata), true),
3408                FetchMetadata::Filtered { filtered, unsafe_ } => (
3409                    Some(unsafe_),
3410                    matches!(
3411                        filtered,
3412                        FilteredMetadata::Basic(_) | FilteredMetadata::Cors(_)
3413                    ),
3414                ),
3415            },
3416            Err(_) => (None, true),
3417        };
3418
3419        let (status_is_success, is_seekable) =
3420            metadata.as_ref().map_or((false, false), |metadata| {
3421                let status = &metadata.status;
3422                (status.is_success(), *status == StatusCode::PARTIAL_CONTENT)
3423            });
3424
3425        // <https://html.spec.whatwg.org/multipage/#media-data-processing-steps-list>
3426        if !status_is_success {
3427            if element.ready_state.get() == ReadyState::HaveNothing {
3428                // => "If the media data cannot be fetched at all, due to network errors..."
3429                element.media_data_processing_failure_steps();
3430            } else {
3431                // => "If the connection is interrupted after some media data has been received..."
3432                element.media_data_processing_fatal_steps(MEDIA_ERR_NETWORK, CanGc::note());
3433            }
3434            return;
3435        }
3436
3437        if let Some(ref mut current_fetch_context) = *element.current_fetch_context.borrow_mut() {
3438            current_fetch_context.set_seekable(is_seekable);
3439            current_fetch_context.set_origin_clean(origin_clean);
3440        }
3441
3442        if let Some(metadata) = metadata.as_ref() {
3443            if let Some(headers) = metadata.headers.as_ref() {
3444                // For range requests we get the size of the media asset from the Content-Range
3445                // header. Otherwise, we get it from the Content-Length header.
3446                let content_length =
3447                    if let Some(content_range) = headers.typed_get::<ContentRange>() {
3448                        content_range.bytes_len()
3449                    } else {
3450                        headers
3451                            .typed_get::<ContentLength>()
3452                            .map(|content_length| content_length.0)
3453                    };
3454
3455                // We only set the expected input size if it changes.
3456                if content_length != self.expected_content_length {
3457                    if let Some(content_length) = content_length {
3458                        self.expected_content_length = Some(content_length);
3459                    }
3460                }
3461            }
3462        }
3463
3464        // Explicit media player initialization with live/seekable source.
3465        if let Some(expected_content_length) = self.expected_content_length {
3466            if let Err(e) = element
3467                .player
3468                .borrow()
3469                .as_ref()
3470                .unwrap()
3471                .lock()
3472                .unwrap()
3473                .set_input_size(expected_content_length)
3474            {
3475                warn!("Could not set player input size {:?}", e);
3476            }
3477        }
3478    }
3479
3480    fn process_response_chunk(&mut self, _: RequestId, chunk: Vec<u8>) {
3481        let element = self.element.root();
3482
3483        self.fetched_content_length += chunk.len() as u64;
3484
3485        // If an error was received previously, we skip processing the payload.
3486        if let Some(ref mut current_fetch_context) = *element.current_fetch_context.borrow_mut() {
3487            if let Some(CancelReason::Backoff) = current_fetch_context.cancel_reason() {
3488                return;
3489            }
3490
3491            // Discard chunk of the response body if fetch context doesn't support range requests.
3492            let payload = if !current_fetch_context.is_seekable() &&
3493                self.content_length_to_discard != 0
3494            {
3495                if chunk.len() as u64 > self.content_length_to_discard {
3496                    let shrink_chunk = chunk[self.content_length_to_discard as usize..].to_vec();
3497                    self.content_length_to_discard = 0;
3498                    shrink_chunk
3499                } else {
3500                    // Completely discard this response chunk.
3501                    self.content_length_to_discard -= chunk.len() as u64;
3502                    return;
3503                }
3504            } else {
3505                chunk
3506            };
3507
3508            if let Err(e) = {
3509                let mut data_source = current_fetch_context.data_source().borrow_mut();
3510                data_source.add_buffer_to_queue(DataBuffer::Payload(payload));
3511                data_source
3512                    .process_into_player_from_queue(element.player.borrow().as_ref().unwrap())
3513            } {
3514                // If we are pushing too much data and we know that we can
3515                // restart the download later from where we left, we cancel
3516                // the current request. Otherwise, we continue the request
3517                // assuming that we may drop some frames.
3518                if e == PlayerError::EnoughData {
3519                    current_fetch_context.cancel(CancelReason::Backoff);
3520                }
3521                return;
3522            }
3523        }
3524
3525        // https://html.spec.whatwg.org/multipage/#concept-media-load-resource step 4,
3526        // => "If mode is remote" step 2
3527        if Instant::now() > self.next_progress_event {
3528            element
3529                .owner_global()
3530                .task_manager()
3531                .media_element_task_source()
3532                .queue_simple_event(element.upcast(), atom!("progress"));
3533            self.next_progress_event = Instant::now() + Duration::from_millis(350);
3534        }
3535    }
3536
3537    fn process_response_eof(
3538        &mut self,
3539        _: RequestId,
3540        status: Result<ResourceFetchTiming, NetworkError>,
3541    ) {
3542        let element = self.element.root();
3543
3544        // <https://html.spec.whatwg.org/multipage/#media-data-processing-steps-list>
3545        if status.is_ok() && self.fetched_content_length != 0 {
3546            // => "Once the entire media resource has been fetched..."
3547
3548            // There are no more chunks of the response body forthcoming, so we can
3549            // go ahead and notify the media backend not to expect any further data.
3550            if let Some(ref mut current_fetch_context) = *element.current_fetch_context.borrow_mut()
3551            {
3552                // On initial state change READY -> PAUSED the media player perform
3553                // seek to initial position by event with seek segment (TIME format)
3554                // while media stack operates in BYTES format and configuring segment
3555                // start and stop positions without the total size of the stream is not
3556                // possible. As fallback the media player perform seek with BYTES format
3557                // and initiate seek request via "seek-data" callback with required offset.
3558                if self.expected_content_length.is_none() {
3559                    if let Err(e) = element
3560                        .player
3561                        .borrow()
3562                        .as_ref()
3563                        .unwrap()
3564                        .lock()
3565                        .unwrap()
3566                        .set_input_size(self.fetched_content_length)
3567                    {
3568                        warn!("Could not set player input size {:?}", e);
3569                    }
3570                }
3571
3572                let mut data_source = current_fetch_context.data_source().borrow_mut();
3573
3574                data_source.add_buffer_to_queue(DataBuffer::EndOfStream);
3575                let _ = data_source
3576                    .process_into_player_from_queue(element.player.borrow().as_ref().unwrap());
3577            }
3578
3579            // Step 1. Fire an event named progress at the media element.
3580            element
3581                .upcast::<EventTarget>()
3582                .fire_event(atom!("progress"), CanGc::note());
3583
3584            // Step 2. Set the networkState to NETWORK_IDLE and fire an event named suspend at the
3585            // media element.
3586            element.network_state.set(NetworkState::Idle);
3587
3588            element
3589                .upcast::<EventTarget>()
3590                .fire_event(atom!("suspend"), CanGc::note());
3591        } else if status.is_err() && element.ready_state.get() != ReadyState::HaveNothing {
3592            // => "If the connection is interrupted after some media data has been received..."
3593            element.media_data_processing_fatal_steps(MEDIA_ERR_NETWORK, CanGc::note());
3594        } else {
3595            // => "If the media data can be fetched but is found by inspection to be in an
3596            // unsupported format, or can otherwise not be rendered at all"
3597            element.media_data_processing_failure_steps();
3598        }
3599    }
3600
3601    fn resource_timing_mut(&mut self) -> &mut ResourceFetchTiming {
3602        &mut self.resource_timing
3603    }
3604
3605    fn resource_timing(&self) -> &ResourceFetchTiming {
3606        &self.resource_timing
3607    }
3608
3609    fn submit_resource_timing(&mut self) {
3610        network_listener::submit_timing(self, CanGc::note())
3611    }
3612
3613    fn process_csp_violations(&mut self, _request_id: RequestId, violations: Vec<Violation>) {
3614        let global = &self.resource_timing_global();
3615        global.report_csp_violations(violations, None, None);
3616    }
3617
3618    fn should_invoke(&self) -> bool {
3619        let element = self.element.root();
3620
3621        if element.generation_id.get() != self.generation_id || element.player.borrow().is_none() {
3622            return false;
3623        }
3624
3625        let Some(ref current_fetch_context) = *element.current_fetch_context.borrow() else {
3626            return false;
3627        };
3628
3629        // Whether the new fetch request was triggered.
3630        if current_fetch_context.request_id() != self.request_id {
3631            return false;
3632        }
3633
3634        // Whether the current fetch request was cancelled due to a network or decoding error, or
3635        // was aborted by the user.
3636        if let Some(cancel_reason) = current_fetch_context.cancel_reason() {
3637            if matches!(*cancel_reason, CancelReason::Error | CancelReason::Abort) {
3638                return false;
3639            }
3640        }
3641
3642        true
3643    }
3644}
3645
3646impl ResourceTimingListener for HTMLMediaElementFetchListener {
3647    fn resource_timing_information(&self) -> (InitiatorType, ServoUrl) {
3648        let initiator_type = InitiatorType::LocalName(
3649            self.element
3650                .root()
3651                .upcast::<Element>()
3652                .local_name()
3653                .to_string(),
3654        );
3655        (initiator_type, self.url.clone())
3656    }
3657
3658    fn resource_timing_global(&self) -> DomRoot<GlobalScope> {
3659        self.element.root().owner_document().global()
3660    }
3661}
3662
3663impl HTMLMediaElementFetchListener {
3664    fn new(element: &HTMLMediaElement, request_id: RequestId, url: ServoUrl, offset: u64) -> Self {
3665        Self {
3666            element: Trusted::new(element),
3667            generation_id: element.generation_id.get(),
3668            request_id,
3669            next_progress_event: Instant::now() + Duration::from_millis(350),
3670            resource_timing: ResourceFetchTiming::new(ResourceTimingType::Resource),
3671            url,
3672            expected_content_length: None,
3673            fetched_content_length: 0,
3674            content_length_to_discard: offset,
3675        }
3676    }
3677}