diff options
author | Martin Robinson <mrobinson@igalia.com> | 2024-12-26 04:34:54 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-12-26 03:34:54 +0000 |
commit | 5f927a2c285c779b631ba36520904189ebd5cd4c (patch) | |
tree | fd9326f2f730fad0ced85c9f8765aa866fba95e5 /components/script/messaging.rs | |
parent | 1e95712772284e434ab8f4c028d6cf78d2f63e1c (diff) | |
download | servo-5f927a2c285c779b631ba36520904189ebd5cd4c.tar.gz servo-5f927a2c285c779b631ba36520904189ebd5cd4c.zip |
script: Refactor channels in `ScriptThread` into receivers and senders (#34776)
Create two new data structures in the `script` crate to hold senders and
receiver:
- `ScriptThreadSenders`: holds all outgoing channels from the
`ScriptThread` including a channel to the `ScriptThread` itself. The
ultimate goal with this is to reduce duplication by giving a boxed
version of this this to `Window`s.
- `ScriptThradReceivers`: holds all incoming channels to the
`ScriptThread`. This isn't cloenable like the senders. This is used to
abstract away `recv()` and `try_recv()` methods used to make the
`ScriptThread` event loop easier to read.
In addition:
- The many duplicated `ScriptThread` self-senders for the `TaskManager`
have been removed and, in general, a lot of boilerplate is removed as
well.
- Visibilty of all methods affected by this change is changed to
`pub(crate)` in order to take advantage of dead code detection. Some
dead code produced from macros is removed.
- Some conversion code is refactord into implementations of the `From`
trait.
- The names of channels uses a standard "sender" and "receiver" naming
as well as trying to be descriptive of where they go in `ScriptThread`
as well as `InitialScriptState`
Signed-off-by: Martin Robinson <mrobinson@igalia.com>
Diffstat (limited to 'components/script/messaging.rs')
-rw-r--r-- | components/script/messaging.rs | 398 |
1 files changed, 398 insertions, 0 deletions
diff --git a/components/script/messaging.rs b/components/script/messaging.rs new file mode 100644 index 00000000000..f077f969b19 --- /dev/null +++ b/components/script/messaging.rs @@ -0,0 +1,398 @@ +/* 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<PipelineId> { + 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<Atom>, + painter: Box<dyn Painter>, + }, + /// 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<PipelineId> { + 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<QueuedTask> { + 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<CommonScriptMsg> for Box<dyn ScriptChan + Send> { + fn send(&self, msg: CommonScriptMsg) { + ScriptChan::send(&**self, msg).unwrap(); + } +} + +impl ScriptPort for Receiver<CommonScriptMsg> { + fn recv(&self) -> Result<CommonScriptMsg, ()> { + self.recv().map_err(|_| ()) + } +} + +impl ScriptPort for Receiver<MainThreadScriptMsg> { + fn recv(&self) -> Result<CommonScriptMsg, ()> { + 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<CommonScriptMsg, ()> { + self.recv().map(|(_, msg)| msg).map_err(|_| ()) + } +} + +impl ScriptPort for Receiver<(TrustedWorkerAddress, MainThreadScriptMsg)> { + fn recv(&self) -> Result<CommonScriptMsg, ()> { + 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<CommonScriptMsg, ()> { + 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<CommonScriptMsg>); + +impl ScriptChan for SendableMainThreadScriptChan { + fn send(&self, msg: CommonScriptMsg) -> Result<(), ()> { + self.0.send(msg).map_err(|_| ()) + } + + fn as_boxed(&self) -> Box<dyn ScriptChan + Send> { + 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<MainThreadScriptMsg>); + +impl ScriptChan for MainThreadScriptChan { + fn send(&self, msg: CommonScriptMsg) -> Result<(), ()> { + self.0 + .send(MainThreadScriptMsg::Common(msg)) + .map_err(|_| ()) + } + + fn as_boxed(&self) -> Box<dyn ScriptChan + Send> { + Box::new(MainThreadScriptChan((self.0).clone())) + } +} + +impl OpaqueSender<CommonScriptMsg> for Sender<MainThreadScriptMsg> { + 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<BluetoothRequest>, + + /// A [`Sender`] that sends messages to the `Constellation`. + #[no_trace] + pub constellation_sender: IpcSender<ConstellationControlMsg>, + + /// 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<LayoutMsg>, + + /// The [`Sender`] on which messages can be sent to the `ImageCache`. + #[no_trace] + pub image_cache_sender: Sender<ImageCacheMsg>, + + /// 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<IpcSender<ScriptToDevtoolsControlMsg>>, + + #[no_trace] + pub devtools_client_to_script_thread_sender: IpcSender<DevtoolScriptControlMsg>, + + #[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<ConstellationControlMsg>, + + /// The [`Receiver`] which receives incoming messages from the `ImageCache`. + #[no_trace] + pub image_cache_receiver: Receiver<ImageCacheMsg>, + + /// For receiving commands from an optional devtools server. Will be ignored if + /// no such server exists. + #[no_trace] + pub devtools_server_receiver: Receiver<DevtoolScriptControlMsg>, + + /// Receiver to receive commands from optional WebGPU server. + #[no_trace] + #[cfg(feature = "webgpu")] + pub webgpu_receiver: RefCell<Receiver<WebGPUMsg>>, +} + +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<MainThreadScriptMsg>, + 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<MainThreadScriptMsg>, + ) -> Option<MixedMessage> { + 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 + } +} |