1use core::fmt;
6#[cfg(feature = "webgpu")]
7use std::cell::RefCell;
8use std::option::Option;
9use std::result::Result;
10
11use crossbeam_channel::{Receiver, Select, SelectedOperation, SendError, Sender};
12#[cfg(feature = "devtools")]
13use devtools_traits::{DevtoolScriptControlMsg, ScriptToDevtoolsControlMsg};
14use embedder_traits::{EmbedderControlId, EmbedderControlResponse, ScriptToEmbedderChan};
15use net_traits::FetchResponseMsg;
16use net_traits::image_cache::ImageCacheResponseMessage;
17use profile_traits::mem::{self as profile_mem, OpaqueSender, ReportsChan};
18use profile_traits::time::{self as profile_time};
19use rustc_hash::FxHashSet;
20use script_traits::{Painter, ScriptThreadMessage};
21use servo_base::generic_channel::{GenericCallback, GenericSender, RoutedReceiver};
22use servo_base::id::{PipelineId, WebViewId};
23#[cfg(feature = "bluetooth")]
24use servo_bluetooth_traits::BluetoothRequest;
25use servo_constellation_traits::ScriptToConstellationMessage;
26use stylo_atoms::Atom;
27use timers::TimerScheduler;
28#[cfg(feature = "webgpu")]
29use webgpu_traits::WebGPUMsg;
30
31use crate::dom::abstractworker::WorkerScriptMsg;
32use crate::dom::bindings::trace::CustomTraceable;
33use crate::dom::csp::Violation;
34use crate::dom::dedicatedworkerglobalscope::DedicatedWorkerScriptMsg;
35use crate::dom::serviceworkerglobalscope::ServiceWorkerScriptMsg;
36use crate::dom::sharedworkerglobalscope::SharedWorkerScriptMsg;
37use crate::dom::worker::TrustedWorkerAddress;
38use crate::dom::{WorkletControl, WorkletExecutor};
39use crate::runtime::script_runtime::ScriptThreadEventCategory;
40use crate::tasks::task::TaskBox;
41use crate::tasks::task_queue::{QueuedTask, QueuedTaskConversion, TaskQueue};
42use crate::tasks::task_source::TaskSourceName;
43
44#[expect(clippy::large_enum_variant)]
45#[derive(Debug)]
46pub(crate) enum MixedMessage {
48 FromConstellation(ScriptThreadMessage),
49 FromScript(MainThreadScriptMsg),
50 #[cfg(feature = "devtools")]
51 FromDevtools(Box<DevtoolScriptControlMsg>),
52 FromImageCache(ImageCacheResponseMessage),
53 #[cfg(feature = "webgpu")]
54 FromWebGPUServer(WebGPUMsg),
55 TimerFired,
56}
57
58impl MixedMessage {
59 pub(crate) fn pipeline_id(&self) -> Option<PipelineId> {
60 match self {
61 MixedMessage::FromConstellation(inner_msg) => match inner_msg {
62 ScriptThreadMessage::StopDelayingLoadEventsMode(id) => Some(*id),
63 ScriptThreadMessage::SpawnPipeline(new_pipeline_info) => new_pipeline_info
64 .parent_info
65 .or(Some(new_pipeline_info.new_pipeline_id)),
66 ScriptThreadMessage::Resize(id, ..) => Some(*id),
67 ScriptThreadMessage::UpdateWebViewState(..) => None,
68 ScriptThreadMessage::ResizeInactive(id, ..) => Some(*id),
69 ScriptThreadMessage::UnloadDocument(id) => Some(*id),
70 ScriptThreadMessage::ExitPipeline(_webview_id, id, ..) => Some(*id),
71 ScriptThreadMessage::ExitScriptThread => None,
72 ScriptThreadMessage::SendInputEvent(_, id, _) => Some(*id),
73 ScriptThreadMessage::RefreshCursor(id, ..) => Some(*id),
74 ScriptThreadMessage::GetTitle(id) => Some(*id),
75 ScriptThreadMessage::GetDocumentOrigin(id, _) => Some(*id),
76 ScriptThreadMessage::GetDocumentOriginDetails(id, _) => Some(*id),
77 ScriptThreadMessage::SetDocumentActivity(id, ..) => Some(*id),
78 ScriptThreadMessage::SetThrottled(id, ..) => Some(*id),
79 ScriptThreadMessage::NavigateIframe(id, ..) => Some(*id),
80 ScriptThreadMessage::PostMessage { target: id, .. } => Some(*id),
81 ScriptThreadMessage::UpdatePipelineId(_, _, _, id, _) => Some(*id),
82 ScriptThreadMessage::UpdateHistoryState(id, ..) => Some(*id),
83 ScriptThreadMessage::RemoveHistoryStates(id, ..) => Some(*id),
84
85 ScriptThreadMessage::FocusDocumentAsPartOfFocusingSteps(id, ..) => Some(*id),
86 ScriptThreadMessage::UnfocusDocumentAsPartOfFocusingSteps(id, ..) => Some(*id),
87 ScriptThreadMessage::FocusDocument(id, ..) => Some(*id),
88 ScriptThreadMessage::WebDriverScriptCommand(id, ..) => Some(*id),
89 ScriptThreadMessage::TickAllAnimations(..) => None,
90 ScriptThreadMessage::WebFontLoadFinished(id, ..) => Some(*id),
91 ScriptThreadMessage::DispatchIFrameLoadEvent {
92 target: _,
93 parent: id,
94 child: _,
95 } => Some(*id),
96 ScriptThreadMessage::DispatchStorageEvent(id, ..) => Some(*id),
97 #[cfg(feature = "devtools")]
98 ScriptThreadMessage::ReportCSSError(id, ..) => Some(*id),
99 ScriptThreadMessage::Reload(id, ..) => Some(*id),
100 ScriptThreadMessage::PaintMetric(id, ..) => Some(*id),
101 ScriptThreadMessage::ExitFullScreen(id, ..) => Some(*id),
102 ScriptThreadMessage::MediaSessionAction(..) => None,
103 #[cfg(feature = "webgpu")]
104 ScriptThreadMessage::SetWebGPUPort(..) => None,
105 ScriptThreadMessage::SetScrollStates(id, ..) => Some(*id),
106 ScriptThreadMessage::EvaluateJavaScript(_, id, _, _) => Some(*id),
107 ScriptThreadMessage::SendImageKeysBatch(..) => None,
108 ScriptThreadMessage::PreferencesUpdated(..) => None,
109 ScriptThreadMessage::NoLongerWaitingOnAsychronousImageUpdates(_) => None,
110 ScriptThreadMessage::ForwardKeyboardScroll(id, _) => Some(*id),
111 ScriptThreadMessage::RequestScreenshotReadiness(_, id) => Some(*id),
112 ScriptThreadMessage::EmbedderControlResponse(id, _) => Some(id.pipeline_id),
113 ScriptThreadMessage::SetUserContents(..) => None,
114 ScriptThreadMessage::DestroyUserContentManager(..) => None,
115 ScriptThreadMessage::UpdatePinchZoomInfos(id, _) => Some(*id),
116 ScriptThreadMessage::SetAccessibilityActive(..) => None,
117 ScriptThreadMessage::ForwardAccessibilityAction(id, _) => Some(*id),
118 ScriptThreadMessage::TriggerGarbageCollection => None,
119 },
120 MixedMessage::FromScript(inner_msg) => match inner_msg {
121 MainThreadScriptMsg::Common(CommonScriptMsg::Task(_, _, pipeline_id, _)) => {
122 *pipeline_id
123 },
124 MainThreadScriptMsg::Common(CommonScriptMsg::CollectReports(_)) => None,
125 MainThreadScriptMsg::Common(CommonScriptMsg::ReportCspViolations(
126 pipeline_id,
127 _,
128 )) => Some(*pipeline_id),
129 MainThreadScriptMsg::NavigationResponse { pipeline_id, .. } => Some(*pipeline_id),
130 MainThreadScriptMsg::WorkletLoaded(pipeline_id) => Some(*pipeline_id),
131 MainThreadScriptMsg::RegisterPaintWorklet { pipeline_id, .. } => Some(*pipeline_id),
132 MainThreadScriptMsg::Inactive => None,
133 MainThreadScriptMsg::WakeUp => None,
134 MainThreadScriptMsg::ForwardEmbedderControlResponseFromFileManager(
135 control_id,
136 ..,
137 ) => Some(control_id.pipeline_id),
138 },
139 MixedMessage::FromImageCache(response) => match response {
140 ImageCacheResponseMessage::NotifyPendingImageLoadStatus(response) => {
141 Some(response.pipeline_id)
142 },
143 ImageCacheResponseMessage::VectorImageRasterizationComplete(response) => {
144 Some(response.pipeline_id)
145 },
146 },
147 #[cfg(feature = "devtools")]
148 MixedMessage::FromDevtools(_) => None,
149 MixedMessage::TimerFired => None,
150 #[cfg(feature = "webgpu")]
151 MixedMessage::FromWebGPUServer(..) => None,
152 }
153 }
154}
155
156#[derive(Debug)]
158pub(crate) enum MainThreadScriptMsg {
159 Common(CommonScriptMsg),
161 WorkletLoaded(PipelineId),
164 NavigationResponse {
165 pipeline_id: PipelineId,
166 message: Box<FetchResponseMsg>,
167 },
168 RegisterPaintWorklet {
170 pipeline_id: PipelineId,
171 name: Atom,
172 properties: Vec<Atom>,
173 painter: Box<dyn Painter>,
174 },
175 Inactive,
177 WakeUp,
179 ForwardEmbedderControlResponseFromFileManager(EmbedderControlId, EmbedderControlResponse),
182}
183
184pub(crate) enum CommonScriptMsg {
187 CollectReports(ReportsChan),
190 Task(
192 ScriptThreadEventCategory,
193 Box<dyn TaskBox>,
194 Option<PipelineId>,
195 TaskSourceName,
196 ),
197 ReportCspViolations(PipelineId, Vec<Violation>),
199}
200
201impl fmt::Debug for CommonScriptMsg {
202 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
203 match *self {
204 CommonScriptMsg::CollectReports(_) => write!(f, "CollectReports(...)"),
205 CommonScriptMsg::Task(ref category, ref task, _, _) => {
206 f.debug_tuple("Task").field(category).field(task).finish()
207 },
208 CommonScriptMsg::ReportCspViolations(..) => write!(f, "ReportCspViolations(...)"),
209 }
210 }
211}
212
213#[derive(Clone, JSTraceable, MallocSizeOf)]
217pub(crate) enum ScriptEventLoopSender {
218 MainThread(Sender<MainThreadScriptMsg>),
220 SharedWorker(Sender<SharedWorkerScriptMsg>),
222 ServiceWorker(Sender<ServiceWorkerScriptMsg>),
224 Worklet(WorkletExecutor),
226 DedicatedWorker {
230 sender: Sender<DedicatedWorkerScriptMsg>,
231 main_thread_worker: TrustedWorkerAddress,
232 },
233}
234
235impl ScriptEventLoopSender {
236 pub(crate) fn send(&self, message: CommonScriptMsg) -> Result<(), SendError<()>> {
238 match self {
239 Self::MainThread(sender) => sender
240 .send(MainThreadScriptMsg::Common(message))
241 .map_err(|_| SendError(())),
242 Self::SharedWorker(sender) => sender
243 .send(SharedWorkerScriptMsg::CommonWorker(
244 WorkerScriptMsg::Common(message),
245 ))
246 .map_err(|_| SendError(())),
247 Self::ServiceWorker(sender) => sender
248 .send(ServiceWorkerScriptMsg::CommonWorker(
249 WorkerScriptMsg::Common(message),
250 ))
251 .map_err(|_| SendError(())),
252 Self::DedicatedWorker {
253 sender,
254 main_thread_worker,
255 } => {
256 let common_message = WorkerScriptMsg::Common(message);
257 sender
258 .send(DedicatedWorkerScriptMsg::CommonWorker(
259 main_thread_worker.clone(),
260 common_message,
261 ))
262 .map_err(|_| SendError(()))
263 },
264 Self::Worklet(executor) => {
265 executor.send_control_message(WorkletControl::Common(message))
266 },
267 }
268 }
269}
270
271pub(crate) enum ScriptEventLoopReceiver {
275 MainThread(Receiver<MainThreadScriptMsg>),
277 SharedWorker(Receiver<SharedWorkerScriptMsg>),
279 DedicatedWorker(Receiver<DedicatedWorkerScriptMsg>),
281}
282
283impl ScriptEventLoopReceiver {
284 pub(crate) fn recv(&self) -> Result<CommonScriptMsg, ()> {
285 match self {
286 Self::MainThread(receiver) => match receiver.recv() {
287 Ok(MainThreadScriptMsg::Common(script_msg)) => Ok(script_msg),
288 Ok(_) => panic!("unexpected main thread event message!"),
289 Err(_) => Err(()),
290 },
291 Self::SharedWorker(receiver) => match receiver.recv() {
292 Ok(SharedWorkerScriptMsg::CommonWorker(WorkerScriptMsg::Common(message))) => {
293 Ok(message)
294 },
295 Ok(_) => panic!("unexpected shared worker event message!"),
296 Err(_) => Err(()),
297 },
298 Self::DedicatedWorker(receiver) => match receiver.recv() {
299 Ok(DedicatedWorkerScriptMsg::CommonWorker(_, WorkerScriptMsg::Common(message))) => {
300 Ok(message)
301 },
302 Ok(_) => panic!("unexpected worker event message!"),
303 Err(_) => Err(()),
304 },
305 }
306 }
307}
308
309impl QueuedTaskConversion for MainThreadScriptMsg {
310 fn task_source_name(&self) -> Option<&TaskSourceName> {
311 let script_msg = match self {
312 MainThreadScriptMsg::Common(script_msg) => script_msg,
313 _ => return None,
314 };
315 match script_msg {
316 CommonScriptMsg::Task(_category, _boxed, _pipeline_id, task_source) => {
317 Some(task_source)
318 },
319 _ => None,
320 }
321 }
322
323 fn pipeline_id(&self) -> Option<PipelineId> {
324 let script_msg = match self {
325 MainThreadScriptMsg::Common(script_msg) => script_msg,
326 _ => return None,
327 };
328 match script_msg {
329 CommonScriptMsg::Task(_category, _boxed, pipeline_id, _task_source) => *pipeline_id,
330 _ => None,
331 }
332 }
333
334 fn into_queued_task(self) -> Option<QueuedTask> {
335 let script_msg = match self {
336 MainThreadScriptMsg::Common(script_msg) => script_msg,
337 _ => return None,
338 };
339 let (event_category, task, pipeline_id, task_source) = match script_msg {
340 CommonScriptMsg::Task(category, boxed, pipeline_id, task_source) => {
341 (category, boxed, pipeline_id, task_source)
342 },
343 _ => return None,
344 };
345 Some(QueuedTask {
346 worker: None,
347 event_category,
348 task,
349 pipeline_id,
350 task_source,
351 })
352 }
353
354 fn from_queued_task(queued_task: QueuedTask) -> Self {
355 let script_msg = CommonScriptMsg::Task(
356 queued_task.event_category,
357 queued_task.task,
358 queued_task.pipeline_id,
359 queued_task.task_source,
360 );
361 MainThreadScriptMsg::Common(script_msg)
362 }
363
364 fn inactive_msg() -> Self {
365 MainThreadScriptMsg::Inactive
366 }
367
368 fn wake_up_msg() -> Self {
369 MainThreadScriptMsg::WakeUp
370 }
371
372 fn is_wake_up(&self) -> bool {
373 matches!(self, MainThreadScriptMsg::WakeUp)
374 }
375}
376
377impl OpaqueSender<CommonScriptMsg> for ScriptEventLoopSender {
378 fn send(&self, message: CommonScriptMsg) {
379 if self.send(message).is_err() {
380 log::warn!("Error communicating with the target thread from the profiler");
381 }
382 }
383}
384
385#[derive(Clone, JSTraceable)]
386pub(crate) struct ScriptThreadSenders {
387 pub(crate) self_sender: Sender<MainThreadScriptMsg>,
390
391 #[no_trace]
393 #[cfg(feature = "bluetooth")]
394 pub(crate) bluetooth_sender: GenericSender<BluetoothRequest>,
395
396 #[no_trace]
398 pub(crate) constellation_sender: GenericSender<ScriptThreadMessage>,
399
400 #[no_trace]
403 pub(crate) pipeline_to_constellation_sender:
404 GenericSender<(WebViewId, PipelineId, ScriptToConstellationMessage)>,
405
406 #[no_trace]
408 pub(crate) pipeline_to_embedder_sender: ScriptToEmbedderChan,
409
410 #[no_trace]
413 pub(crate) image_cache_sender: Sender<ImageCacheResponseMessage>,
414
415 #[no_trace]
417 pub(crate) time_profiler_sender: profile_time::ProfilerChan,
418
419 #[no_trace]
421 pub(crate) memory_profiler_sender: profile_mem::ProfilerChan,
422
423 #[no_trace]
425 #[cfg(feature = "devtools")]
426 pub(crate) devtools_server_sender: Option<GenericCallback<ScriptToDevtoolsControlMsg>>,
427
428 #[no_trace]
429 #[cfg(feature = "devtools")]
430 pub(crate) devtools_client_to_script_thread_sender: GenericSender<DevtoolScriptControlMsg>,
431}
432
433#[derive(JSTraceable)]
434pub(crate) struct ScriptThreadReceivers {
435 #[no_trace]
437 pub(crate) constellation_receiver: RoutedReceiver<ScriptThreadMessage>,
438
439 #[no_trace]
441 pub(crate) image_cache_receiver: Receiver<ImageCacheResponseMessage>,
442
443 #[no_trace]
446 #[cfg(feature = "devtools")]
447 pub(crate) devtools_server_receiver: RoutedReceiver<DevtoolScriptControlMsg>,
448
449 #[no_trace]
452 #[cfg(feature = "webgpu")]
453 pub(crate) webgpu_receiver: RefCell<RoutedReceiver<WebGPUMsg>>,
454}
455
456impl ScriptThreadReceivers {
457 pub(crate) fn recv(
460 &self,
461 task_queue: &TaskQueue<MainThreadScriptMsg>,
462 timer_scheduler: &TimerScheduler,
463 fully_active: &FxHashSet<PipelineId>,
464 ) -> MixedMessage {
465 let mut select = Select::new();
466
467 let task_recv = task_queue.select();
468 let task_index = select.recv(task_recv);
469 let constellation_index = select.recv(&self.constellation_receiver);
470 #[cfg(feature = "devtools")]
471 let devtools_index = select.recv(&self.devtools_server_receiver);
472 let image_cache_index = select.recv(&self.image_cache_receiver);
473
474 #[cfg(feature = "webgpu")]
475 let webgpu_receiver = self.webgpu_receiver.borrow();
476 #[cfg(feature = "webgpu")]
477 let webgpu_index = select.recv(&*webgpu_receiver);
478
479 let message_from_operation = |operation: SelectedOperation| {
480 let index = operation.index();
481 if index == task_index {
482 let msg = operation.recv(task_recv).unwrap();
483 task_queue.take_tasks(msg, fully_active);
484 let event = task_queue.recv().expect(
485 "Spurious wake-up of the event-loop, task-queue has no tasks available",
486 );
487 MixedMessage::FromScript(event)
488 } else if index == constellation_index {
489 MixedMessage::FromConstellation(
490 operation
491 .recv(&self.constellation_receiver)
492 .unwrap()
493 .unwrap(),
494 )
495 } else if index == image_cache_index {
496 MixedMessage::FromImageCache(operation.recv(&self.image_cache_receiver).unwrap())
497 } else {
498 #[cfg(feature = "devtools")]
499 if index == devtools_index {
500 return MixedMessage::FromDevtools(Box::new(
501 operation
502 .recv(&self.devtools_server_receiver)
503 .unwrap()
504 .unwrap(),
505 ));
506 }
507
508 #[cfg(feature = "webgpu")]
509 {
510 debug_assert_eq!(index, webgpu_index);
511 MixedMessage::FromWebGPUServer(
512 operation.recv(&*webgpu_receiver).unwrap().unwrap(),
513 )
514 }
515 #[cfg(not(feature = "webgpu"))]
516 unreachable!("select returned an unknown index {index}")
517 }
518 };
519
520 if let Some(deadline) = timer_scheduler.next_deadline() {
521 select
522 .select_deadline(deadline)
523 .map(message_from_operation)
524 .unwrap_or(MixedMessage::TimerFired)
525 } else {
526 message_from_operation(select.select())
527 }
528 }
529
530 pub(crate) fn try_recv(
533 &self,
534 task_queue: &TaskQueue<MainThreadScriptMsg>,
535 fully_active: &FxHashSet<PipelineId>,
536 ) -> Option<MixedMessage> {
537 if let Ok(message) = self.constellation_receiver.try_recv() {
538 let message = message
539 .inspect_err(|e| {
540 log::warn!(
541 "ScriptThreadReceivers IPC error on constellation_receiver: {:?}",
542 e
543 );
544 })
545 .ok()?;
546 return MixedMessage::FromConstellation(message).into();
547 }
548 if let Ok(message) = task_queue.take_tasks_and_recv(fully_active) {
549 return MixedMessage::FromScript(message).into();
550 }
551 #[cfg(feature = "devtools")]
552 if let Ok(message) = self.devtools_server_receiver.try_recv() {
553 return MixedMessage::FromDevtools(Box::new(message.unwrap())).into();
554 }
555 if let Ok(message) = self.image_cache_receiver.try_recv() {
556 return MixedMessage::FromImageCache(message).into();
557 }
558 #[cfg(feature = "webgpu")]
559 if let Ok(message) = self.webgpu_receiver.borrow().try_recv() {
560 return MixedMessage::FromWebGPUServer(message.unwrap()).into();
561 }
562 None
563 }
564}