Skip to main content

servo_constellation/
event_loop.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5//! This module contains the `EventLoop` type, which is the constellation's
6//! view of a script thread. When an `EventLoop` is dropped, an `ExitScriptThread`
7//! message is sent to the script thread, asking it to shut down.
8
9use 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
30/// <https://html.spec.whatwg.org/multipage/#event-loop>
31pub struct EventLoop {
32    script_chan: GenericSender<ScriptThreadMessage>,
33    id: ScriptEventLoopId,
34    /// When running in another process, this is an `IpcSender` to the BackgroundHangMonitor
35    /// on the other side of the process boundary. When running in the same process, the
36    /// BackgroundHangMonitor is shared among all [`EventLoop`]s so this will be `None`.
37    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    /// Send a message to the event loop.
202    pub fn send(&self, msg: ScriptThreadMessage) -> Result<(), SendError> {
203        self.script_chan.send(msg)
204    }
205
206    /// If this is [`EventLoop`] is in another process, send a message to its `BackgroundHangMonitor`,
207    /// otherwise do nothing.
208    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/// All of the information necessary to create a new script [`EventLoop`] in a new process.
221#[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    /// The broken image icon data that is used to create an image to show in place of broken images.
231    pub broken_image_icon_data: Vec<u8>,
232}