diff options
Diffstat (limited to 'components/script/dom')
20 files changed, 724 insertions, 115 deletions
diff --git a/components/script/dom/audiocontext.rs b/components/script/dom/audiocontext.rs index 2b6f04b421b..0212cb78ffa 100644 --- a/components/script/dom/audiocontext.rs +++ b/components/script/dom/audiocontext.rs @@ -20,6 +20,8 @@ use crate::dom::bindings::num::Finite; use crate::dom::bindings::refcounted::{Trusted, TrustedPromise}; use crate::dom::bindings::reflector::{reflect_dom_object, DomObject}; use crate::dom::bindings::root::DomRoot; +use crate::dom::htmlmediaelement::HTMLMediaElement; +use crate::dom::mediaelementaudiosourcenode::MediaElementAudioSourceNode; use crate::dom::promise::Promise; use crate::dom::window::Window; use crate::task_source::TaskSource; @@ -97,6 +99,10 @@ impl AudioContext { self.context.resume(); } } + + pub fn base(&self) -> DomRoot<BaseAudioContext> { + DomRoot::from_ref(&self.context) + } } impl AudioContextMethods for AudioContext { @@ -240,6 +246,16 @@ impl AudioContextMethods for AudioContext { // Step 6. promise } + + /// https://webaudio.github.io/web-audio-api/#dom-audiocontext-createmediaelementsource + fn CreateMediaElementSource( + &self, + media_element: &HTMLMediaElement, + ) -> Fallible<DomRoot<MediaElementAudioSourceNode>> { + let global = self.global(); + let window = global.as_window(); + MediaElementAudioSourceNode::new(window, self, media_element) + } } impl From<AudioContextLatencyCategory> for LatencyCategory { diff --git a/components/script/dom/bindings/codegen/CodegenRust.py b/components/script/dom/bindings/codegen/CodegenRust.py index 7610b8442b4..fe3398417b2 100644 --- a/components/script/dom/bindings/codegen/CodegenRust.py +++ b/components/script/dom/bindings/codegen/CodegenRust.py @@ -6425,7 +6425,8 @@ class CGDictionary(CGThing): mustRoot = "" if self.membersNeedTracing(): mustRoot = "#[unrooted_must_root_lint::must_root]\n" - derive += ["Default"] + if not self.hasRequiredFields(self.dictionary): + derive += ["Default"] return (string.Template( "#[derive(${derive})]\n" @@ -6485,16 +6486,14 @@ class CGDictionary(CGThing): selfName = self.makeClassName(d) if self.membersNeedTracing(): actualType = "RootedTraceableBox<%s>" % selfName - preInitial = "let mut dictionary = RootedTraceableBox::new(%s::default());\n" % selfName - initParent = initParent = ("dictionary.parent = %s;\n" % initParent) if initParent else "" - memberInits = CGList([memberInit(m, False) for m in self.memberInfo]) - postInitial = "" + preInitial = "let dictionary = RootedTraceableBox::new(%s {\n" % selfName + postInitial = "});\n" else: actualType = selfName preInitial = "let dictionary = %s {\n" % selfName postInitial = "};\n" - initParent = ("parent: %s,\n" % initParent) if initParent else "" - memberInits = CGList([memberInit(m, True) for m in self.memberInfo]) + initParent = ("parent: %s,\n" % initParent) if initParent else "" + memberInits = CGList([memberInit(m, True) for m in self.memberInfo]) return string.Template( "impl ${selfName} {\n" @@ -6540,8 +6539,8 @@ class CGDictionary(CGThing): "initParent": CGIndenter(CGGeneric(initParent), indentLevel=16).define(), "initMembers": CGIndenter(memberInits, indentLevel=16).define(), "insertMembers": CGIndenter(memberInserts, indentLevel=8).define(), - "preInitial": CGIndenter(CGGeneric(preInitial), indentLevel=16).define(), - "postInitial": CGIndenter(CGGeneric(postInitial), indentLevel=16).define(), + "preInitial": CGIndenter(CGGeneric(preInitial), indentLevel=8).define(), + "postInitial": CGIndenter(CGGeneric(postInitial), indentLevel=8).define(), }) def membersNeedTracing(self): diff --git a/components/script/dom/bindings/trace.rs b/components/script/dom/bindings/trace.rs index 65faac1bb95..2051a1a19d8 100644 --- a/components/script/dom/bindings/trace.rs +++ b/components/script/dom/bindings/trace.rs @@ -57,7 +57,7 @@ use content_security_policy::CspList; use crossbeam_channel::{Receiver, Sender}; use cssparser::RGBA; use devtools_traits::{CSSError, TimelineMarkerType, WorkerId}; -use embedder_traits::EventLoopWaker; +use embedder_traits::{EventLoopWaker, MediaMetadata}; use encoding_rs::{Decoder, Encoding}; use euclid::default::{Point2D, Rect, Rotation3D, Transform2D, Transform3D}; use euclid::Length as EuclidLength; @@ -94,8 +94,8 @@ use profile_traits::time::ProfilerChan as TimeProfilerChan; use script_layout_interface::rpc::LayoutRPC; use script_layout_interface::OpaqueStyleAndLayoutData; use script_traits::transferable::MessagePortImpl; -use script_traits::DrawAPaintImageResult; -use script_traits::{DocumentActivity, ScriptToConstellationChan, TimerEventId, TimerSource}; +use script_traits::{DocumentActivity, DrawAPaintImageResult}; +use script_traits::{MediaSessionActionType, ScriptToConstellationChan, TimerEventId, TimerSource}; use script_traits::{UntrustedNodeAddress, WindowSizeData, WindowSizeType}; use selectors::matching::ElementSelectorFlags; use serde::{Deserialize, Serialize}; @@ -107,7 +107,8 @@ use servo_media::audio::context::AudioContext; use servo_media::audio::graph::NodeId; use servo_media::audio::panner_node::{DistanceModel, PanningModel}; use servo_media::audio::param::ParamType; -use servo_media::player::frame::Frame; +use servo_media::player::audio::AudioRenderer; +use servo_media::player::video::VideoFrame; use servo_media::player::Player; use servo_media::streams::registry::MediaStreamId; use servo_media::streams::MediaStreamType; @@ -532,8 +533,11 @@ unsafe_no_jsmanaged_fields!(Point2D<f32>, Rect<Au>); unsafe_no_jsmanaged_fields!(Rect<f32>); unsafe_no_jsmanaged_fields!(CascadeData); unsafe_no_jsmanaged_fields!(WindowGLContext); -unsafe_no_jsmanaged_fields!(Frame); +unsafe_no_jsmanaged_fields!(VideoFrame); unsafe_no_jsmanaged_fields!(WebGLContextId); +unsafe_no_jsmanaged_fields!(Arc<Mutex<dyn AudioRenderer>>); +unsafe_no_jsmanaged_fields!(MediaSessionActionType); +unsafe_no_jsmanaged_fields!(MediaMetadata); unsafe impl<'a> JSTraceable for &'a str { #[inline] diff --git a/components/script/dom/dedicatedworkerglobalscope.rs b/components/script/dom/dedicatedworkerglobalscope.rs index f5895379003..9b16b0348d7 100644 --- a/components/script/dom/dedicatedworkerglobalscope.rs +++ b/components/script/dom/dedicatedworkerglobalscope.rs @@ -32,6 +32,7 @@ use crate::script_runtime::{ new_child_runtime, CommonScriptMsg, JSContext as SafeJSContext, Runtime, ScriptChan, ScriptPort, }; use crate::task_queue::{QueuedTask, QueuedTaskConversion, TaskQueue}; +use crate::task_source::networking::NetworkingTaskSource; use crate::task_source::TaskSourceName; use crossbeam_channel::{unbounded, Receiver, Sender}; use devtools_traits::DevtoolScriptControlMsg; @@ -344,7 +345,16 @@ impl DedicatedWorkerGlobalScope { .referrer_policy(referrer_policy) .origin(origin); - let runtime = unsafe { new_child_runtime(parent) }; + let runtime = unsafe { + if let Some(pipeline_id) = pipeline_id { + new_child_runtime( + parent, + Some(NetworkingTaskSource(parent_sender.clone(), pipeline_id)), + ) + } else { + new_child_runtime(parent, None) + } + }; let (devtools_mpsc_chan, devtools_mpsc_port) = unbounded(); ROUTER.route_ipc_receiver_to_crossbeam_sender( diff --git a/components/script/dom/document.rs b/components/script/dom/document.rs index 5a344879005..a7a56665bf1 100644 --- a/components/script/dom/document.rs +++ b/components/script/dom/document.rs @@ -506,62 +506,69 @@ impl Document { pub fn set_activity(&self, activity: DocumentActivity) { // This function should only be called on documents with a browsing context assert!(self.has_browsing_context); + if activity == self.activity.get() { + return; + } + // Set the document's activity level, reflow if necessary, and suspend or resume timers. - if activity != self.activity.get() { - self.activity.set(activity); - let media = ServoMedia::get().unwrap(); - let pipeline_id = self.window().pipeline_id().expect("doc with no pipeline"); - let client_context_id = - ClientContextId::build(pipeline_id.namespace_id.0, pipeline_id.index.0.get()); - if activity == DocumentActivity::FullyActive { - self.title_changed(); - self.dirty_all_nodes(); - self.window() - .reflow(ReflowGoal::Full, ReflowReason::CachedPageNeededReflow); - self.window().resume(); - media.resume(&client_context_id); - // html.spec.whatwg.org/multipage/#history-traversal - // Step 4.6 - if self.ready_state.get() == DocumentReadyState::Complete { - let document = Trusted::new(self); - self.window - .task_manager() - .dom_manipulation_task_source() - .queue( - task!(fire_pageshow_event: move || { - let document = document.root(); - let window = document.window(); - // Step 4.6.1 - if document.page_showing.get() { - return; - } - // Step 4.6.2 - document.page_showing.set(true); - // Step 4.6.4 - let event = PageTransitionEvent::new( - window, - atom!("pageshow"), - false, // bubbles - false, // cancelable - true, // persisted - ); - let event = event.upcast::<Event>(); - event.set_trusted(true); - // FIXME(nox): Why are errors silenced here? - let _ = window.upcast::<EventTarget>().dispatch_event_with_target( - document.upcast(), - &event, - ); - }), - self.window.upcast(), - ) - .unwrap(); - } - } else { - self.window().suspend(); - media.suspend(&client_context_id); - } + self.activity.set(activity); + let media = ServoMedia::get().unwrap(); + let pipeline_id = self.window().pipeline_id().expect("doc with no pipeline"); + let client_context_id = + ClientContextId::build(pipeline_id.namespace_id.0, pipeline_id.index.0.get()); + + if activity != DocumentActivity::FullyActive { + self.window().suspend(); + media.suspend(&client_context_id); + return; } + + self.title_changed(); + self.dirty_all_nodes(); + self.window() + .reflow(ReflowGoal::Full, ReflowReason::CachedPageNeededReflow); + self.window().resume(); + media.resume(&client_context_id); + + if self.ready_state.get() != DocumentReadyState::Complete { + return; + } + + // html.spec.whatwg.org/multipage/#history-traversal + // Step 4.6 + let document = Trusted::new(self); + self.window + .task_manager() + .dom_manipulation_task_source() + .queue( + task!(fire_pageshow_event: move || { + let document = document.root(); + let window = document.window(); + // Step 4.6.1 + if document.page_showing.get() { + return; + } + // Step 4.6.2 + document.page_showing.set(true); + // Step 4.6.4 + let event = PageTransitionEvent::new( + window, + atom!("pageshow"), + false, // bubbles + false, // cancelable + true, // persisted + ); + let event = event.upcast::<Event>(); + event.set_trusted(true); + // FIXME(nox): Why are errors silenced here? + let _ = window.upcast::<EventTarget>().dispatch_event_with_target( + document.upcast(), + &event, + ); + }), + self.window.upcast(), + ) + .unwrap(); } pub fn origin(&self) -> &MutableOrigin { diff --git a/components/script/dom/htmlmediaelement.rs b/components/script/dom/htmlmediaelement.rs index 5afc2deb381..f8f707a0dd9 100644 --- a/components/script/dom/htmlmediaelement.rs +++ b/components/script/dom/htmlmediaelement.rs @@ -15,8 +15,10 @@ use crate::dom::bindings::codegen::Bindings::HTMLMediaElementBinding::HTMLMediaE use crate::dom::bindings::codegen::Bindings::HTMLSourceElementBinding::HTMLSourceElementMethods; use crate::dom::bindings::codegen::Bindings::MediaErrorBinding::MediaErrorConstants::*; use crate::dom::bindings::codegen::Bindings::MediaErrorBinding::MediaErrorMethods; +use crate::dom::bindings::codegen::Bindings::NavigatorBinding::NavigatorBinding::NavigatorMethods; use crate::dom::bindings::codegen::Bindings::NodeBinding::NodeBinding::NodeMethods; use crate::dom::bindings::codegen::Bindings::TextTrackBinding::{TextTrackKind, TextTrackMode}; +use crate::dom::bindings::codegen::Bindings::WindowBinding::WindowBinding::WindowMethods; use crate::dom::bindings::codegen::InheritTypes::{ElementTypeId, HTMLElementTypeId}; use crate::dom::bindings::codegen::InheritTypes::{HTMLMediaElementTypeId, NodeTypeId}; use crate::dom::bindings::codegen::UnionTypes::{ @@ -65,6 +67,7 @@ use crate::script_thread::ScriptThread; use crate::task_source::TaskSource; use dom_struct::dom_struct; use embedder_traits::resources::{self, Resource as EmbedderResource}; +use embedder_traits::{MediaSessionEvent, MediaSessionPlaybackState}; use euclid::default::Size2D; use headers::{ContentLength, ContentRange, HeaderMapExt}; use html5ever::{LocalName, Prefix}; @@ -79,7 +82,8 @@ use net_traits::{CoreResourceMsg, FetchChannels, FetchMetadata, FetchResponseLis use net_traits::{NetworkError, ResourceFetchTiming, ResourceTimingType}; use script_layout_interface::HTMLMediaData; use servo_config::pref; -use servo_media::player::frame::{Frame, FrameRenderer}; +use servo_media::player::audio::AudioRenderer; +use servo_media::player::video::{VideoFrame, VideoFrameRenderer}; use servo_media::player::{PlaybackState, Player, PlayerError, PlayerEvent, SeekLock, StreamType}; use servo_media::{ClientContextId, ServoMedia, SupportsMediaType}; use servo_url::ServoUrl; @@ -100,10 +104,10 @@ enum FrameStatus { Unlocked, } -struct FrameHolder(FrameStatus, Frame); +struct FrameHolder(FrameStatus, VideoFrame); impl FrameHolder { - fn new(frame: Frame) -> FrameHolder { + fn new(frame: VideoFrame) -> FrameHolder { FrameHolder(FrameStatus::Unlocked, frame) } @@ -119,7 +123,7 @@ impl FrameHolder { }; } - fn set(&mut self, new_frame: Frame) { + fn set(&mut self, new_frame: VideoFrame) { if self.0 == FrameStatus::Unlocked { self.1 = new_frame }; @@ -137,7 +141,7 @@ impl FrameHolder { } } - fn get_frame(&self) -> Frame { + fn get_frame(&self) -> VideoFrame { self.1.clone() } } @@ -170,8 +174,8 @@ impl MediaFrameRenderer { } } -impl FrameRenderer for MediaFrameRenderer { - fn render(&mut self, frame: Frame) { +impl VideoFrameRenderer for MediaFrameRenderer { + fn render(&mut self, frame: VideoFrame) { let mut txn = Transaction::new(); if let Some(old_image_key) = mem::replace(&mut self.very_old_frame, self.old_frame.take()) { @@ -325,7 +329,9 @@ pub struct HTMLMediaElement { #[ignore_malloc_size_of = "servo_media"] player: DomRefCell<Option<Arc<Mutex<dyn Player>>>>, #[ignore_malloc_size_of = "Arc"] - frame_renderer: Arc<Mutex<MediaFrameRenderer>>, + video_renderer: Arc<Mutex<MediaFrameRenderer>>, + #[ignore_malloc_size_of = "Arc"] + audio_renderer: DomRefCell<Option<Arc<Mutex<dyn AudioRenderer>>>>, /// https://html.spec.whatwg.org/multipage/#show-poster-flag show_poster: Cell<bool>, /// https://html.spec.whatwg.org/multipage/#dom-media-duration @@ -410,9 +416,10 @@ impl HTMLMediaElement { pending_play_promises: Default::default(), in_flight_play_promises_queue: Default::default(), player: Default::default(), - frame_renderer: Arc::new(Mutex::new(MediaFrameRenderer::new( + video_renderer: Arc::new(Mutex::new(MediaFrameRenderer::new( document.window().get_webrender_api_sender(), ))), + audio_renderer: Default::default(), show_poster: Cell::new(true), duration: Cell::new(f64::NAN), playback_position: Cell::new(0.), @@ -588,7 +595,6 @@ impl HTMLMediaElement { match (old_ready_state, ready_state) { (ReadyState::HaveNothing, ReadyState::HaveMetadata) => { task_source.queue_simple_event(self.upcast(), atom!("loadedmetadata"), &window); - // No other steps are applicable in this case. return; }, @@ -1293,7 +1299,7 @@ impl HTMLMediaElement { // Step 6. if let ImageResponse::Loaded(image, _) = image { - self.frame_renderer + self.video_renderer .lock() .unwrap() .render_poster_frame(image); @@ -1325,11 +1331,14 @@ impl HTMLMediaElement { let window = window_from_node(self); let (action_sender, action_receiver) = ipc::channel::<PlayerEvent>().unwrap(); - let renderer: Option<Arc<Mutex<dyn FrameRenderer>>> = match self.media_type_id() { + let video_renderer: Option<Arc<Mutex<dyn VideoFrameRenderer>>> = match self.media_type_id() + { HTMLMediaElementTypeId::HTMLAudioElement => None, - HTMLMediaElementTypeId::HTMLVideoElement => Some(self.frame_renderer.clone()), + HTMLMediaElementTypeId::HTMLVideoElement => Some(self.video_renderer.clone()), }; + let audio_renderer = self.audio_renderer.borrow().as_ref().map(|r| r.clone()); + let pipeline_id = window .pipeline_id() .expect("Cannot create player outside of a pipeline"); @@ -1339,7 +1348,8 @@ impl HTMLMediaElement { &client_context_id, stream_type, action_sender, - renderer, + video_renderer, + audio_renderer, Box::new(window.get_player_context()), ); @@ -1385,7 +1395,7 @@ impl HTMLMediaElement { .unwrap_or((0, None)); self.id.set(player_id); - self.frame_renderer.lock().unwrap().player_id = Some(player_id); + self.video_renderer.lock().unwrap().player_id = Some(player_id); if let Some(image_receiver) = image_receiver { let trusted_node = Trusted::new(self); @@ -1400,11 +1410,11 @@ impl HTMLMediaElement { if let Err(err) = task_source.queue_with_canceller( task!(handle_glplayer_message: move || { trace!("GLPlayer message {:?}", msg); - let frame_renderer = this.root().frame_renderer.clone(); + let video_renderer = this.root().video_renderer.clone(); match msg { GLPlayerMsgForward::Lock(sender) => { - frame_renderer + video_renderer .lock() .unwrap() .current_frame_holder @@ -1415,7 +1425,7 @@ impl HTMLMediaElement { }); }, GLPlayerMsgForward::Unlock() => { - frame_renderer + video_renderer .lock() .unwrap() .current_frame_holder @@ -1527,7 +1537,7 @@ impl HTMLMediaElement { ))); self.upcast::<EventTarget>().fire_event(atom!("error")); }, - PlayerEvent::FrameUpdated => { + PlayerEvent::VideoFrameUpdated => { self.upcast::<Node>().dirty(NodeDamage::OtherNodeDamage); }, PlayerEvent::MetadataUpdated(ref metadata) => { @@ -1717,6 +1727,17 @@ impl HTMLMediaElement { if self.Controls() { self.render_controls(); } + + let global = self.global(); + let window = global.as_window(); + + // Update the media session metadata title with the obtained metadata. + window.Navigator().MediaSession().update_title( + metadata + .title + .clone() + .unwrap_or(window.get_url().into_string()), + ); }, PlayerEvent::NeedData => { // The player needs more data. @@ -1774,13 +1795,33 @@ impl HTMLMediaElement { }; ScriptThread::await_stable_state(Microtask::MediaElement(task)); }, - PlayerEvent::StateChanged(ref state) => match *state { - PlaybackState::Paused => { - if self.ready_state.get() == ReadyState::HaveMetadata { - self.change_ready_state(ReadyState::HaveEnoughData); - } - }, - _ => {}, + PlayerEvent::StateChanged(ref state) => { + let mut media_session_playback_state = MediaSessionPlaybackState::None_; + match *state { + PlaybackState::Paused => { + media_session_playback_state = MediaSessionPlaybackState::Paused; + if self.ready_state.get() == ReadyState::HaveMetadata { + self.change_ready_state(ReadyState::HaveEnoughData); + } + }, + PlaybackState::Playing => { + media_session_playback_state = MediaSessionPlaybackState::Playing; + }, + PlaybackState::Buffering => { + // Do not send the media session playback state change event + // in this case as a None_ state is expected to clean up the + // session. + return; + }, + _ => {}, + }; + debug!( + "Sending media session event playback state changed to {:?}", + media_session_playback_state + ); + self.send_media_session_event(MediaSessionEvent::PlaybackStateChange( + media_session_playback_state, + )); }, } } @@ -1855,12 +1896,35 @@ impl HTMLMediaElement { } } - pub fn get_current_frame(&self) -> Option<Frame> { - match self.frame_renderer.lock().unwrap().current_frame_holder { + pub fn get_current_frame(&self) -> Option<VideoFrame> { + match self.video_renderer.lock().unwrap().current_frame_holder { Some(ref holder) => Some(holder.get_frame()), None => return None, } } + + /// By default the audio is rendered through the audio sink automatically + /// selected by the servo-media Player instance. However, in some cases, like + /// the WebAudio MediaElementAudioSourceNode, we need to set a custom audio + /// renderer. + pub fn set_audio_renderer(&self, audio_renderer: Arc<Mutex<dyn AudioRenderer>>) { + *self.audio_renderer.borrow_mut() = Some(audio_renderer); + if let Some(ref player) = *self.player.borrow() { + if let Err(e) = player.lock().unwrap().stop() { + eprintln!("Could not stop player {:?}", e); + } + self.media_element_load_algorithm(); + } + } + + fn send_media_session_event(&self, event: MediaSessionEvent) { + let global = self.global(); + let media_session = global.as_window().Navigator().MediaSession(); + + media_session.register_media_instance(&self); + + media_session.send_event(event); + } } // XXX Placeholder for [https://github.com/servo/servo/issues/22293] @@ -2365,7 +2429,7 @@ impl LayoutHTMLMediaElementHelpers for LayoutDom<HTMLMediaElement> { fn data(&self) -> HTMLMediaData { let media = unsafe { &*self.unsafe_get() }; HTMLMediaData { - current_frame: media.frame_renderer.lock().unwrap().current_frame.clone(), + current_frame: media.video_renderer.lock().unwrap().current_frame.clone(), } } } diff --git a/components/script/dom/htmlvideoelement.rs b/components/script/dom/htmlvideoelement.rs index 55990c1ffe5..69f8b9a85e6 100644 --- a/components/script/dom/htmlvideoelement.rs +++ b/components/script/dom/htmlvideoelement.rs @@ -35,7 +35,7 @@ use net_traits::{ CoreResourceMsg, FetchChannels, FetchMetadata, FetchResponseListener, FetchResponseMsg, }; use net_traits::{NetworkError, ResourceFetchTiming, ResourceTimingType}; -use servo_media::player::frame::Frame; +use servo_media::player::video::VideoFrame; use servo_url::ServoUrl; use std::cell::Cell; use std::sync::{Arc, Mutex}; @@ -58,8 +58,8 @@ pub struct HTMLVideoElement { /// is being fetched. load_blocker: DomRefCell<Option<LoadBlocker>>, /// A copy of the last frame - #[ignore_malloc_size_of = "Frame"] - last_frame: DomRefCell<Option<Frame>>, + #[ignore_malloc_size_of = "VideoFrame"] + last_frame: DomRefCell<Option<VideoFrame>>, } impl HTMLVideoElement { diff --git a/components/script/dom/mediaelementaudiosourcenode.rs b/components/script/dom/mediaelementaudiosourcenode.rs new file mode 100644 index 00000000000..07ca70bd80e --- /dev/null +++ b/components/script/dom/mediaelementaudiosourcenode.rs @@ -0,0 +1,80 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +use crate::dom::audiocontext::AudioContext; +use crate::dom::audionode::AudioNode; +use crate::dom::bindings::codegen::Bindings::MediaElementAudioSourceNodeBinding; +use crate::dom::bindings::codegen::Bindings::MediaElementAudioSourceNodeBinding::MediaElementAudioSourceNodeMethods; +use crate::dom::bindings::codegen::Bindings::MediaElementAudioSourceNodeBinding::MediaElementAudioSourceOptions; +use crate::dom::bindings::error::Fallible; +use crate::dom::bindings::reflector::reflect_dom_object; +use crate::dom::bindings::root::{Dom, DomRoot}; +use crate::dom::htmlmediaelement::HTMLMediaElement; +use crate::dom::window::Window; +use dom_struct::dom_struct; +use servo_media::audio::media_element_source_node::MediaElementSourceNodeMessage; +use servo_media::audio::node::{AudioNodeInit, AudioNodeMessage}; +use std::sync::mpsc; + +#[dom_struct] +pub struct MediaElementAudioSourceNode { + node: AudioNode, + media_element: Dom<HTMLMediaElement>, +} + +impl MediaElementAudioSourceNode { + #[allow(unrooted_must_root)] + fn new_inherited( + context: &AudioContext, + media_element: &HTMLMediaElement, + ) -> Fallible<MediaElementAudioSourceNode> { + let node = AudioNode::new_inherited( + AudioNodeInit::MediaElementSourceNode, + &*context.base(), + Default::default(), + 0, + 1, + )?; + let (sender, receiver) = mpsc::channel(); + node.message(AudioNodeMessage::MediaElementSourceNode( + MediaElementSourceNodeMessage::GetAudioRenderer(sender), + )); + let audio_renderer = receiver.recv().unwrap(); + media_element.set_audio_renderer(audio_renderer); + let media_element = Dom::from_ref(media_element); + Ok(MediaElementAudioSourceNode { + node, + media_element, + }) + } + + #[allow(unrooted_must_root)] + pub fn new( + window: &Window, + context: &AudioContext, + media_element: &HTMLMediaElement, + ) -> Fallible<DomRoot<MediaElementAudioSourceNode>> { + let node = MediaElementAudioSourceNode::new_inherited(context, media_element)?; + Ok(reflect_dom_object( + Box::new(node), + window, + MediaElementAudioSourceNodeBinding::Wrap, + )) + } + + pub fn Constructor( + window: &Window, + context: &AudioContext, + options: &MediaElementAudioSourceOptions, + ) -> Fallible<DomRoot<MediaElementAudioSourceNode>> { + MediaElementAudioSourceNode::new(window, context, &*options.mediaElement) + } +} + +impl MediaElementAudioSourceNodeMethods for MediaElementAudioSourceNode { + /// https://webaudio.github.io/web-audio-api/#dom-mediaelementaudiosourcenode-mediaelement + fn MediaElement(&self) -> DomRoot<HTMLMediaElement> { + DomRoot::from_ref(&*self.media_element) + } +} diff --git a/components/script/dom/mediametadata.rs b/components/script/dom/mediametadata.rs new file mode 100644 index 00000000000..f2e94abfaa1 --- /dev/null +++ b/components/script/dom/mediametadata.rs @@ -0,0 +1,97 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +use crate::dom::bindings::cell::DomRefCell; +use crate::dom::bindings::codegen::Bindings::MediaMetadataBinding; +use crate::dom::bindings::codegen::Bindings::MediaMetadataBinding::MediaMetadataInit; +use crate::dom::bindings::codegen::Bindings::MediaMetadataBinding::MediaMetadataMethods; +use crate::dom::bindings::error::Fallible; +use crate::dom::bindings::reflector::{reflect_dom_object, Reflector}; +use crate::dom::bindings::root::{DomRoot, MutNullableDom}; +use crate::dom::bindings::str::DOMString; +use crate::dom::mediasession::MediaSession; +use crate::dom::window::Window; +use dom_struct::dom_struct; + +#[dom_struct] +pub struct MediaMetadata { + reflector_: Reflector, + session: MutNullableDom<MediaSession>, + title: DomRefCell<DOMString>, + artist: DomRefCell<DOMString>, + album: DomRefCell<DOMString>, +} + +impl MediaMetadata { + fn new_inherited(init: &MediaMetadataInit) -> MediaMetadata { + MediaMetadata { + reflector_: Reflector::new(), + session: Default::default(), + title: DomRefCell::new(init.title.clone()), + artist: DomRefCell::new(init.artist.clone()), + album: DomRefCell::new(init.album.clone()), + } + } + + pub fn new(global: &Window, init: &MediaMetadataInit) -> DomRoot<MediaMetadata> { + reflect_dom_object( + Box::new(MediaMetadata::new_inherited(init)), + global, + MediaMetadataBinding::Wrap, + ) + } + + /// https://w3c.github.io/mediasession/#dom-mediametadata-mediametadata + pub fn Constructor( + window: &Window, + init: &MediaMetadataInit, + ) -> Fallible<DomRoot<MediaMetadata>> { + Ok(MediaMetadata::new(window, init)) + } + + fn queue_update_metadata_algorithm(&self) { + if self.session.get().is_none() { + return; + } + } + + pub fn set_session(&self, session: &MediaSession) { + self.session.set(Some(&session)); + } +} + +impl MediaMetadataMethods for MediaMetadata { + /// https://w3c.github.io/mediasession/#dom-mediametadata-title + fn Title(&self) -> DOMString { + self.title.borrow().clone() + } + + /// https://w3c.github.io/mediasession/#dom-mediametadata-title + fn SetTitle(&self, value: DOMString) { + *self.title.borrow_mut() = value; + self.queue_update_metadata_algorithm(); + } + + /// https://w3c.github.io/mediasession/#dom-mediametadata-artist + fn Artist(&self) -> DOMString { + self.artist.borrow().clone() + } + + /// https://w3c.github.io/mediasession/#dom-mediametadata-artist + fn SetArtist(&self, value: DOMString) { + *self.artist.borrow_mut() = value; + self.queue_update_metadata_algorithm(); + } + + /// https://w3c.github.io/mediasession/#dom-mediametadata-album + fn Album(&self) -> DOMString { + self.album.borrow().clone() + } + + /// https://w3c.github.io/mediasession/#dom-mediametadata-album + fn SetAlbum(&self, value: DOMString) { + *self.album.borrow_mut() = value; + self.queue_update_metadata_algorithm(); + } +} diff --git a/components/script/dom/mediasession.rs b/components/script/dom/mediasession.rs new file mode 100644 index 00000000000..1523e9a0ae6 --- /dev/null +++ b/components/script/dom/mediasession.rs @@ -0,0 +1,213 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +use crate::compartments::{AlreadyInCompartment, InCompartment}; +use crate::dom::bindings::callback::ExceptionHandling; +use crate::dom::bindings::cell::DomRefCell; +use crate::dom::bindings::codegen::Bindings::HTMLMediaElementBinding::HTMLMediaElementMethods; +use crate::dom::bindings::codegen::Bindings::MediaMetadataBinding::MediaMetadataInit; +use crate::dom::bindings::codegen::Bindings::MediaMetadataBinding::MediaMetadataMethods; +use crate::dom::bindings::codegen::Bindings::MediaSessionBinding; +use crate::dom::bindings::codegen::Bindings::MediaSessionBinding::MediaSessionAction; +use crate::dom::bindings::codegen::Bindings::MediaSessionBinding::MediaSessionActionHandler; +use crate::dom::bindings::codegen::Bindings::MediaSessionBinding::MediaSessionMethods; +use crate::dom::bindings::codegen::Bindings::MediaSessionBinding::MediaSessionPlaybackState; +use crate::dom::bindings::reflector::{reflect_dom_object, DomObject, Reflector}; +use crate::dom::bindings::root::{DomRoot, MutNullableDom}; +use crate::dom::bindings::str::DOMString; +use crate::dom::htmlmediaelement::HTMLMediaElement; +use crate::dom::mediametadata::MediaMetadata; +use crate::dom::window::Window; +use dom_struct::dom_struct; +use embedder_traits::MediaMetadata as EmbedderMediaMetadata; +use embedder_traits::MediaSessionEvent; +use script_traits::MediaSessionActionType; +use script_traits::ScriptMsg; +use std::collections::HashMap; +use std::rc::Rc; + +#[dom_struct] +pub struct MediaSession { + reflector_: Reflector, + /// https://w3c.github.io/mediasession/#dom-mediasession-metadata + #[ignore_malloc_size_of = "defined in embedder_traits"] + metadata: DomRefCell<Option<EmbedderMediaMetadata>>, + /// https://w3c.github.io/mediasession/#dom-mediasession-playbackstate + playback_state: DomRefCell<MediaSessionPlaybackState>, + /// https://w3c.github.io/mediasession/#supported-media-session-actions + #[ignore_malloc_size_of = "Rc"] + action_handlers: DomRefCell<HashMap<MediaSessionActionType, Rc<MediaSessionActionHandler>>>, + /// The media instance controlled by this media session. + /// For now only HTMLMediaElements are controlled by media sessions. + media_instance: MutNullableDom<HTMLMediaElement>, +} + +impl MediaSession { + #[allow(unrooted_must_root)] + fn new_inherited() -> MediaSession { + let media_session = MediaSession { + reflector_: Reflector::new(), + metadata: DomRefCell::new(None), + playback_state: DomRefCell::new(MediaSessionPlaybackState::None), + action_handlers: DomRefCell::new(HashMap::new()), + media_instance: Default::default(), + }; + media_session + } + + pub fn new(window: &Window) -> DomRoot<MediaSession> { + reflect_dom_object( + Box::new(MediaSession::new_inherited()), + window, + MediaSessionBinding::Wrap, + ) + } + + pub fn register_media_instance(&self, media_instance: &HTMLMediaElement) { + self.media_instance.set(Some(media_instance)); + } + + pub fn handle_action(&self, action: MediaSessionActionType) { + debug!("Handle media session action {:?}", action); + + if let Some(handler) = self.action_handlers.borrow().get(&action) { + if handler.Call__(ExceptionHandling::Report).is_err() { + warn!("Error calling MediaSessionActionHandler callback"); + } + return; + } + + // Default action. + if let Some(media) = self.media_instance.get() { + match action { + MediaSessionActionType::Play => { + let in_compartment_proof = AlreadyInCompartment::assert(&self.global()); + media.Play(InCompartment::Already(&in_compartment_proof)); + }, + MediaSessionActionType::Pause => { + media.Pause(); + }, + MediaSessionActionType::SeekBackward => {}, + MediaSessionActionType::SeekForward => {}, + MediaSessionActionType::PreviousTrack => {}, + MediaSessionActionType::NextTrack => {}, + MediaSessionActionType::SkipAd => {}, + MediaSessionActionType::Stop => {}, + MediaSessionActionType::SeekTo => {}, + } + } + } + + pub fn send_event(&self, event: MediaSessionEvent) { + let global = self.global(); + let window = global.as_window(); + let pipeline_id = window + .pipeline_id() + .expect("Cannot send media session event outside of a pipeline"); + window.send_to_constellation(ScriptMsg::MediaSessionEvent(pipeline_id, event)); + } + + pub fn update_title(&self, title: String) { + let mut metadata = self.metadata.borrow_mut(); + if let Some(ref mut metadata) = *metadata { + // We only update the title with the data provided by the media + // player and iff the user did not provide a title. + if !metadata.title.is_empty() { + return; + } + metadata.title = title; + } else { + *metadata = Some(EmbedderMediaMetadata::new(title)); + } + self.send_event(MediaSessionEvent::SetMetadata( + metadata.as_ref().unwrap().clone(), + )); + } +} + +impl MediaSessionMethods for MediaSession { + /// https://w3c.github.io/mediasession/#dom-mediasession-metadata + fn GetMetadata(&self) -> Option<DomRoot<MediaMetadata>> { + if let Some(ref metadata) = *self.metadata.borrow() { + let mut init = MediaMetadataInit::empty(); + init.title = DOMString::from_string(metadata.title.clone()); + init.artist = DOMString::from_string(metadata.artist.clone()); + init.album = DOMString::from_string(metadata.album.clone()); + let global = self.global(); + Some(MediaMetadata::new(&global.as_window(), &init)) + } else { + None + } + } + + /// https://w3c.github.io/mediasession/#dom-mediasession-metadata + fn SetMetadata(&self, metadata: Option<&MediaMetadata>) { + if let Some(ref metadata) = metadata { + metadata.set_session(self); + } + + let global = self.global(); + let window = global.as_window(); + let _metadata = match metadata { + Some(m) => { + let title = if m.Title().is_empty() { + window.get_url().into_string() + } else { + m.Title().into() + }; + EmbedderMediaMetadata { + title, + artist: m.Artist().into(), + album: m.Album().into(), + } + }, + None => EmbedderMediaMetadata::new(window.get_url().into_string()), + }; + + *self.metadata.borrow_mut() = Some(_metadata.clone()); + + self.send_event(MediaSessionEvent::SetMetadata(_metadata)); + } + + /// https://w3c.github.io/mediasession/#dom-mediasession-playbackstate + fn PlaybackState(&self) -> MediaSessionPlaybackState { + *self.playback_state.borrow() + } + + /// https://w3c.github.io/mediasession/#dom-mediasession-playbackstate + fn SetPlaybackState(&self, state: MediaSessionPlaybackState) { + *self.playback_state.borrow_mut() = state; + } + + /// https://w3c.github.io/mediasession/#update-action-handler-algorithm + fn SetActionHandler( + &self, + action: MediaSessionAction, + handler: Option<Rc<MediaSessionActionHandler>>, + ) { + match handler { + Some(handler) => self + .action_handlers + .borrow_mut() + .insert(action.into(), handler.clone()), + None => self.action_handlers.borrow_mut().remove(&action.into()), + }; + } +} + +impl From<MediaSessionAction> for MediaSessionActionType { + fn from(action: MediaSessionAction) -> MediaSessionActionType { + match action { + MediaSessionAction::Play => MediaSessionActionType::Play, + MediaSessionAction::Pause => MediaSessionActionType::Pause, + MediaSessionAction::Seekbackward => MediaSessionActionType::SeekBackward, + MediaSessionAction::Seekforward => MediaSessionActionType::SeekForward, + MediaSessionAction::Previoustrack => MediaSessionActionType::PreviousTrack, + MediaSessionAction::Nexttrack => MediaSessionActionType::NextTrack, + MediaSessionAction::Skipad => MediaSessionActionType::SkipAd, + MediaSessionAction::Stop => MediaSessionActionType::Stop, + MediaSessionAction::Seekto => MediaSessionActionType::SeekTo, + } + } +} diff --git a/components/script/dom/mod.rs b/components/script/dom/mod.rs index 16d2c6fb686..ef562931bee 100644 --- a/components/script/dom/mod.rs +++ b/components/script/dom/mod.rs @@ -394,11 +394,14 @@ pub mod inputevent; pub mod keyboardevent; pub mod location; pub mod mediadevices; +pub mod mediaelementaudiosourcenode; pub mod mediaerror; pub mod mediafragmentparser; pub mod medialist; +pub mod mediametadata; pub mod mediaquerylist; pub mod mediaquerylistevent; +pub mod mediasession; pub mod mediastream; pub mod mediastreamtrack; pub mod messagechannel; diff --git a/components/script/dom/navigator.rs b/components/script/dom/navigator.rs index 8a0f6a21d98..127883dd956 100644 --- a/components/script/dom/navigator.rs +++ b/components/script/dom/navigator.rs @@ -12,6 +12,7 @@ use crate::dom::bindings::str::DOMString; use crate::dom::bluetooth::Bluetooth; use crate::dom::gamepadlist::GamepadList; use crate::dom::mediadevices::MediaDevices; +use crate::dom::mediasession::MediaSession; use crate::dom::mimetypearray::MimeTypeArray; use crate::dom::navigatorinfo; use crate::dom::permissions::Permissions; @@ -34,6 +35,7 @@ pub struct Navigator { mediadevices: MutNullableDom<MediaDevices>, gamepads: MutNullableDom<GamepadList>, permissions: MutNullableDom<Permissions>, + mediasession: MutNullableDom<MediaSession>, } impl Navigator { @@ -48,6 +50,7 @@ impl Navigator { mediadevices: Default::default(), gamepads: Default::default(), permissions: Default::default(), + mediasession: Default::default(), } } @@ -186,4 +189,20 @@ impl NavigatorMethods for Navigator { self.mediadevices .or_init(|| MediaDevices::new(&self.global())) } + + /// https://w3c.github.io/mediasession/#dom-navigator-mediasession + fn MediaSession(&self) -> DomRoot<MediaSession> { + self.mediasession.or_init(|| { + // There is a single MediaSession instance per Pipeline + // and only one active MediaSession globally. + // + // MediaSession creation can happen in two cases: + // + // - If content gets `navigator.mediaSession` + // - If a media instance (HTMLMediaElement so far) starts playing media. + let global = self.global(); + let window = global.as_window(); + MediaSession::new(window) + }) + } } diff --git a/components/script/dom/promiserejectionevent.rs b/components/script/dom/promiserejectionevent.rs index 4e4a148905a..19bf96b9b12 100644 --- a/components/script/dom/promiserejectionevent.rs +++ b/components/script/dom/promiserejectionevent.rs @@ -5,7 +5,7 @@ use crate::dom::bindings::codegen::Bindings::EventBinding::EventMethods; use crate::dom::bindings::codegen::Bindings::PromiseRejectionEventBinding; use crate::dom::bindings::codegen::Bindings::PromiseRejectionEventBinding::PromiseRejectionEventMethods; -use crate::dom::bindings::error::{Error, Fallible}; +use crate::dom::bindings::error::Fallible; use crate::dom::bindings::inheritance::Castable; use crate::dom::bindings::reflector::reflect_dom_object; use crate::dom::bindings::root::DomRoot; @@ -72,14 +72,7 @@ impl PromiseRejectionEvent { init: RootedTraceableBox<PromiseRejectionEventBinding::PromiseRejectionEventInit>, ) -> Fallible<DomRoot<Self>> { let reason = init.reason.handle(); - let promise = match init.promise.as_ref() { - Some(promise) => promise.clone(), - None => { - return Err(Error::Type( - "required member promise is undefined.".to_string(), - )); - }, - }; + let promise = init.promise.clone(); let bubbles = EventBubbles::from(init.parent.bubbles); let cancelable = EventCancelable::from(init.parent.cancelable); diff --git a/components/script/dom/serviceworkerglobalscope.rs b/components/script/dom/serviceworkerglobalscope.rs index 657f43627ed..cb72a4a25e4 100644 --- a/components/script/dom/serviceworkerglobalscope.rs +++ b/components/script/dom/serviceworkerglobalscope.rs @@ -315,7 +315,7 @@ impl ServiceWorkerGlobalScope { }, }; - let runtime = new_rt_and_cx(); + let runtime = new_rt_and_cx(None); let (devtools_mpsc_chan, devtools_mpsc_port) = unbounded(); ROUTER diff --git a/components/script/dom/webidls/AudioContext.webidl b/components/script/dom/webidls/AudioContext.webidl index 9e5dd6bd556..cd9e18edfa0 100644 --- a/components/script/dom/webidls/AudioContext.webidl +++ b/components/script/dom/webidls/AudioContext.webidl @@ -33,7 +33,7 @@ interface AudioContext : BaseAudioContext { Promise<void> suspend(); Promise<void> close(); - // MediaElementAudioSourceNode createMediaElementSource(HTMLMediaElement mediaElement); + [Throws] MediaElementAudioSourceNode createMediaElementSource(HTMLMediaElement mediaElement); // MediaStreamAudioSourceNode createMediaStreamSource(MediaStream mediaStream); // MediaStreamTrackAudioSourceNode createMediaStreamTrackSource(MediaStreamTrack mediaStreamTrack); // MediaStreamAudioDestinationNode createMediaStreamDestination(); diff --git a/components/script/dom/webidls/MediaElementAudioSourceNode.webidl b/components/script/dom/webidls/MediaElementAudioSourceNode.webidl new file mode 100644 index 00000000000..5afe7775caa --- /dev/null +++ b/components/script/dom/webidls/MediaElementAudioSourceNode.webidl @@ -0,0 +1,17 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ +/* + * The origin of this IDL file is + * https://webaudio.github.io/web-audio-api/#mediaelementaudiosourcenode + */ + +dictionary MediaElementAudioSourceOptions { + required HTMLMediaElement mediaElement; +}; + +[Exposed=Window] +interface MediaElementAudioSourceNode : AudioNode { + [Throws] constructor (AudioContext context, MediaElementAudioSourceOptions options); + [SameObject] readonly attribute HTMLMediaElement mediaElement; +}; diff --git a/components/script/dom/webidls/MediaMetadata.webidl b/components/script/dom/webidls/MediaMetadata.webidl new file mode 100644 index 00000000000..495aeef8e35 --- /dev/null +++ b/components/script/dom/webidls/MediaMetadata.webidl @@ -0,0 +1,30 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ +/* + * The origin of this IDL file is + * https://w3c.github.io/mediasession/#mediametadata + */ + +dictionary MediaImage { + required USVString src; + DOMString sizes = ""; + DOMString type = ""; +}; + +[Exposed=Window] +interface MediaMetadata { + [Throws] constructor(optional MediaMetadataInit init = {}); + attribute DOMString title; + attribute DOMString artist; + attribute DOMString album; + // TODO: https://github.com/servo/servo/issues/10072 + // attribute FrozenArray<MediaImage> artwork; +}; + +dictionary MediaMetadataInit { + DOMString title = ""; + DOMString artist = ""; + DOMString album = ""; + sequence<MediaImage> artwork = []; +}; diff --git a/components/script/dom/webidls/MediaSession.webidl b/components/script/dom/webidls/MediaSession.webidl new file mode 100644 index 00000000000..12b3fe062ba --- /dev/null +++ b/components/script/dom/webidls/MediaSession.webidl @@ -0,0 +1,57 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ +/* + * The origin of this IDL file is + * https://w3c.github.io/mediasession/#mediasession + */ + +[Exposed=Window] +partial interface Navigator { + [SameObject] readonly attribute MediaSession mediaSession; +}; + +enum MediaSessionPlaybackState { + "none", + "paused", + "playing" +}; + +enum MediaSessionAction { + "play", + "pause", + "seekbackward", + "seekforward", + "previoustrack", + "nexttrack", + "skipad", + "stop", + "seekto" +}; + +dictionary MediaSessionActionDetails { + required MediaSessionAction action; +}; + +dictionary MediaSessionSeekActionDetails : MediaSessionActionDetails { + double? seekOffset; +}; + +dictionary MediaSessionSeekToActionDetails : MediaSessionActionDetails { + required double seekTime; + boolean? fastSeek; +}; + +callback MediaSessionActionHandler = void(/*MediaSessionActionDetails details*/); + +[Exposed=Window] +interface MediaSession { + attribute MediaMetadata? metadata; + + attribute MediaSessionPlaybackState playbackState; + + void setActionHandler(MediaSessionAction action, MediaSessionActionHandler? handler); + + //void setPositionState(optional MediaPositionState? state); +}; + diff --git a/components/script/dom/webidls/PromiseRejectionEvent.webidl b/components/script/dom/webidls/PromiseRejectionEvent.webidl index 6ef93b8b1a7..70d11b1ff33 100644 --- a/components/script/dom/webidls/PromiseRejectionEvent.webidl +++ b/components/script/dom/webidls/PromiseRejectionEvent.webidl @@ -6,12 +6,12 @@ [Exposed=(Window,Worker)] interface PromiseRejectionEvent : Event { - [Throws] constructor(DOMString type, optional PromiseRejectionEventInit eventInitDict = {}); + [Throws] constructor(DOMString type, PromiseRejectionEventInit eventInitDict); readonly attribute Promise<any> promise; readonly attribute any reason; }; dictionary PromiseRejectionEventInit : EventInit { - /* required */ Promise<any> promise; + required Promise<any> promise; any reason; }; diff --git a/components/script/dom/worklet.rs b/components/script/dom/worklet.rs index e882566e07f..196864f5527 100644 --- a/components/script/dom/worklet.rs +++ b/components/script/dom/worklet.rs @@ -477,7 +477,7 @@ impl WorkletThread { global_init: init.global_init, global_scopes: HashMap::new(), control_buffer: None, - runtime: new_rt_and_cx(), + runtime: new_rt_and_cx(None), should_gc: false, gc_threshold: MIN_GC_THRESHOLD, }); |