servoshell/desktop/
events_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//! An event loop implementation that works in headless mode.
6
7use std::sync::{Arc, Condvar, Mutex};
8use std::time;
9
10use log::warn;
11use servo::EventLoopWaker;
12use winit::error::EventLoopError;
13use winit::event_loop::EventLoop as WinitEventLoop;
14#[cfg(target_os = "macos")]
15use winit::platform::macos::{ActivationPolicy, EventLoopBuilderExtMacOS};
16
17use super::app::App;
18
19pub type EventLoopProxy = winit::event_loop::EventLoopProxy<AppEvent>;
20
21#[derive(Debug)]
22pub enum AppEvent {
23    /// Another process or thread has kicked the OS event loop with EventLoopWaker.
24    Waker,
25    Accessibility(accesskit_winit::Event),
26}
27
28impl From<accesskit_winit::Event> for AppEvent {
29    fn from(event: accesskit_winit::Event) -> AppEvent {
30        AppEvent::Accessibility(event)
31    }
32}
33
34/// The real or fake OS event loop.
35#[allow(dead_code)]
36#[allow(clippy::large_enum_variant)]
37enum EventLoop {
38    /// A real Winit windowing event loop.
39    Winit(winit::event_loop::EventLoop<AppEvent>),
40    /// A fake event loop which contains a signalling flag used to ensure
41    /// that pending events get processed in a timely fashion, and a condition
42    /// variable to allow waiting on that flag changing state.
43    Headless(Arc<(Mutex<bool>, Condvar)>),
44}
45
46pub struct EventsLoop(EventLoop);
47
48impl EventsLoop {
49    // Ideally, we could use the winit event loop in both modes,
50    // but on Linux, the event loop requires a X11 server.
51    #[cfg(not(any(target_os = "linux", target_os = "macos", target_os = "windows")))]
52    pub fn new(_headless: bool, _has_output_file: bool) -> Result<EventsLoop, EventLoopError> {
53        Ok(EventsLoop(EventLoop::Winit(
54            WinitEventLoop::with_user_event().build()?,
55        )))
56    }
57    #[cfg(any(target_os = "linux", target_os = "windows"))]
58    pub fn new(headless: bool, _has_output_file: bool) -> Result<EventsLoop, EventLoopError> {
59        Ok(EventsLoop(if headless {
60            EventLoop::Headless(Arc::new((Mutex::new(false), Condvar::new())))
61        } else {
62            EventLoop::Winit(WinitEventLoop::with_user_event().build()?)
63        }))
64    }
65    #[cfg(target_os = "macos")]
66    pub fn new(headless: bool, _has_output_file: bool) -> Result<EventsLoop, EventLoopError> {
67        Ok(EventsLoop(if headless {
68            EventLoop::Headless(Arc::new((Mutex::new(false), Condvar::new())))
69        } else {
70            let mut event_loop_builder = WinitEventLoop::with_user_event();
71            if _has_output_file {
72                // Prevent the window from showing in Dock.app, stealing focus,
73                // when generating an output file.
74                event_loop_builder.with_activation_policy(ActivationPolicy::Prohibited);
75            }
76            EventLoop::Winit(event_loop_builder.build()?)
77        }))
78    }
79}
80
81impl EventsLoop {
82    pub(crate) fn event_loop_proxy(&self) -> Option<EventLoopProxy> {
83        match self.0 {
84            EventLoop::Winit(ref events_loop) => Some(events_loop.create_proxy()),
85            EventLoop::Headless(..) => None,
86        }
87    }
88
89    pub fn create_event_loop_waker(&self) -> Box<dyn EventLoopWaker> {
90        match self.0 {
91            EventLoop::Winit(ref events_loop) => Box::new(HeadedEventLoopWaker::new(events_loop)),
92            EventLoop::Headless(ref data) => Box::new(HeadlessEventLoopWaker(data.clone())),
93        }
94    }
95
96    pub fn run_app(self, app: &mut App) {
97        match self.0 {
98            EventLoop::Winit(events_loop) => {
99                events_loop
100                    .run_app(app)
101                    .expect("Failed while running events loop");
102            },
103            EventLoop::Headless(ref data) => {
104                let (flag, condvar) = &**data;
105
106                app.init(None);
107                loop {
108                    self.sleep(flag, condvar);
109                    app.handle_webdriver_messages();
110                    if !app.handle_events_with_headless() {
111                        break;
112                    }
113                    if !app.animating() {
114                        *flag.lock().unwrap() = false;
115                    }
116                }
117            },
118        }
119    }
120
121    fn sleep(&self, lock: &Mutex<bool>, condvar: &Condvar) {
122        // To avoid sleeping when we should be processing events, do two things:
123        // * before sleeping, check whether our signalling flag has been set
124        // * wait on a condition variable with a maximum timeout, to allow
125        //   being woken up by any signals that occur while sleeping.
126        let guard = lock.lock().unwrap();
127        if *guard {
128            return;
129        }
130        let _ = condvar
131            .wait_timeout(guard, time::Duration::from_millis(5))
132            .unwrap();
133    }
134}
135
136struct HeadedEventLoopWaker {
137    proxy: Arc<Mutex<winit::event_loop::EventLoopProxy<AppEvent>>>,
138}
139impl HeadedEventLoopWaker {
140    fn new(events_loop: &winit::event_loop::EventLoop<AppEvent>) -> HeadedEventLoopWaker {
141        let proxy = Arc::new(Mutex::new(events_loop.create_proxy()));
142        HeadedEventLoopWaker { proxy }
143    }
144}
145impl EventLoopWaker for HeadedEventLoopWaker {
146    fn wake(&self) {
147        // Kick the OS event loop awake.
148        if let Err(err) = self.proxy.lock().unwrap().send_event(AppEvent::Waker) {
149            warn!("Failed to wake up event loop ({}).", err);
150        }
151    }
152    fn clone_box(&self) -> Box<dyn EventLoopWaker> {
153        Box::new(HeadedEventLoopWaker {
154            proxy: self.proxy.clone(),
155        })
156    }
157}
158
159struct HeadlessEventLoopWaker(Arc<(Mutex<bool>, Condvar)>);
160impl EventLoopWaker for HeadlessEventLoopWaker {
161    fn wake(&self) {
162        // Set the signalling flag and notify the condition variable.
163        // This ensures that any sleep operation is interrupted,
164        // and any non-sleeping operation will have a change to check
165        // the flag before going to sleep.
166        let (ref flag, ref condvar) = *self.0;
167        let mut flag = flag.lock().unwrap();
168        *flag = true;
169        condvar.notify_all();
170    }
171    fn clone_box(&self) -> Box<dyn EventLoopWaker> {
172        Box::new(HeadlessEventLoopWaker(self.0.clone()))
173    }
174}