servoshell/desktop/
app.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//! Application entry point, runs the event loop.
6
7use std::path::Path;
8use std::rc::Rc;
9use std::time::Instant;
10use std::{env, fs};
11
12use log::warn;
13use servo::protocol_handler::ProtocolRegistry;
14use servo::{
15    EventLoopWaker, Opts, Preferences, ServoBuilder, ServoUrl, UserContentManager, UserScript,
16};
17use url::Url;
18use winit::application::ApplicationHandler;
19use winit::event::WindowEvent;
20use winit::event_loop::{ActiveEventLoop, ControlFlow, EventLoopProxy};
21use winit::window::WindowId;
22
23use super::event_loop::AppEvent;
24use super::{headed_window, headless_window};
25use crate::desktop::event_loop::ServoShellEventLoop;
26use crate::desktop::protocols;
27use crate::desktop::tracing::trace_winit_event;
28use crate::parser::{get_default_url, location_bar_input_to_url};
29use crate::prefs::ServoShellPreferences;
30use crate::running_app_state::{RunningAppState, UserInterfaceCommand};
31use crate::window::{PlatformWindow, ServoShellWindow};
32
33pub(crate) enum AppState {
34    Initializing,
35    Running(Rc<RunningAppState>),
36    ShuttingDown,
37}
38
39pub struct App {
40    opts: Opts,
41    preferences: Preferences,
42    servoshell_preferences: ServoShellPreferences,
43    waker: Box<dyn EventLoopWaker>,
44    event_loop_proxy: Option<EventLoopProxy<AppEvent>>,
45    initial_url: ServoUrl,
46    t_start: Instant,
47    t: Instant,
48    state: AppState,
49}
50
51impl App {
52    pub fn new(
53        opts: Opts,
54        preferences: Preferences,
55        servo_shell_preferences: ServoShellPreferences,
56        event_loop: &ServoShellEventLoop,
57    ) -> Self {
58        let initial_url = get_default_url(
59            servo_shell_preferences.url.as_deref(),
60            env::current_dir().unwrap(),
61            |path| fs::metadata(path).is_ok(),
62            &servo_shell_preferences,
63        );
64
65        let t = Instant::now();
66        App {
67            opts,
68            preferences,
69            servoshell_preferences: servo_shell_preferences,
70            waker: event_loop.create_event_loop_waker(),
71            event_loop_proxy: event_loop.event_loop_proxy(),
72            initial_url: initial_url.clone(),
73            t_start: t,
74            t,
75            state: AppState::Initializing,
76        }
77    }
78
79    /// Initialize Application once event loop start running.
80    pub fn init(&mut self, active_event_loop: Option<&ActiveEventLoop>) {
81        let mut user_content_manager = UserContentManager::new();
82        for script in load_userscripts(self.servoshell_preferences.userscripts_directory.as_deref())
83            .expect("Loading userscripts failed")
84        {
85            user_content_manager.add_script(script);
86        }
87
88        let mut protocol_registry = ProtocolRegistry::default();
89        let _ = protocol_registry.register(
90            "urlinfo",
91            protocols::urlinfo::UrlInfoProtocolHander::default(),
92        );
93        let _ =
94            protocol_registry.register("servo", protocols::servo::ServoProtocolHandler::default());
95        let _ = protocol_registry.register(
96            "resource",
97            protocols::resource::ResourceProtocolHandler::default(),
98        );
99
100        let servo_builder = ServoBuilder::default()
101            .opts(self.opts.clone())
102            .preferences(self.preferences.clone())
103            .user_content_manager(user_content_manager)
104            .protocol_registry(protocol_registry)
105            .event_loop_waker(self.waker.clone());
106
107        let url = self.initial_url.as_url().clone();
108        let platform_window = self.create_platform_window(url, active_event_loop);
109
110        #[cfg(feature = "webxr")]
111        let servo_builder =
112            servo_builder.webxr_registry(super::webxr::XrDiscoveryWebXrRegistry::new_boxed(
113                platform_window.clone(),
114                active_event_loop,
115                &self.preferences,
116            ));
117
118        let servo = servo_builder.build();
119        servo.setup_logging();
120
121        let running_state = Rc::new(RunningAppState::new(
122            servo,
123            self.servoshell_preferences.clone(),
124            self.waker.clone(),
125        ));
126        running_state.open_window(platform_window, self.initial_url.as_url().clone());
127
128        self.state = AppState::Running(running_state);
129    }
130
131    fn create_platform_window(
132        &self,
133        url: Url,
134        active_event_loop: Option<&ActiveEventLoop>,
135    ) -> Rc<dyn PlatformWindow> {
136        assert_eq!(
137            self.servoshell_preferences.headless,
138            active_event_loop.is_none()
139        );
140
141        let Some(active_event_loop) = active_event_loop else {
142            return headless_window::Window::new(&self.servoshell_preferences);
143        };
144
145        headed_window::Window::new(
146            &self.servoshell_preferences,
147            active_event_loop,
148            self.event_loop_proxy
149                .clone()
150                .expect("Should always have event loop proxy in headed mode."),
151            url,
152        )
153    }
154
155    pub fn pump_servo_event_loop(&mut self, active_event_loop: Option<&ActiveEventLoop>) -> bool {
156        let AppState::Running(state) = &self.state else {
157            return false;
158        };
159
160        state.foreach_window_and_interface_commands(|window, commands| {
161            self.handle_interface_commands_for_window(active_event_loop, state, window, commands);
162        });
163
164        if !state.spin_event_loop() {
165            self.state = AppState::ShuttingDown;
166            return false;
167        }
168        true
169    }
170
171    /// Takes any events generated during `egui` updates and performs their actions.
172    fn handle_interface_commands_for_window(
173        &self,
174        active_event_loop: Option<&ActiveEventLoop>,
175        state: &Rc<RunningAppState>,
176        window: &ServoShellWindow,
177        commands: Vec<UserInterfaceCommand>,
178    ) {
179        for event in commands {
180            match event {
181                UserInterfaceCommand::Go(location) => {
182                    window.set_needs_update();
183                    let Some(url) = location_bar_input_to_url(
184                        &location.clone(),
185                        &state.servoshell_preferences.searchpage,
186                    ) else {
187                        warn!("failed to parse location");
188                        break;
189                    };
190                    if let Some(active_webview) = window.active_webview() {
191                        active_webview.load(url.into_url());
192                    }
193                },
194                UserInterfaceCommand::Back => {
195                    if let Some(active_webview) = window.active_webview() {
196                        active_webview.go_back(1);
197                    }
198                },
199                UserInterfaceCommand::Forward => {
200                    if let Some(active_webview) = window.active_webview() {
201                        active_webview.go_forward(1);
202                    }
203                },
204                UserInterfaceCommand::Reload => {
205                    window.set_needs_update();
206                    if let Some(active_webview) = window.active_webview() {
207                        active_webview.reload();
208                    }
209                },
210                UserInterfaceCommand::ReloadAll => {
211                    for window in state.windows().values() {
212                        window.set_needs_update();
213                        for (_, webview) in window.webviews() {
214                            webview.reload();
215                        }
216                    }
217                },
218                UserInterfaceCommand::NewWebView => {
219                    window.set_needs_update();
220                    let url = Url::parse("servo:newtab").expect("Should always be able to parse");
221                    window.create_and_activate_toplevel_webview(state.clone(), url);
222                },
223                UserInterfaceCommand::CloseWebView(id) => {
224                    window.set_needs_update();
225                    window.close_webview(id);
226                },
227                UserInterfaceCommand::NewWindow => {
228                    let url = Url::parse("servo:newtab").unwrap();
229                    let platform_window =
230                        self.create_platform_window(url.clone(), active_event_loop);
231                    state.open_window(platform_window, url);
232                },
233            }
234        }
235    }
236}
237
238impl ApplicationHandler<AppEvent> for App {
239    fn resumed(&mut self, event_loop: &ActiveEventLoop) {
240        self.init(Some(event_loop));
241    }
242
243    fn window_event(
244        &mut self,
245        event_loop: &ActiveEventLoop,
246        window_id: WindowId,
247        window_event: WindowEvent,
248    ) {
249        let now = Instant::now();
250        trace_winit_event!(
251            window_event,
252            "@{:?} (+{:?}) {window_event:?}",
253            now - self.t_start,
254            now - self.t
255        );
256        self.t = now;
257
258        {
259            let AppState::Running(state) = &self.state else {
260                return;
261            };
262            let window_id: u64 = window_id.into();
263            if let Some(window) = state.window(window_id.into()) {
264                window.platform_window().handle_winit_window_event(
265                    state.clone(),
266                    &window,
267                    window_event,
268                );
269            }
270        }
271
272        if !self.pump_servo_event_loop(event_loop.into()) {
273            event_loop.exit();
274        }
275        // Block until the window gets an event
276        event_loop.set_control_flow(ControlFlow::Wait);
277    }
278
279    fn user_event(&mut self, event_loop: &ActiveEventLoop, app_event: AppEvent) {
280        {
281            let AppState::Running(state) = &self.state else {
282                return;
283            };
284            if let Some(window_id) = app_event.window_id() {
285                let window_id: u64 = window_id.into();
286                if let Some(window) = state.window(window_id.into()) {
287                    window.platform_window().handle_winit_app_event(app_event);
288                }
289            }
290        }
291
292        if !self.pump_servo_event_loop(event_loop.into()) {
293            event_loop.exit();
294        }
295
296        // Block until the window gets an event
297        event_loop.set_control_flow(ControlFlow::Wait);
298    }
299}
300
301fn load_userscripts(userscripts_directory: Option<&Path>) -> std::io::Result<Vec<UserScript>> {
302    let mut userscripts = Vec::new();
303    if let Some(userscripts_directory) = &userscripts_directory {
304        let mut files = std::fs::read_dir(userscripts_directory)?
305            .map(|e| e.map(|entry| entry.path()))
306            .collect::<Result<Vec<_>, _>>()?;
307        files.sort_unstable();
308        for file in files {
309            userscripts.push(UserScript {
310                script: std::fs::read_to_string(&file)?,
311                source_file: Some(file),
312            });
313        }
314    }
315    Ok(userscripts)
316}