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, 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
34/// <https://html.spec.whatwg.org/multipage/#event-loop>
35pub struct EventLoop {
36    script_chan: GenericSender<ScriptThreadMessage>,
37    id: ScriptEventLoopId,
38    /// When running in another process, this is an `IpcSender` to the BackgroundHangMonitor
39    /// on the other side of the process boundary. When running in the same process, the
40    /// BackgroundHangMonitor is shared among all [`EventLoop`]s so this will be `None`.
41    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        // Route messages coming from content to devtools as appropriate.
78        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    /// Send a message to the event loop.
225    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    /// If this is [`EventLoop`] is in another process, send a message to its `BackgroundHangMonitor`,
232    /// otherwise do nothing.
233    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/// All of the information necessary to create a new script [`EventLoop`] in a new process.
246#[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    /// The broken image icon data that is used to create an image to show in place of broken images.
255    pub broken_image_icon_data: Vec<u8>,
256}