1use std::hash::Hash;
10use std::marker::PhantomData;
11use std::rc::Rc;
12
13use background_hang_monitor_api::{BackgroundHangMonitorControlMsg, HangMonitorAlert};
14use base::generic_channel::{self, GenericReceiver, GenericSender};
15use base::id::ScriptEventLoopId;
16use constellation_traits::ServiceWorkerManagerFactory;
17use devtools_traits::DevtoolsControlMsg;
18use embedder_traits::ScriptToEmbedderChan;
19use ipc_channel::ipc::IpcSender;
20use ipc_channel::router::ROUTER;
21use ipc_channel::{Error, ipc};
22use layout_api::ScriptThreadFactory;
23use log::{error, warn};
24use media::WindowGLContext;
25use script_traits::{InitialScriptState, ScriptThreadMessage};
26use serde::{Deserialize, Serialize};
27use servo_config::opts::{self, Opts};
28use servo_config::prefs::{self, Preferences};
29
30use crate::constellation::route_ipc_receiver_to_new_crossbeam_receiver_preserving_errors;
31use crate::sandboxing::spawn_multiprocess;
32use crate::{Constellation, UnprivilegedContent};
33
34pub struct EventLoop {
36 script_chan: GenericSender<ScriptThreadMessage>,
37 id: ScriptEventLoopId,
38 background_hang_monitor_sender: Option<GenericSender<BackgroundHangMonitorControlMsg>>,
42 dont_send_or_sync: PhantomData<Rc<()>>,
43}
44
45impl PartialEq for EventLoop {
46 fn eq(&self, other: &Self) -> bool {
47 self.id == other.id
48 }
49}
50
51impl Eq for EventLoop {}
52
53impl Hash for EventLoop {
54 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
55 self.id.hash(state);
56 }
57}
58
59impl Drop for EventLoop {
60 fn drop(&mut self) {
61 self.send_message_to_background_hang_monitor(&BackgroundHangMonitorControlMsg::Exit);
62
63 if let Err(error) = self.script_chan.send(ScriptThreadMessage::ExitScriptThread) {
64 error!("Did not successfully request EventLoop exit: {error}");
65 }
66 }
67}
68
69impl EventLoop {
70 pub(crate) fn spawn<STF: ScriptThreadFactory, SWF: ServiceWorkerManagerFactory>(
71 constellation: &mut Constellation<STF, SWF>,
72 is_private: bool,
73 ) -> Result<Rc<Self>, Error> {
74 let (script_chan, script_port) =
75 base::generic_channel::channel().expect("Pipeline script chan");
76
77 let devtools_sender = constellation.devtools_sender.as_ref();
79 let script_to_devtools_ipc_sender = devtools_sender.map(|devtools_sender| {
80 let (script_to_devtools_ipc_sender, script_to_devtools_ipc_receiver) =
81 ipc::channel().expect("Pipeline script to devtools chan");
82 let devtools_sender = (*devtools_sender).clone();
83 ROUTER.add_typed_route(
84 script_to_devtools_ipc_receiver,
85 Box::new(move |message| match message {
86 Err(error) => error!("Cast to ScriptToDevtoolsControlMsg failed ({error})."),
87 Ok(message) => {
88 if let Err(error) =
89 devtools_sender.send(DevtoolsControlMsg::FromScript(message))
90 {
91 warn!("Sending to devtools failed ({error:?})")
92 }
93 },
94 }),
95 );
96 script_to_devtools_ipc_sender
97 });
98
99 let embedder_chan = constellation.embedder_proxy.sender.clone();
100 let eventloop_waker = constellation.embedder_proxy.event_loop_waker.clone();
101 let script_to_embedder_sender = ScriptToEmbedderChan::new(embedder_chan, eventloop_waker);
102
103 let resource_threads = if is_private {
104 constellation.private_resource_threads.clone()
105 } else {
106 constellation.public_resource_threads.clone()
107 };
108 let storage_threads = if is_private {
109 constellation.private_storage_threads.clone()
110 } else {
111 constellation.public_storage_threads.clone()
112 };
113
114 let event_loop_id = ScriptEventLoopId::new();
115 let initial_script_state = InitialScriptState {
116 id: event_loop_id,
117 script_to_constellation_sender: constellation.script_sender.clone(),
118 script_to_embedder_sender,
119 namespace_request_sender: constellation.namespace_ipc_sender.clone(),
120 devtools_server_sender: script_to_devtools_ipc_sender,
121 #[cfg(feature = "bluetooth")]
122 bluetooth_sender: constellation.bluetooth_ipc_sender.clone(),
123 system_font_service: constellation.system_font_service.to_sender(),
124 resource_threads,
125 storage_threads,
126 time_profiler_sender: constellation.time_profiler_chan.clone(),
127 memory_profiler_sender: constellation.mem_profiler_chan.clone(),
128 constellation_to_script_sender: script_chan,
129 constellation_to_script_receiver: script_port,
130 pipeline_namespace_id: constellation.next_pipeline_namespace_id(),
131 cross_process_compositor_api: constellation
132 .compositor_proxy
133 .cross_process_compositor_api
134 .clone(),
135 webgl_chan: constellation
136 .webgl_threads
137 .as_ref()
138 .map(|threads| threads.pipeline()),
139 webxr_registry: constellation.webxr_registry.clone(),
140 player_context: WindowGLContext::get(),
141 user_content_manager: constellation.user_content_manager.clone(),
142 privileged_urls: constellation.privileged_urls.clone(),
143 };
144
145 let event_loop = if opts::get().multiprocess {
146 Self::spawn_in_process(constellation, initial_script_state)?
147 } else {
148 Self::spawn_in_thread(constellation, initial_script_state)
149 };
150
151 let event_loop = Rc::new(event_loop);
152 constellation.add_event_loop(&event_loop);
153 Ok(event_loop)
154 }
155
156 fn spawn_in_thread<STF: ScriptThreadFactory, SWF: ServiceWorkerManagerFactory>(
157 constellation: &mut Constellation<STF, SWF>,
158 initial_script_state: InitialScriptState,
159 ) -> Self {
160 let script_chan = initial_script_state.constellation_to_script_sender.clone();
161 let id = initial_script_state.id;
162 let background_hang_monitor_register = constellation
163 .background_monitor_register
164 .clone()
165 .expect("Couldn't start content, no background monitor has been initiated");
166 let join_handle = STF::create(
167 initial_script_state,
168 constellation.layout_factory.clone(),
169 constellation.image_cache_factory.clone(),
170 background_hang_monitor_register,
171 );
172 constellation.add_event_loop_join_handle(join_handle);
173
174 Self {
175 script_chan,
176 id,
177 background_hang_monitor_sender: None,
178 dont_send_or_sync: PhantomData,
179 }
180 }
181
182 fn spawn_in_process<STF: ScriptThreadFactory, SWF: ServiceWorkerManagerFactory>(
183 constellation: &mut Constellation<STF, SWF>,
184 initial_script_state: InitialScriptState,
185 ) -> Result<Self, Error> {
186 let script_chan = initial_script_state.constellation_to_script_sender.clone();
187 let id = initial_script_state.id;
188
189 let (background_hand_monitor_sender, backgrond_hand_monitor_receiver) =
190 generic_channel::channel().expect("Sampler chan");
191 let (lifeline_sender, lifeline_receiver) =
192 ipc::channel().expect("Failed to create lifeline channel");
193
194 let process = spawn_multiprocess(UnprivilegedContent::ScriptEventLoop(
195 NewScriptEventLoopProcessInfo {
196 initial_script_state,
197 constellation_to_bhm_receiver: backgrond_hand_monitor_receiver,
198 bhm_to_constellation_sender: constellation.background_hang_monitor_sender.clone(),
199 lifeline_sender,
200 opts: (*opts::get()).clone(),
201 prefs: Box::new(prefs::get().clone()),
202 broken_image_icon_data: constellation.broken_image_icon_data.clone(),
203 },
204 ))?;
205
206 let crossbeam_receiver =
207 route_ipc_receiver_to_new_crossbeam_receiver_preserving_errors(lifeline_receiver);
208 constellation
209 .process_manager
210 .add(crossbeam_receiver, process);
211
212 Ok(Self {
213 script_chan,
214 id,
215 background_hang_monitor_sender: Some(background_hand_monitor_sender),
216 dont_send_or_sync: PhantomData,
217 })
218 }
219
220 pub(crate) fn id(&self) -> ScriptEventLoopId {
221 self.id
222 }
223
224 pub fn send(&self, msg: ScriptThreadMessage) -> Result<(), Error> {
226 self.script_chan
227 .send(msg)
228 .map_err(|_err| Box::new(ipc_channel::ErrorKind::Custom("SendError".into())))
229 }
230
231 pub(crate) fn send_message_to_background_hang_monitor(
234 &self,
235 message: &BackgroundHangMonitorControlMsg,
236 ) {
237 if let Some(background_hang_monitor_sender) = &self.background_hang_monitor_sender {
238 if let Err(error) = background_hang_monitor_sender.send(message.clone()) {
239 error!("Could not send message ({message:?}) to BHM: {error}");
240 }
241 }
242 }
243}
244
245#[derive(Deserialize, Serialize)]
247pub struct NewScriptEventLoopProcessInfo {
248 pub initial_script_state: InitialScriptState,
249 pub constellation_to_bhm_receiver: GenericReceiver<BackgroundHangMonitorControlMsg>,
250 pub bhm_to_constellation_sender: GenericSender<HangMonitorAlert>,
251 pub lifeline_sender: IpcSender<()>,
252 pub opts: Opts,
253 pub prefs: Box<Preferences>,
254 pub broken_image_icon_data: Vec<u8>,
256}