/* 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 std::cell::RefCell; use std::option::Option; use std::result::Result; use base::id::PipelineId; use bluetooth_traits::BluetoothRequest; use crossbeam_channel::{select, Receiver, Sender}; use devtools_traits::{DevtoolScriptControlMsg, ScriptToDevtoolsControlMsg}; use ipc_channel::ipc::IpcSender; use net_traits::image_cache::PendingImageResponse; use profile_traits::mem::{self as profile_mem, OpaqueSender}; use profile_traits::time::{self as profile_time}; use script_traits::{ConstellationControlMsg, LayoutMsg, Painter, ScriptMsg}; use servo_atoms::Atom; use timers::TimerScheduler; #[cfg(feature = "webgpu")] use webgpu::WebGPUMsg; use crate::dom::serviceworker::TrustedServiceWorkerAddress; use crate::dom::worker::TrustedWorkerAddress; use crate::script_runtime::{CommonScriptMsg, ScriptChan, ScriptPort}; use crate::task_queue::{QueuedTask, QueuedTaskConversion, TaskQueue}; use crate::task_source::TaskSourceName; pub(crate) type ImageCacheMsg = (PipelineId, PendingImageResponse); #[derive(Debug)] pub(crate) enum MixedMessage { FromConstellation(ConstellationControlMsg), FromScript(MainThreadScriptMsg), FromDevtools(DevtoolScriptControlMsg), FromImageCache((PipelineId, PendingImageResponse)), #[cfg(feature = "webgpu")] FromWebGPUServer(WebGPUMsg), TimerFired, } impl MixedMessage { pub(crate) fn pipeline_id(&self) -> Option { match self { MixedMessage::FromConstellation(ref inner_msg) => match *inner_msg { ConstellationControlMsg::StopDelayingLoadEventsMode(id) => Some(id), ConstellationControlMsg::NavigationResponse(id, _) => Some(id), ConstellationControlMsg::AttachLayout(ref new_layout_info) => new_layout_info .parent_info .or(Some(new_layout_info.new_pipeline_id)), ConstellationControlMsg::Resize(id, ..) => Some(id), ConstellationControlMsg::ThemeChange(id, ..) => Some(id), ConstellationControlMsg::ResizeInactive(id, ..) => Some(id), ConstellationControlMsg::UnloadDocument(id) => Some(id), ConstellationControlMsg::ExitPipeline(id, ..) => Some(id), ConstellationControlMsg::ExitScriptThread => None, ConstellationControlMsg::SendEvent(id, ..) => Some(id), ConstellationControlMsg::Viewport(id, ..) => Some(id), ConstellationControlMsg::GetTitle(id) => Some(id), ConstellationControlMsg::SetDocumentActivity(id, ..) => Some(id), ConstellationControlMsg::SetThrottled(id, ..) => Some(id), ConstellationControlMsg::SetThrottledInContainingIframe(id, ..) => Some(id), ConstellationControlMsg::NavigateIframe(id, ..) => Some(id), ConstellationControlMsg::PostMessage { target: id, .. } => Some(id), ConstellationControlMsg::UpdatePipelineId(_, _, _, id, _) => Some(id), ConstellationControlMsg::UpdateHistoryState(id, ..) => Some(id), ConstellationControlMsg::RemoveHistoryStates(id, ..) => Some(id), ConstellationControlMsg::FocusIFrame(id, ..) => Some(id), ConstellationControlMsg::WebDriverScriptCommand(id, ..) => Some(id), ConstellationControlMsg::TickAllAnimations(id, ..) => Some(id), ConstellationControlMsg::WebFontLoaded(id, ..) => Some(id), ConstellationControlMsg::DispatchIFrameLoadEvent { target: _, parent: id, child: _, } => Some(id), ConstellationControlMsg::DispatchStorageEvent(id, ..) => Some(id), ConstellationControlMsg::ReportCSSError(id, ..) => Some(id), ConstellationControlMsg::Reload(id, ..) => Some(id), ConstellationControlMsg::PaintMetric(id, ..) => Some(id), ConstellationControlMsg::ExitFullScreen(id, ..) => Some(id), ConstellationControlMsg::MediaSessionAction(..) => None, #[cfg(feature = "webgpu")] ConstellationControlMsg::SetWebGPUPort(..) => None, ConstellationControlMsg::SetScrollStates(id, ..) => Some(id), ConstellationControlMsg::SetEpochPaintTime(id, ..) => Some(id), }, MixedMessage::FromScript(ref inner_msg) => match *inner_msg { MainThreadScriptMsg::Common(CommonScriptMsg::Task(_, _, pipeline_id, _)) => { pipeline_id }, MainThreadScriptMsg::Common(CommonScriptMsg::CollectReports(_)) => None, MainThreadScriptMsg::WorkletLoaded(pipeline_id) => Some(pipeline_id), MainThreadScriptMsg::RegisterPaintWorklet { pipeline_id, .. } => Some(pipeline_id), MainThreadScriptMsg::Inactive => None, MainThreadScriptMsg::WakeUp => None, }, MixedMessage::FromImageCache((pipeline_id, _)) => Some(*pipeline_id), MixedMessage::FromDevtools(_) | MixedMessage::TimerFired => None, #[cfg(feature = "webgpu")] MixedMessage::FromWebGPUServer(..) => None, } } } /// Messages used to control the script event loop. #[derive(Debug)] pub(crate) enum MainThreadScriptMsg { /// Common variants associated with the script messages Common(CommonScriptMsg), /// Notifies the script thread that a new worklet has been loaded, and thus the page should be /// reflowed. WorkletLoaded(PipelineId), /// Notifies the script thread that a new paint worklet has been registered. RegisterPaintWorklet { pipeline_id: PipelineId, name: Atom, properties: Vec, painter: Box, }, /// A task related to a not fully-active document has been throttled. Inactive, /// Wake-up call from the task queue. WakeUp, } impl QueuedTaskConversion for MainThreadScriptMsg { fn task_source_name(&self) -> Option<&TaskSourceName> { let script_msg = match self { MainThreadScriptMsg::Common(script_msg) => script_msg, _ => return None, }; match script_msg { CommonScriptMsg::Task(_category, _boxed, _pipeline_id, task_source) => { Some(task_source) }, _ => None, } } fn pipeline_id(&self) -> Option { let script_msg = match self { MainThreadScriptMsg::Common(script_msg) => script_msg, _ => return None, }; match script_msg { CommonScriptMsg::Task(_category, _boxed, pipeline_id, _task_source) => *pipeline_id, _ => None, } } fn into_queued_task(self) -> Option { let script_msg = match self { MainThreadScriptMsg::Common(script_msg) => script_msg, _ => return None, }; let (category, boxed, pipeline_id, task_source) = match script_msg { CommonScriptMsg::Task(category, boxed, pipeline_id, task_source) => { (category, boxed, pipeline_id, task_source) }, _ => return None, }; Some((None, category, boxed, pipeline_id, task_source)) } fn from_queued_task(queued_task: QueuedTask) -> Self { let (_worker, category, boxed, pipeline_id, task_source) = queued_task; let script_msg = CommonScriptMsg::Task(category, boxed, pipeline_id, task_source); MainThreadScriptMsg::Common(script_msg) } fn inactive_msg() -> Self { MainThreadScriptMsg::Inactive } fn wake_up_msg() -> Self { MainThreadScriptMsg::WakeUp } fn is_wake_up(&self) -> bool { matches!(self, MainThreadScriptMsg::WakeUp) } } impl OpaqueSender for Box { fn send(&self, msg: CommonScriptMsg) { ScriptChan::send(&**self, msg).unwrap(); } } impl ScriptPort for Receiver { fn recv(&self) -> Result { self.recv().map_err(|_| ()) } } impl ScriptPort for Receiver { fn recv(&self) -> Result { match self.recv() { Ok(MainThreadScriptMsg::Common(script_msg)) => Ok(script_msg), Ok(_) => panic!("unexpected main thread event message!"), Err(_) => Err(()), } } } impl ScriptPort for Receiver<(TrustedWorkerAddress, CommonScriptMsg)> { fn recv(&self) -> Result { self.recv().map(|(_, msg)| msg).map_err(|_| ()) } } impl ScriptPort for Receiver<(TrustedWorkerAddress, MainThreadScriptMsg)> { fn recv(&self) -> Result { match self.recv().map(|(_, msg)| msg) { Ok(MainThreadScriptMsg::Common(script_msg)) => Ok(script_msg), Ok(_) => panic!("unexpected main thread event message!"), Err(_) => Err(()), } } } impl ScriptPort for Receiver<(TrustedServiceWorkerAddress, CommonScriptMsg)> { fn recv(&self) -> Result { self.recv().map(|(_, msg)| msg).map_err(|_| ()) } } /// Encapsulates internal communication of shared messages within the script thread. #[derive(Clone, JSTraceable)] pub(crate) struct SendableMainThreadScriptChan(#[no_trace] pub Sender); impl ScriptChan for SendableMainThreadScriptChan { fn send(&self, msg: CommonScriptMsg) -> Result<(), ()> { self.0.send(msg).map_err(|_| ()) } fn as_boxed(&self) -> Box { Box::new(SendableMainThreadScriptChan((self.0).clone())) } } /// Encapsulates internal communication of main thread messages within the script thread. #[derive(Clone, JSTraceable)] pub(crate) struct MainThreadScriptChan(#[no_trace] pub Sender); impl ScriptChan for MainThreadScriptChan { fn send(&self, msg: CommonScriptMsg) -> Result<(), ()> { self.0 .send(MainThreadScriptMsg::Common(msg)) .map_err(|_| ()) } fn as_boxed(&self) -> Box { Box::new(MainThreadScriptChan((self.0).clone())) } } impl OpaqueSender for Sender { fn send(&self, msg: CommonScriptMsg) { self.send(MainThreadScriptMsg::Common(msg)).unwrap() } } #[derive(Clone, JSTraceable)] pub(crate) struct ScriptThreadSenders { /// A channel to hand out to script thread-based entities that need to be able to enqueue /// events in the event queue. pub self_sender: MainThreadScriptChan, /// A handle to the bluetooth thread. #[no_trace] pub bluetooth_sender: IpcSender, /// A [`Sender`] that sends messages to the `Constellation`. #[no_trace] pub constellation_sender: IpcSender, /// A [`Sender`] that sends messages to the `Constellation` associated with /// particular pipelines. #[no_trace] pub pipeline_to_constellation_sender: IpcSender<(PipelineId, ScriptMsg)>, /// A sender for layout to communicate to the constellation. #[no_trace] pub layout_to_constellation_ipc_sender: IpcSender, /// The [`Sender`] on which messages can be sent to the `ImageCache`. #[no_trace] pub image_cache_sender: Sender, /// For providing contact with the time profiler. #[no_trace] pub time_profiler_sender: profile_time::ProfilerChan, /// For providing contact with the memory profiler. #[no_trace] pub memory_profiler_sender: profile_mem::ProfilerChan, /// For providing instructions to an optional devtools server. #[no_trace] pub devtools_server_sender: Option>, #[no_trace] pub devtools_client_to_script_thread_sender: IpcSender, #[no_trace] pub content_process_shutdown_sender: Sender<()>, } #[derive(JSTraceable)] pub(crate) struct ScriptThreadReceivers { /// A [`Receiver`] that receives messages from the constellation. #[no_trace] pub constellation_receiver: Receiver, /// The [`Receiver`] which receives incoming messages from the `ImageCache`. #[no_trace] pub image_cache_receiver: Receiver, /// For receiving commands from an optional devtools server. Will be ignored if no such server /// exists. When devtools are not active this will be [`crossbeam_channel::never()`]. #[no_trace] pub devtools_server_receiver: Receiver, /// Receiver to receive commands from optional WebGPU server. When there is no active /// WebGPU context, this will be [`crossbeam_channel::never()`]. #[no_trace] #[cfg(feature = "webgpu")] pub webgpu_receiver: RefCell>, } impl ScriptThreadReceivers { /// Block until a message is received by any of the receivers of this [`ScriptThreadReceivers`] /// or the given [`TaskQueue`] or [`TimerScheduler`]. Return the first message received. pub(crate) fn recv( &self, task_queue: &TaskQueue, timer_scheduler: &TimerScheduler, ) -> MixedMessage { select! { recv(task_queue.select()) -> msg => { task_queue.take_tasks(msg.unwrap()); let event = task_queue .recv() .expect("Spurious wake-up of the event-loop, task-queue has no tasks available"); MixedMessage::FromScript(event) }, recv(self.constellation_receiver) -> msg => MixedMessage::FromConstellation(msg.unwrap()), recv(self.devtools_server_receiver) -> msg => MixedMessage::FromDevtools(msg.unwrap()), recv(self.image_cache_receiver) -> msg => MixedMessage::FromImageCache(msg.unwrap()), recv(timer_scheduler.wait_channel()) -> _ => MixedMessage::TimerFired, recv({ #[cfg(feature = "webgpu")] { self.webgpu_receiver.borrow() } #[cfg(not(feature = "webgpu"))] { &crossbeam_channel::never::<()>() } }) -> msg => { #[cfg(feature = "webgpu")] { MixedMessage::FromWebGPUServer(msg.unwrap()) } #[cfg(not(feature = "webgpu"))] { unreachable!("This should never be hit when webgpu is disabled"); } } } } /// Try to receive a from any of the receivers of this [`ScriptThreadReceivers`] or the given /// [`TaskQueue`]. Return `None` if no messages are ready to be received. pub(crate) fn try_recv( &self, task_queue: &TaskQueue, ) -> Option { if let Ok(message) = self.constellation_receiver.try_recv() { return MixedMessage::FromConstellation(message).into(); } if let Ok(message) = task_queue.take_tasks_and_recv() { return MixedMessage::FromScript(message).into(); } if let Ok(message) = self.devtools_server_receiver.try_recv() { return MixedMessage::FromDevtools(message).into(); } if let Ok(message) = self.image_cache_receiver.try_recv() { return MixedMessage::FromImageCache(message).into(); } #[cfg(feature = "webgpu")] if let Ok(message) = self.webgpu_receiver.borrow().try_recv() { return MixedMessage::FromWebGPUServer(message).into(); } None } }