script/
messaging.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 core::fmt;
6#[cfg(feature = "webgpu")]
7use std::cell::RefCell;
8use std::option::Option;
9use std::result::Result;
10
11use crossbeam_channel::{Receiver, SendError, Sender, select};
12use devtools_traits::{DevtoolScriptControlMsg, ScriptToDevtoolsControlMsg};
13use embedder_traits::{EmbedderControlId, EmbedderControlResponse, ScriptToEmbedderChan};
14use net_traits::FetchResponseMsg;
15use net_traits::image_cache::ImageCacheResponseMessage;
16use profile_traits::mem::{self as profile_mem, OpaqueSender, ReportsChan};
17use profile_traits::time::{self as profile_time};
18use rustc_hash::FxHashSet;
19use script_traits::{Painter, ScriptThreadMessage};
20use servo_base::generic_channel::{GenericCallback, GenericSender, RoutedReceiver};
21use servo_base::id::{PipelineId, WebViewId};
22#[cfg(feature = "bluetooth")]
23use servo_bluetooth_traits::BluetoothRequest;
24use servo_constellation_traits::ScriptToConstellationMessage;
25use stylo_atoms::Atom;
26use timers::TimerScheduler;
27#[cfg(feature = "webgpu")]
28use webgpu_traits::WebGPUMsg;
29
30use crate::dom::abstractworker::WorkerScriptMsg;
31use crate::dom::bindings::trace::CustomTraceable;
32use crate::dom::csp::Violation;
33use crate::dom::dedicatedworkerglobalscope::DedicatedWorkerScriptMsg;
34use crate::dom::serviceworkerglobalscope::ServiceWorkerScriptMsg;
35use crate::dom::worker::TrustedWorkerAddress;
36use crate::script_runtime::ScriptThreadEventCategory;
37use crate::task::TaskBox;
38use crate::task_queue::{QueuedTask, QueuedTaskConversion, TaskQueue};
39use crate::task_source::TaskSourceName;
40
41#[expect(clippy::large_enum_variant)]
42#[derive(Debug)]
43pub(crate) enum MixedMessage {
44    FromConstellation(ScriptThreadMessage),
45    FromScript(MainThreadScriptMsg),
46    FromDevtools(DevtoolScriptControlMsg),
47    FromImageCache(ImageCacheResponseMessage),
48    #[cfg(feature = "webgpu")]
49    FromWebGPUServer(WebGPUMsg),
50    TimerFired,
51}
52
53impl MixedMessage {
54    pub(crate) fn pipeline_id(&self) -> Option<PipelineId> {
55        match self {
56            MixedMessage::FromConstellation(inner_msg) => match inner_msg {
57                ScriptThreadMessage::StopDelayingLoadEventsMode(id) => Some(*id),
58                ScriptThreadMessage::SpawnPipeline(new_pipeline_info) => new_pipeline_info
59                    .parent_info
60                    .or(Some(new_pipeline_info.new_pipeline_id)),
61                ScriptThreadMessage::Resize(id, ..) => Some(*id),
62                ScriptThreadMessage::ThemeChange(id, ..) => Some(*id),
63                ScriptThreadMessage::ResizeInactive(id, ..) => Some(*id),
64                ScriptThreadMessage::UnloadDocument(id) => Some(*id),
65                ScriptThreadMessage::ExitPipeline(_webview_id, id, ..) => Some(*id),
66                ScriptThreadMessage::ExitScriptThread => None,
67                ScriptThreadMessage::SendInputEvent(_, id, _) => Some(*id),
68                ScriptThreadMessage::RefreshCursor(id, ..) => Some(*id),
69                ScriptThreadMessage::GetTitle(id) => Some(*id),
70                ScriptThreadMessage::GetDocumentOrigin(id, _) => Some(*id),
71                ScriptThreadMessage::SetDocumentActivity(id, ..) => Some(*id),
72                ScriptThreadMessage::SetThrottled(_, id, ..) => Some(*id),
73                ScriptThreadMessage::SetThrottledInContainingIframe(_, id, ..) => Some(*id),
74                ScriptThreadMessage::NavigateIframe(id, ..) => Some(*id),
75                ScriptThreadMessage::PostMessage { target: id, .. } => Some(*id),
76                ScriptThreadMessage::UpdatePipelineId(_, _, _, id, _) => Some(*id),
77                ScriptThreadMessage::UpdateHistoryState(id, ..) => Some(*id),
78                ScriptThreadMessage::RemoveHistoryStates(id, ..) => Some(*id),
79                ScriptThreadMessage::FocusIFrame(id, ..) => Some(*id),
80                ScriptThreadMessage::FocusDocument(id, ..) => Some(*id),
81                ScriptThreadMessage::Unfocus(id, ..) => Some(*id),
82                ScriptThreadMessage::WebDriverScriptCommand(id, ..) => Some(*id),
83                ScriptThreadMessage::TickAllAnimations(..) => None,
84                ScriptThreadMessage::WebFontLoaded(id) => Some(*id),
85                ScriptThreadMessage::DispatchIFrameLoadEvent {
86                    target: _,
87                    parent: id,
88                    child: _,
89                } => Some(*id),
90                ScriptThreadMessage::DispatchStorageEvent(id, ..) => Some(*id),
91                ScriptThreadMessage::ReportCSSError(id, ..) => Some(*id),
92                ScriptThreadMessage::Reload(id, ..) => Some(*id),
93                ScriptThreadMessage::PaintMetric(id, ..) => Some(*id),
94                ScriptThreadMessage::ExitFullScreen(id, ..) => Some(*id),
95                ScriptThreadMessage::MediaSessionAction(..) => None,
96                #[cfg(feature = "webgpu")]
97                ScriptThreadMessage::SetWebGPUPort(..) => None,
98                ScriptThreadMessage::SetScrollStates(id, ..) => Some(*id),
99                ScriptThreadMessage::EvaluateJavaScript(_, id, _, _) => Some(*id),
100                ScriptThreadMessage::SendImageKeysBatch(..) => None,
101                ScriptThreadMessage::PreferencesUpdated(..) => None,
102                ScriptThreadMessage::NoLongerWaitingOnAsychronousImageUpdates(_) => None,
103                ScriptThreadMessage::ForwardKeyboardScroll(id, _) => Some(*id),
104                ScriptThreadMessage::RequestScreenshotReadiness(_, id) => Some(*id),
105                ScriptThreadMessage::EmbedderControlResponse(id, _) => Some(id.pipeline_id),
106                ScriptThreadMessage::SetUserContents(..) => None,
107                ScriptThreadMessage::DestroyUserContentManager(..) => None,
108                ScriptThreadMessage::AccessibilityTreeUpdate(..) => None,
109                ScriptThreadMessage::UpdatePinchZoomInfos(id, _) => Some(*id),
110                ScriptThreadMessage::SetAccessibilityActive(..) => None,
111                ScriptThreadMessage::TriggerGarbageCollection => None,
112            },
113            MixedMessage::FromScript(inner_msg) => match inner_msg {
114                MainThreadScriptMsg::Common(CommonScriptMsg::Task(_, _, pipeline_id, _)) => {
115                    *pipeline_id
116                },
117                MainThreadScriptMsg::Common(CommonScriptMsg::CollectReports(_)) => None,
118                MainThreadScriptMsg::Common(CommonScriptMsg::ReportCspViolations(
119                    pipeline_id,
120                    _,
121                )) => Some(*pipeline_id),
122                MainThreadScriptMsg::NavigationResponse { pipeline_id, .. } => Some(*pipeline_id),
123                MainThreadScriptMsg::WorkletLoaded(pipeline_id) => Some(*pipeline_id),
124                MainThreadScriptMsg::RegisterPaintWorklet { pipeline_id, .. } => Some(*pipeline_id),
125                MainThreadScriptMsg::Inactive => None,
126                MainThreadScriptMsg::WakeUp => None,
127                MainThreadScriptMsg::ForwardEmbedderControlResponseFromFileManager(
128                    control_id,
129                    ..,
130                ) => Some(control_id.pipeline_id),
131            },
132            MixedMessage::FromImageCache(response) => match response {
133                ImageCacheResponseMessage::NotifyPendingImageLoadStatus(response) => {
134                    Some(response.pipeline_id)
135                },
136                ImageCacheResponseMessage::VectorImageRasterizationComplete(response) => {
137                    Some(response.pipeline_id)
138                },
139            },
140            MixedMessage::FromDevtools(_) | MixedMessage::TimerFired => None,
141            #[cfg(feature = "webgpu")]
142            MixedMessage::FromWebGPUServer(..) => None,
143        }
144    }
145}
146
147/// Messages used to control the script event loop.
148#[derive(Debug)]
149pub(crate) enum MainThreadScriptMsg {
150    /// Common variants associated with the script messages
151    Common(CommonScriptMsg),
152    /// Notifies the script thread that a new worklet has been loaded, and thus the page should be
153    /// reflowed.
154    WorkletLoaded(PipelineId),
155    NavigationResponse {
156        pipeline_id: PipelineId,
157        message: Box<FetchResponseMsg>,
158    },
159    /// Notifies the script thread that a new paint worklet has been registered.
160    RegisterPaintWorklet {
161        pipeline_id: PipelineId,
162        name: Atom,
163        properties: Vec<Atom>,
164        painter: Box<dyn Painter>,
165    },
166    /// A task related to a not fully-active document has been throttled.
167    Inactive,
168    /// Wake-up call from the task queue.
169    WakeUp,
170    /// The `FileManagerThread` has finished selecting files is forwarding the response to
171    /// the main thread of this `ScriptThread`.
172    ForwardEmbedderControlResponseFromFileManager(EmbedderControlId, EmbedderControlResponse),
173}
174
175/// Common messages used to control the event loops in both the script and the worker
176pub(crate) enum CommonScriptMsg {
177    /// Requests that the script thread measure its memory usage. The results are sent back via the
178    /// supplied channel.
179    CollectReports(ReportsChan),
180    /// Generic message that encapsulates event handling.
181    Task(
182        ScriptThreadEventCategory,
183        Box<dyn TaskBox>,
184        Option<PipelineId>,
185        TaskSourceName,
186    ),
187    /// Report CSP violations in the script
188    ReportCspViolations(PipelineId, Vec<Violation>),
189}
190
191impl fmt::Debug for CommonScriptMsg {
192    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
193        match *self {
194            CommonScriptMsg::CollectReports(_) => write!(f, "CollectReports(...)"),
195            CommonScriptMsg::Task(ref category, ref task, _, _) => {
196                f.debug_tuple("Task").field(category).field(task).finish()
197            },
198            CommonScriptMsg::ReportCspViolations(..) => write!(f, "ReportCspViolations(...)"),
199        }
200    }
201}
202
203/// A wrapper around various types of `Sender`s that send messages back to the event loop
204/// of a script context event loop. This will either target the main `ScriptThread` event
205/// loop or that of a worker.
206#[derive(Clone, JSTraceable, MallocSizeOf)]
207pub(crate) enum ScriptEventLoopSender {
208    /// A sender that sends to the main `ScriptThread` event loop.
209    MainThread(Sender<MainThreadScriptMsg>),
210    /// A sender that sends to a `ServiceWorker` event loop.
211    ServiceWorker(Sender<ServiceWorkerScriptMsg>),
212    /// A sender that sends to a dedicated worker (such as a generic Web Worker) event loop.
213    /// Note that this sender keeps the main thread Worker DOM object alive as long as it or
214    /// or any message it sends is not dropped.
215    DedicatedWorker {
216        sender: Sender<DedicatedWorkerScriptMsg>,
217        main_thread_worker: TrustedWorkerAddress,
218    },
219}
220
221impl ScriptEventLoopSender {
222    /// Send a message to the event loop, which might be a main thread event loop or a worker event loop.
223    pub(crate) fn send(&self, message: CommonScriptMsg) -> Result<(), SendError<()>> {
224        match self {
225            Self::MainThread(sender) => sender
226                .send(MainThreadScriptMsg::Common(message))
227                .map_err(|_| SendError(())),
228            Self::ServiceWorker(sender) => sender
229                .send(ServiceWorkerScriptMsg::CommonWorker(
230                    WorkerScriptMsg::Common(message),
231                ))
232                .map_err(|_| SendError(())),
233            Self::DedicatedWorker {
234                sender,
235                main_thread_worker,
236            } => {
237                let common_message = WorkerScriptMsg::Common(message);
238                sender
239                    .send(DedicatedWorkerScriptMsg::CommonWorker(
240                        main_thread_worker.clone(),
241                        common_message,
242                    ))
243                    .map_err(|_| SendError(()))
244            },
245        }
246    }
247}
248
249/// A wrapper around various types of `Receiver`s that receive event loop messages. Used for
250/// synchronous DOM APIs that need to abstract over multiple kinds of event loops (worker/main
251/// thread) with different Receiver interfaces.
252pub(crate) enum ScriptEventLoopReceiver {
253    /// A receiver that receives messages to the main `ScriptThread` event loop.
254    MainThread(Receiver<MainThreadScriptMsg>),
255    /// A receiver that receives messages to dedicated workers (such as a generic Web Worker) event loop.
256    DedicatedWorker(Receiver<DedicatedWorkerScriptMsg>),
257}
258
259impl ScriptEventLoopReceiver {
260    pub(crate) fn recv(&self) -> Result<CommonScriptMsg, ()> {
261        match self {
262            Self::MainThread(receiver) => match receiver.recv() {
263                Ok(MainThreadScriptMsg::Common(script_msg)) => Ok(script_msg),
264                Ok(_) => panic!("unexpected main thread event message!"),
265                Err(_) => Err(()),
266            },
267            Self::DedicatedWorker(receiver) => match receiver.recv() {
268                Ok(DedicatedWorkerScriptMsg::CommonWorker(_, WorkerScriptMsg::Common(message))) => {
269                    Ok(message)
270                },
271                Ok(_) => panic!("unexpected worker event message!"),
272                Err(_) => Err(()),
273            },
274        }
275    }
276}
277
278impl QueuedTaskConversion for MainThreadScriptMsg {
279    fn task_source_name(&self) -> Option<&TaskSourceName> {
280        let script_msg = match self {
281            MainThreadScriptMsg::Common(script_msg) => script_msg,
282            _ => return None,
283        };
284        match script_msg {
285            CommonScriptMsg::Task(_category, _boxed, _pipeline_id, task_source) => {
286                Some(task_source)
287            },
288            _ => None,
289        }
290    }
291
292    fn pipeline_id(&self) -> Option<PipelineId> {
293        let script_msg = match self {
294            MainThreadScriptMsg::Common(script_msg) => script_msg,
295            _ => return None,
296        };
297        match script_msg {
298            CommonScriptMsg::Task(_category, _boxed, pipeline_id, _task_source) => *pipeline_id,
299            _ => None,
300        }
301    }
302
303    fn into_queued_task(self) -> Option<QueuedTask> {
304        let script_msg = match self {
305            MainThreadScriptMsg::Common(script_msg) => script_msg,
306            _ => return None,
307        };
308        let (event_category, task, pipeline_id, task_source) = match script_msg {
309            CommonScriptMsg::Task(category, boxed, pipeline_id, task_source) => {
310                (category, boxed, pipeline_id, task_source)
311            },
312            _ => return None,
313        };
314        Some(QueuedTask {
315            worker: None,
316            event_category,
317            task,
318            pipeline_id,
319            task_source,
320        })
321    }
322
323    fn from_queued_task(queued_task: QueuedTask) -> Self {
324        let script_msg = CommonScriptMsg::Task(
325            queued_task.event_category,
326            queued_task.task,
327            queued_task.pipeline_id,
328            queued_task.task_source,
329        );
330        MainThreadScriptMsg::Common(script_msg)
331    }
332
333    fn inactive_msg() -> Self {
334        MainThreadScriptMsg::Inactive
335    }
336
337    fn wake_up_msg() -> Self {
338        MainThreadScriptMsg::WakeUp
339    }
340
341    fn is_wake_up(&self) -> bool {
342        matches!(self, MainThreadScriptMsg::WakeUp)
343    }
344}
345
346impl OpaqueSender<CommonScriptMsg> for ScriptEventLoopSender {
347    fn send(&self, message: CommonScriptMsg) {
348        self.send(message).unwrap()
349    }
350}
351
352#[derive(Clone, JSTraceable)]
353pub(crate) struct ScriptThreadSenders {
354    /// A channel to hand out to script thread-based entities that need to be able to enqueue
355    /// events in the event queue.
356    pub(crate) self_sender: Sender<MainThreadScriptMsg>,
357
358    /// A handle to the bluetooth thread.
359    #[no_trace]
360    #[cfg(feature = "bluetooth")]
361    pub(crate) bluetooth_sender: GenericSender<BluetoothRequest>,
362
363    /// A [`Sender`] that sends messages to the `ScriptThread`.
364    #[no_trace]
365    pub(crate) constellation_sender: GenericSender<ScriptThreadMessage>,
366
367    /// A [`Sender`] that sends messages to the `Constellation` associated with
368    /// particular pipelines.
369    #[no_trace]
370    pub(crate) pipeline_to_constellation_sender:
371        GenericSender<(WebViewId, PipelineId, ScriptToConstellationMessage)>,
372
373    /// A channel to send messages to the Embedder.
374    #[no_trace]
375    pub(crate) pipeline_to_embedder_sender: ScriptToEmbedderChan,
376
377    /// The shared [`Sender`] which is sent to the `ImageCache` when requesting an image.
378    /// Messages on this channel are sent to [`ScriptThreadReceivers::image_cache_receiver`].
379    #[no_trace]
380    pub(crate) image_cache_sender: Sender<ImageCacheResponseMessage>,
381
382    /// For providing contact with the time profiler.
383    #[no_trace]
384    pub(crate) time_profiler_sender: profile_time::ProfilerChan,
385
386    /// For providing contact with the memory profiler.
387    #[no_trace]
388    pub(crate) memory_profiler_sender: profile_mem::ProfilerChan,
389
390    /// For providing instructions to an optional devtools server.
391    #[no_trace]
392    pub(crate) devtools_server_sender: Option<GenericCallback<ScriptToDevtoolsControlMsg>>,
393
394    #[no_trace]
395    pub(crate) devtools_client_to_script_thread_sender: GenericSender<DevtoolScriptControlMsg>,
396}
397
398#[derive(JSTraceable)]
399pub(crate) struct ScriptThreadReceivers {
400    /// A [`Receiver`] that receives messages from the constellation.
401    #[no_trace]
402    pub(crate) constellation_receiver: RoutedReceiver<ScriptThreadMessage>,
403
404    /// The [`Receiver`] which receives incoming messages from the `ImageCache`.
405    #[no_trace]
406    pub(crate) image_cache_receiver: Receiver<ImageCacheResponseMessage>,
407
408    /// For receiving commands from an optional devtools server. Will be ignored if no such server
409    /// exists. When devtools are not active this will be [`crossbeam_channel::never()`].
410    #[no_trace]
411    pub(crate) devtools_server_receiver: RoutedReceiver<DevtoolScriptControlMsg>,
412
413    /// Receiver to receive commands from optional WebGPU server. When there is no active
414    /// WebGPU context, this will be [`crossbeam_channel::never()`].
415    #[no_trace]
416    #[cfg(feature = "webgpu")]
417    pub(crate) webgpu_receiver: RefCell<RoutedReceiver<WebGPUMsg>>,
418}
419
420impl ScriptThreadReceivers {
421    /// Block until a message is received by any of the receivers of this [`ScriptThreadReceivers`]
422    /// or the given [`TaskQueue`] or [`TimerScheduler`]. Return the first message received.
423    pub(crate) fn recv(
424        &self,
425        task_queue: &TaskQueue<MainThreadScriptMsg>,
426        timer_scheduler: &TimerScheduler,
427        fully_active: &FxHashSet<PipelineId>,
428    ) -> MixedMessage {
429        select! {
430            recv(task_queue.select()) -> msg => {
431                task_queue.take_tasks(msg.unwrap(), fully_active);
432                let event = task_queue
433                    .recv()
434                    .expect("Spurious wake-up of the event-loop, task-queue has no tasks available");
435                MixedMessage::FromScript(event)
436            },
437            recv(self.constellation_receiver) -> msg => MixedMessage::FromConstellation(msg.unwrap().unwrap()),
438            recv(self.devtools_server_receiver) -> msg => MixedMessage::FromDevtools(msg.unwrap().unwrap()),
439            recv(self.image_cache_receiver) -> msg => MixedMessage::FromImageCache(msg.unwrap()),
440            recv(timer_scheduler.wait_channel()) -> _ => MixedMessage::TimerFired,
441            recv({
442                #[cfg(feature = "webgpu")]
443                {
444                    self.webgpu_receiver.borrow()
445                }
446                #[cfg(not(feature = "webgpu"))]
447                {
448                    crossbeam_channel::never::<()>()
449                }
450            }) -> msg => {
451                #[cfg(feature = "webgpu")]
452                {
453                    MixedMessage::FromWebGPUServer(msg.unwrap().unwrap())
454                }
455                #[cfg(not(feature = "webgpu"))]
456                {
457                    unreachable!("This should never be hit when webgpu is disabled ({msg:?})");
458                }
459            }
460        }
461    }
462
463    /// Try to receive a from any of the receivers of this [`ScriptThreadReceivers`] or the given
464    /// [`TaskQueue`]. Return `None` if no messages are ready to be received.
465    pub(crate) fn try_recv(
466        &self,
467        task_queue: &TaskQueue<MainThreadScriptMsg>,
468        fully_active: &FxHashSet<PipelineId>,
469    ) -> Option<MixedMessage> {
470        if let Ok(message) = self.constellation_receiver.try_recv() {
471            let message = message
472                .inspect_err(|e| {
473                    log::warn!(
474                        "ScriptThreadReceivers IPC error on constellation_receiver: {:?}",
475                        e
476                    );
477                })
478                .ok()?;
479            return MixedMessage::FromConstellation(message).into();
480        }
481        if let Ok(message) = task_queue.take_tasks_and_recv(fully_active) {
482            return MixedMessage::FromScript(message).into();
483        }
484        if let Ok(message) = self.devtools_server_receiver.try_recv() {
485            return MixedMessage::FromDevtools(message.unwrap()).into();
486        }
487        if let Ok(message) = self.image_cache_receiver.try_recv() {
488            return MixedMessage::FromImageCache(message).into();
489        }
490        #[cfg(feature = "webgpu")]
491        if let Ok(message) = self.webgpu_receiver.borrow().try_recv() {
492            return MixedMessage::FromWebGPUServer(message.unwrap()).into();
493        }
494        None
495    }
496}