1use 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 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 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 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 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}