1use core::fmt;
6#[cfg(feature = "webgpu")]
7use std::cell::RefCell;
8use std::option::Option;
9use std::result::Result;
10use std::time::Instant;
11
12use crossbeam_channel::{Receiver, RecvTimeoutError, Select, SelectedOperation, SendError, Sender};
13#[cfg(feature = "devtools")]
14use devtools_traits::{DevtoolScriptControlMsg, ScriptToDevtoolsControlMsg};
15use embedder_traits::{EmbedderControlId, EmbedderControlResponse, ScriptToEmbedderChan};
16use net_traits::FetchResponseMsg;
17use net_traits::image_cache::ImageCacheResponseMessage;
18use profile_traits::mem::{self as profile_mem, OpaqueSender, ReportsChan};
19use profile_traits::time::{self as profile_time};
20use rustc_hash::FxHashSet;
21use script_traits::{Painter, ScriptThreadMessage};
22use servo_base::generic_channel::{GenericCallback, GenericSender, RoutedReceiver};
23use servo_base::id::{PipelineId, WebViewId};
24#[cfg(feature = "bluetooth")]
25use servo_bluetooth_traits::BluetoothRequest;
26use servo_constellation_traits::ScriptToConstellationMessage;
27use stylo_atoms::Atom;
28use timers::TimerScheduler;
29#[cfg(feature = "webgpu")]
30use webgpu_traits::WebGPUMsg;
31
32use crate::dom::WorkletExecutor;
33use crate::dom::abstractworker::WorkerScriptMsg;
34use crate::dom::bindings::trace::CustomTraceable;
35use crate::dom::csp::Violation;
36use crate::dom::dedicatedworkerglobalscope::DedicatedWorkerScriptMsg;
37use crate::dom::serviceworkerglobalscope::ServiceWorkerScriptMsg;
38use crate::dom::sharedworkerglobalscope::SharedWorkerScriptMsg;
39use crate::dom::worker::TrustedWorkerAddress;
40use crate::runtime::script_runtime::ScriptThreadEventCategory;
41use crate::tasks::task::TaskBox;
42use crate::tasks::task_queue::{QueuedTask, QueuedTaskConversion, TaskQueue};
43use crate::tasks::task_source::TaskSourceName;
44
45#[expect(clippy::large_enum_variant)]
46#[derive(Debug)]
47pub(crate) enum MixedMessage {
49 FromConstellation(ScriptThreadMessage),
50 FromScript(MainThreadScriptMsg),
51 #[cfg(feature = "devtools")]
52 FromDevtools(Box<DevtoolScriptControlMsg>),
53 FromImageCache(ImageCacheResponseMessage),
54 #[cfg(feature = "webgpu")]
55 FromWebGPUServer(WebGPUMsg),
56 TimerFired,
57}
58
59impl MixedMessage {
60 pub(crate) fn pipeline_id(&self) -> Option<PipelineId> {
61 match self {
62 MixedMessage::FromConstellation(inner_msg) => match inner_msg {
63 ScriptThreadMessage::UpdateServiceWorkerState { pipeline_id, .. } => {
64 Some(*pipeline_id)
65 },
66 ScriptThreadMessage::StopDelayingLoadEventsMode(id) => Some(*id),
67 ScriptThreadMessage::SpawnPipeline(new_pipeline_info) => new_pipeline_info
68 .parent_info
69 .or(Some(new_pipeline_info.new_pipeline_id)),
70 ScriptThreadMessage::Resize(id, ..) => Some(*id),
71 ScriptThreadMessage::UpdateWebViewState(..) => None,
72 ScriptThreadMessage::ResizeInactive(id, ..) => Some(*id),
73 ScriptThreadMessage::UnloadDocument(id) => Some(*id),
74 ScriptThreadMessage::ExitPipeline(_webview_id, id, ..) => Some(*id),
75 ScriptThreadMessage::ExitScriptThread => None,
76 ScriptThreadMessage::SendInputEvent(_, id, _) => Some(*id),
77 ScriptThreadMessage::RefreshCursor(id, ..) => Some(*id),
78 ScriptThreadMessage::GetTitle(id) => Some(*id),
79 ScriptThreadMessage::GetDocumentOrigin(id, _) => Some(*id),
80 ScriptThreadMessage::GetDocumentOriginDetails(id, _) => Some(*id),
81 ScriptThreadMessage::SetDocumentActivity(id, ..) => Some(*id),
82 ScriptThreadMessage::SetThrottled(id, ..) => Some(*id),
83 ScriptThreadMessage::NavigateIframe(id, ..) => Some(*id),
84 ScriptThreadMessage::PostMessage { target: id, .. } => Some(*id),
85 ScriptThreadMessage::UpdatePipelineId(_, _, _, id, _) => Some(*id),
86 ScriptThreadMessage::UpdateHistoryState(id, ..) => Some(*id),
87 ScriptThreadMessage::RemoveHistoryStates(id, ..) => Some(*id),
88
89 ScriptThreadMessage::FocusDocumentAsPartOfFocusingSteps(id, ..) => Some(*id),
90 ScriptThreadMessage::UnfocusDocumentAsPartOfFocusingSteps(id, ..) => Some(*id),
91 ScriptThreadMessage::FocusDocument(id, ..) => Some(*id),
92 ScriptThreadMessage::WebDriverScriptCommand(id, ..) => Some(*id),
93 ScriptThreadMessage::TickAllAnimations(..) => None,
94 ScriptThreadMessage::WebFontLoadFinished(id, ..) => Some(*id),
95 ScriptThreadMessage::IFrameLoadFinished { parent: id, .. } => 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 ScriptThreadMessage::SetNetworkOnlineState(_) => None,
120 },
121 MixedMessage::FromScript(inner_msg) => match inner_msg {
122 MainThreadScriptMsg::Common(CommonScriptMsg::Task(_, _, pipeline_id, _)) => {
123 *pipeline_id
124 },
125 MainThreadScriptMsg::Common(CommonScriptMsg::CollectReports(_)) => None,
126 MainThreadScriptMsg::Common(CommonScriptMsg::ReportCspViolations(
127 pipeline_id,
128 _,
129 )) => Some(*pipeline_id),
130 MainThreadScriptMsg::NavigationResponse { pipeline_id, .. } => Some(*pipeline_id),
131 MainThreadScriptMsg::WorkletLoaded(pipeline_id) => Some(*pipeline_id),
132 MainThreadScriptMsg::RegisterPaintWorklet { pipeline_id, .. } => Some(*pipeline_id),
133 MainThreadScriptMsg::Inactive => None,
134 MainThreadScriptMsg::WakeUp => None,
135 MainThreadScriptMsg::ForwardEmbedderControlResponseFromFileManager(
136 control_id,
137 ..,
138 ) => Some(control_id.pipeline_id),
139 },
140 MixedMessage::FromImageCache(response) => match response {
141 ImageCacheResponseMessage::NotifyPendingImageLoadStatus(response) => {
142 Some(response.pipeline_id)
143 },
144 ImageCacheResponseMessage::VectorImageRasterizationComplete(response) => {
145 Some(response.pipeline_id)
146 },
147 },
148 #[cfg(feature = "devtools")]
149 MixedMessage::FromDevtools(_) => None,
150 MixedMessage::TimerFired => None,
151 #[cfg(feature = "webgpu")]
152 MixedMessage::FromWebGPUServer(..) => None,
153 }
154 }
155}
156
157#[derive(Debug)]
159pub(crate) enum MainThreadScriptMsg {
160 Common(CommonScriptMsg),
162 WorkletLoaded(PipelineId),
165 NavigationResponse {
166 pipeline_id: PipelineId,
167 message: Box<FetchResponseMsg>,
168 },
169 RegisterPaintWorklet {
171 pipeline_id: PipelineId,
172 name: Atom,
173 properties: Vec<Atom>,
174 painter: Box<dyn Painter>,
175 },
176 Inactive,
178 WakeUp,
180 ForwardEmbedderControlResponseFromFileManager(EmbedderControlId, EmbedderControlResponse),
183}
184
185#[derive(MallocSizeOf)]
188pub(crate) enum CommonScriptMsg {
189 CollectReports(ReportsChan),
192 Task(
194 ScriptThreadEventCategory,
195 #[ignore_malloc_size_of = "dyn is difficult"] Box<dyn TaskBox>,
196 Option<PipelineId>,
197 TaskSourceName,
198 ),
199 ReportCspViolations(PipelineId, Vec<Violation>),
201}
202
203impl fmt::Debug for CommonScriptMsg {
204 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
205 match *self {
206 CommonScriptMsg::CollectReports(_) => write!(f, "CollectReports(...)"),
207 CommonScriptMsg::Task(ref category, ref task, _, _) => {
208 f.debug_tuple("Task").field(category).field(task).finish()
209 },
210 CommonScriptMsg::ReportCspViolations(..) => write!(f, "ReportCspViolations(...)"),
211 }
212 }
213}
214
215#[derive(Clone, JSTraceable, MallocSizeOf)]
219pub(crate) enum ScriptEventLoopSender {
220 MainThread(Sender<MainThreadScriptMsg>),
222 SharedWorker(Sender<SharedWorkerScriptMsg>),
224 ServiceWorker(Sender<ServiceWorkerScriptMsg>),
226 Worklet(WorkletExecutor),
228 DedicatedWorker {
232 sender: Sender<DedicatedWorkerScriptMsg>,
233 main_thread_worker: TrustedWorkerAddress,
234 },
235}
236
237impl ScriptEventLoopSender {
238 pub(crate) fn send(&self, message: CommonScriptMsg) -> Result<(), SendError<()>> {
240 match self {
241 Self::MainThread(sender) => sender
242 .send(MainThreadScriptMsg::Common(message))
243 .map_err(|_| SendError(())),
244 Self::SharedWorker(sender) => sender
245 .send(SharedWorkerScriptMsg::CommonWorker(
246 WorkerScriptMsg::Common(message),
247 ))
248 .map_err(|_| SendError(())),
249 Self::ServiceWorker(sender) => sender
250 .send(ServiceWorkerScriptMsg::CommonWorker(
251 WorkerScriptMsg::Common(message),
252 ))
253 .map_err(|_| SendError(())),
254 Self::DedicatedWorker {
255 sender,
256 main_thread_worker,
257 } => {
258 let common_message = WorkerScriptMsg::Common(message);
259 sender
260 .send(DedicatedWorkerScriptMsg::CommonWorker(
261 main_thread_worker.clone(),
262 common_message,
263 ))
264 .map_err(|_| SendError(()))
265 },
266 Self::Worklet(executor) => executor.send_common_message(message),
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 pub(crate) fn recv_deadline(
310 &self,
311 deadline: Instant,
312 ) -> Result<CommonScriptMsg, RecvTimeoutError> {
313 match self {
314 Self::MainThread(receiver) => match receiver.recv_deadline(deadline)? {
315 MainThreadScriptMsg::Common(script_msg) => Ok(script_msg),
316 _ => panic!("unexpected main thread event message!"),
317 },
318 Self::SharedWorker(receiver) => match receiver.recv_deadline(deadline)? {
319 SharedWorkerScriptMsg::CommonWorker(WorkerScriptMsg::Common(message)) => {
320 Ok(message)
321 },
322 _ => panic!("unexpected shared worker event message!"),
323 },
324 Self::DedicatedWorker(receiver) => match receiver.recv_deadline(deadline)? {
325 DedicatedWorkerScriptMsg::CommonWorker(_, WorkerScriptMsg::Common(message)) => {
326 Ok(message)
327 },
328 _ => panic!("unexpected worker event message!"),
329 },
330 }
331 }
332}
333
334impl QueuedTaskConversion for MainThreadScriptMsg {
335 fn pipeline_id(&self) -> Option<PipelineId> {
336 let script_msg = match self {
337 MainThreadScriptMsg::Common(script_msg) => script_msg,
338 _ => return None,
339 };
340 match script_msg {
341 CommonScriptMsg::Task(_category, _boxed, pipeline_id, _task_source) => *pipeline_id,
342 _ => None,
343 }
344 }
345
346 fn into_queued_task(self) -> Option<QueuedTask> {
347 let script_msg = match self {
348 MainThreadScriptMsg::Common(script_msg) => script_msg,
349 _ => return None,
350 };
351 let (event_category, task, pipeline_id, task_source) = match script_msg {
352 CommonScriptMsg::Task(category, boxed, pipeline_id, task_source) => {
353 (category, boxed, pipeline_id, task_source)
354 },
355 _ => return None,
356 };
357 Some(QueuedTask {
358 worker: None,
359 event_category,
360 task,
361 pipeline_id,
362 task_source,
363 })
364 }
365
366 fn from_queued_task(queued_task: QueuedTask) -> Self {
367 let script_msg = CommonScriptMsg::Task(
368 queued_task.event_category,
369 queued_task.task,
370 queued_task.pipeline_id,
371 queued_task.task_source,
372 );
373 MainThreadScriptMsg::Common(script_msg)
374 }
375
376 fn inactive_msg() -> Self {
377 MainThreadScriptMsg::Inactive
378 }
379
380 fn wake_up_msg() -> Self {
381 MainThreadScriptMsg::WakeUp
382 }
383
384 fn is_wake_up(&self) -> bool {
385 matches!(self, MainThreadScriptMsg::WakeUp)
386 }
387}
388
389impl OpaqueSender<CommonScriptMsg> for ScriptEventLoopSender {
390 fn send(&self, message: CommonScriptMsg) {
391 if self.send(message).is_err() {
392 log::warn!("Error communicating with the target thread from the profiler");
393 }
394 }
395}
396
397#[derive(Clone, JSTraceable)]
398pub(crate) struct ScriptThreadSenders {
399 pub(crate) self_sender: Sender<MainThreadScriptMsg>,
402
403 #[no_trace]
405 #[cfg(feature = "bluetooth")]
406 pub(crate) bluetooth_sender: GenericSender<BluetoothRequest>,
407
408 #[no_trace]
410 pub(crate) constellation_sender: GenericSender<ScriptThreadMessage>,
411
412 #[no_trace]
415 pub(crate) pipeline_to_constellation_sender:
416 GenericSender<(WebViewId, PipelineId, ScriptToConstellationMessage)>,
417
418 #[no_trace]
420 pub(crate) pipeline_to_embedder_sender: ScriptToEmbedderChan,
421
422 #[no_trace]
425 pub(crate) image_cache_sender: Sender<ImageCacheResponseMessage>,
426
427 #[no_trace]
429 pub(crate) time_profiler_sender: profile_time::ProfilerChan,
430
431 #[no_trace]
433 pub(crate) memory_profiler_sender: profile_mem::ProfilerChan,
434
435 #[no_trace]
437 #[cfg(feature = "devtools")]
438 pub(crate) devtools_server_sender: Option<GenericCallback<ScriptToDevtoolsControlMsg>>,
439
440 #[no_trace]
441 #[cfg(feature = "devtools")]
442 pub(crate) devtools_client_to_script_thread_sender: GenericSender<DevtoolScriptControlMsg>,
443}
444
445#[derive(JSTraceable)]
446pub(crate) struct ScriptThreadReceivers {
447 #[no_trace]
449 pub(crate) constellation_receiver: RoutedReceiver<ScriptThreadMessage>,
450
451 #[no_trace]
453 pub(crate) image_cache_receiver: Receiver<ImageCacheResponseMessage>,
454
455 #[no_trace]
458 #[cfg(feature = "devtools")]
459 pub(crate) devtools_server_receiver: RoutedReceiver<DevtoolScriptControlMsg>,
460
461 #[no_trace]
464 #[cfg(feature = "webgpu")]
465 pub(crate) webgpu_receiver: RefCell<RoutedReceiver<WebGPUMsg>>,
466}
467
468impl ScriptThreadReceivers {
469 pub(crate) fn recv(
472 &self,
473 task_queue: &TaskQueue<MainThreadScriptMsg>,
474 timer_scheduler: &TimerScheduler,
475 fully_active: &FxHashSet<PipelineId>,
476 ) -> MixedMessage {
477 let mut select = Select::new();
478
479 let task_recv = task_queue.select();
480 let task_index = select.recv(task_recv);
481 let constellation_index = select.recv(&self.constellation_receiver);
482 #[cfg(feature = "devtools")]
483 let devtools_index = select.recv(&self.devtools_server_receiver);
484 let image_cache_index = select.recv(&self.image_cache_receiver);
485
486 #[cfg(feature = "webgpu")]
487 let webgpu_receiver = self.webgpu_receiver.borrow();
488 #[cfg(feature = "webgpu")]
489 let webgpu_index = select.recv(&*webgpu_receiver);
490
491 let message_from_operation = |operation: SelectedOperation| {
492 let index = operation.index();
493 if index == task_index {
494 let msg = operation.recv(task_recv).unwrap();
495 task_queue.take_tasks(msg, fully_active);
496 let event = task_queue.recv().expect(
497 "Spurious wake-up of the event-loop, task-queue has no tasks available",
498 );
499 MixedMessage::FromScript(event)
500 } else if index == constellation_index {
501 MixedMessage::FromConstellation(
502 operation
503 .recv(&self.constellation_receiver)
504 .unwrap()
505 .unwrap(),
506 )
507 } else if index == image_cache_index {
508 MixedMessage::FromImageCache(operation.recv(&self.image_cache_receiver).unwrap())
509 } else {
510 #[cfg(feature = "devtools")]
511 if index == devtools_index {
512 return MixedMessage::FromDevtools(Box::new(
513 operation
514 .recv(&self.devtools_server_receiver)
515 .unwrap()
516 .unwrap(),
517 ));
518 }
519
520 #[cfg(feature = "webgpu")]
521 {
522 debug_assert_eq!(index, webgpu_index);
523 MixedMessage::FromWebGPUServer(
524 operation.recv(&*webgpu_receiver).unwrap().unwrap(),
525 )
526 }
527 #[cfg(not(feature = "webgpu"))]
528 unreachable!("select returned an unknown index {index}")
529 }
530 };
531
532 if let Some(deadline) = timer_scheduler.next_deadline() {
533 select
534 .select_deadline(deadline)
535 .map(message_from_operation)
536 .unwrap_or(MixedMessage::TimerFired)
537 } else {
538 message_from_operation(select.select())
539 }
540 }
541
542 pub(crate) fn try_recv(
545 &self,
546 task_queue: &TaskQueue<MainThreadScriptMsg>,
547 fully_active: &FxHashSet<PipelineId>,
548 ) -> Option<MixedMessage> {
549 if let Ok(message) = self.constellation_receiver.try_recv() {
550 let message = message
551 .inspect_err(|e| {
552 log::warn!(
553 "ScriptThreadReceivers IPC error on constellation_receiver: {:?}",
554 e
555 );
556 })
557 .ok()?;
558 return MixedMessage::FromConstellation(message).into();
559 }
560 if let Ok(message) = task_queue.take_tasks_and_recv(fully_active) {
561 return MixedMessage::FromScript(message).into();
562 }
563 #[cfg(feature = "devtools")]
564 if let Ok(message) = self.devtools_server_receiver.try_recv() {
565 return MixedMessage::FromDevtools(Box::new(message.unwrap())).into();
566 }
567 if let Ok(message) = self.image_cache_receiver.try_recv() {
568 return MixedMessage::FromImageCache(message).into();
569 }
570 #[cfg(feature = "webgpu")]
571 if let Ok(message) = self.webgpu_receiver.borrow().try_recv() {
572 return MixedMessage::FromWebGPUServer(message.unwrap()).into();
573 }
574 None
575 }
576}