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