1use 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#[derive(Debug)]
149pub(crate) enum MainThreadScriptMsg {
150 Common(CommonScriptMsg),
152 WorkletLoaded(PipelineId),
155 NavigationResponse {
156 pipeline_id: PipelineId,
157 message: Box<FetchResponseMsg>,
158 },
159 RegisterPaintWorklet {
161 pipeline_id: PipelineId,
162 name: Atom,
163 properties: Vec<Atom>,
164 painter: Box<dyn Painter>,
165 },
166 Inactive,
168 WakeUp,
170 ForwardEmbedderControlResponseFromFileManager(EmbedderControlId, EmbedderControlResponse),
173}
174
175pub(crate) enum CommonScriptMsg {
177 CollectReports(ReportsChan),
180 Task(
182 ScriptThreadEventCategory,
183 Box<dyn TaskBox>,
184 Option<PipelineId>,
185 TaskSourceName,
186 ),
187 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#[derive(Clone, JSTraceable, MallocSizeOf)]
207pub(crate) enum ScriptEventLoopSender {
208 MainThread(Sender<MainThreadScriptMsg>),
210 ServiceWorker(Sender<ServiceWorkerScriptMsg>),
212 DedicatedWorker {
216 sender: Sender<DedicatedWorkerScriptMsg>,
217 main_thread_worker: TrustedWorkerAddress,
218 },
219}
220
221impl ScriptEventLoopSender {
222 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
249pub(crate) enum ScriptEventLoopReceiver {
253 MainThread(Receiver<MainThreadScriptMsg>),
255 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 pub(crate) self_sender: Sender<MainThreadScriptMsg>,
357
358 #[no_trace]
360 #[cfg(feature = "bluetooth")]
361 pub(crate) bluetooth_sender: GenericSender<BluetoothRequest>,
362
363 #[no_trace]
365 pub(crate) constellation_sender: GenericSender<ScriptThreadMessage>,
366
367 #[no_trace]
370 pub(crate) pipeline_to_constellation_sender:
371 GenericSender<(WebViewId, PipelineId, ScriptToConstellationMessage)>,
372
373 #[no_trace]
375 pub(crate) pipeline_to_embedder_sender: ScriptToEmbedderChan,
376
377 #[no_trace]
380 pub(crate) image_cache_sender: Sender<ImageCacheResponseMessage>,
381
382 #[no_trace]
384 pub(crate) time_profiler_sender: profile_time::ProfilerChan,
385
386 #[no_trace]
388 pub(crate) memory_profiler_sender: profile_mem::ProfilerChan,
389
390 #[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 #[no_trace]
402 pub(crate) constellation_receiver: RoutedReceiver<ScriptThreadMessage>,
403
404 #[no_trace]
406 pub(crate) image_cache_receiver: Receiver<ImageCacheResponseMessage>,
407
408 #[no_trace]
411 pub(crate) devtools_server_receiver: RoutedReceiver<DevtoolScriptControlMsg>,
412
413 #[no_trace]
416 #[cfg(feature = "webgpu")]
417 pub(crate) webgpu_receiver: RefCell<RoutedReceiver<WebGPUMsg>>,
418}
419
420impl ScriptThreadReceivers {
421 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 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}