script/dom/workers/
abstractworkerglobalscope.rs1#![cfg_attr(crown, allow(crown::jscontext_first_arg))]
6
7use crossbeam_channel::{Receiver, Select};
8#[cfg(feature = "devtools")]
9use devtools_traits::DevtoolScriptControlMsg;
10use rustc_hash::FxHashSet;
11use script_bindings::reflector::DomObject;
12use servo_base::generic_channel::RoutedReceiver;
13use servo_constellation_traits::WorkerAnimationFrameTick;
14
15use crate::dom::bindings::conversions::DerivedFrom;
16use crate::dom::dedicatedworkerglobalscope::AutoWorkerReset;
17use crate::dom::globalscope::GlobalScope;
18use crate::dom::worker::TrustedWorkerAddress;
19use crate::dom::workerglobalscope::WorkerGlobalScope;
20use crate::realms::enter_auto_realm;
21use crate::tasks::task_queue::{QueuedTaskConversion, TaskQueue};
22
23pub(crate) trait WorkerEventLoopMethods {
24 type WorkerMsg: QueuedTaskConversion + Send;
25 type ControlMsg;
26 type Event;
27 fn task_queue(&self) -> &TaskQueue<Self::WorkerMsg>;
28 fn handle_event(&self, event: Self::Event, cx: &mut js::context::JSContext) -> bool;
29 fn handle_worker_post_event(
30 &self,
31 worker: &TrustedWorkerAddress,
32 ) -> Option<AutoWorkerReset<'_>>;
33 fn from_control_msg(msg: Self::ControlMsg) -> Self::Event;
34 fn from_worker_msg(msg: Self::WorkerMsg) -> Self::Event;
35 #[cfg(feature = "devtools")]
36 fn from_devtools_msg(msg: DevtoolScriptControlMsg) -> Self::Event;
37 fn from_timer_msg() -> Self::Event;
38 fn from_animation_frame_tick_msg(_msg: WorkerAnimationFrameTick) -> Option<Self::Event> {
39 None
40 }
41 fn animation_frame_tick_receiver(&self) -> Option<&RoutedReceiver<WorkerAnimationFrameTick>> {
42 None
43 }
44 fn control_receiver(&self) -> &Receiver<Self::ControlMsg>;
45}
46
47pub(crate) fn run_worker_event_loop<T, WorkerMsg, Event>(
49 worker_scope: &T,
50 worker: Option<&TrustedWorkerAddress>,
51 cx: &mut js::context::JSContext,
52) where
53 WorkerMsg: QueuedTaskConversion + Send,
54 T: WorkerEventLoopMethods<WorkerMsg = WorkerMsg, Event = Event>
55 + DerivedFrom<WorkerGlobalScope>
56 + DerivedFrom<GlobalScope>
57 + DomObject,
58{
59 let scope = worker_scope.upcast::<WorkerGlobalScope>();
60 let task_queue = worker_scope.task_queue();
61
62 #[cfg(feature = "devtools")]
63 let devtools_never = crossbeam_channel::never();
64 #[cfg(feature = "devtools")]
65 let devtools_receiver = scope.devtools_receiver().unwrap_or(&devtools_never);
66 let animation_frame_tick_never = crossbeam_channel::never();
67 let animation_frame_tick_receiver = worker_scope
68 .animation_frame_tick_receiver()
69 .unwrap_or(&animation_frame_tick_never);
70
71 let task_receiver = task_queue.select();
72 let timer_receiver = scope.timer_scheduler().wait_channel();
73 let mut select = Select::new();
74 let control_index = select.recv(worker_scope.control_receiver());
75 let task_index = select.recv(task_receiver);
76 #[cfg(feature = "devtools")]
77 let devtools_index = select.recv(devtools_receiver);
78 let animation_frame_tick_index = select.recv(animation_frame_tick_receiver);
79 let timer_index = select.recv(&timer_receiver);
80
81 let operation = select.select();
82 let event = match operation.index() {
83 index if index == control_index => operation
84 .recv(worker_scope.control_receiver())
85 .ok()
86 .map(T::from_control_msg),
87 index if index == task_index => match operation.recv(task_receiver) {
88 Ok(msg) => {
89 task_queue.take_tasks(msg, &FxHashSet::default());
90 task_queue.recv().ok().map(T::from_worker_msg)
91 },
92 Err(_) => None,
93 },
94 #[cfg(feature = "devtools")]
95 index if index == devtools_index => operation
96 .recv(devtools_receiver)
97 .ok()
98 .and_then(|msg| msg.ok().map(T::from_devtools_msg)),
99 index if index == animation_frame_tick_index => {
100 match operation.recv(animation_frame_tick_receiver) {
101 Ok(Ok(msg)) => T::from_animation_frame_tick_msg(msg),
102 Ok(Err(_)) | Err(_) => None,
103 }
104 },
105 index if index == timer_index => operation
106 .recv(&timer_receiver)
107 .ok()
108 .map(|_| T::from_timer_msg()),
109 _ => unreachable!("selected an unregistered worker event-loop receiver"),
110 };
111
112 let Some(event) = event else {
116 scope.close();
117 return;
118 };
119
120 scope.timer_scheduler().dispatch_completed_timers();
121
122 let mut sequential = vec![event];
123
124 while !scope.is_closing() {
130 match task_queue.take_tasks_and_recv(&FxHashSet::default()) {
133 Err(_) => {
134 #[cfg(feature = "devtools")]
135 if let Ok(message) = devtools_receiver.try_recv() {
136 sequential.push(T::from_devtools_msg(message.unwrap()));
137 continue;
138 }
139
140 break;
141 },
142 Ok(ev) => sequential.push(T::from_worker_msg(ev)),
143 }
144 }
145
146 for event in sequential {
148 let mut realm = enter_auto_realm(cx, worker_scope);
149 let cx = &mut realm.current_realm();
150 let _ar = match worker {
151 Some(worker) => worker_scope.handle_worker_post_event(worker),
152 None => None,
153 };
154 if !worker_scope.handle_event(event, cx) {
155 return;
157 }
158 scope.perform_a_microtask_checkpoint(cx);
160 }
161 worker_scope
162 .upcast::<GlobalScope>()
163 .perform_a_dom_garbage_collection_checkpoint();
164}