1use std::cell::Cell;
8use std::collections::HashMap;
9use std::path::Path;
10use std::rc::Rc;
11use std::time::Instant;
12use std::{env, fs};
13
14use ::servo::ServoBuilder;
15use crossbeam_channel::unbounded;
16use log::{info, trace, warn};
17use net::protocols::ProtocolRegistry;
18use servo::config::opts::Opts;
19use servo::config::prefs::Preferences;
20use servo::servo_url::ServoUrl;
21use servo::user_content_manager::{UserContentManager, UserScript};
22use servo::{EventLoopWaker, WebDriverCommandMsg, WebDriverUserPromptAction};
23use url::Url;
24use winit::application::ApplicationHandler;
25use winit::event::WindowEvent;
26use winit::event_loop::{ActiveEventLoop, ControlFlow};
27use winit::window::WindowId;
28
29use super::app_state::AppState;
30use super::events_loop::{AppEvent, EventLoopProxy, EventsLoop};
31use super::minibrowser::{Minibrowser, MinibrowserEvent};
32use super::{headed_window, headless_window};
33use crate::desktop::app_state::RunningAppState;
34use crate::desktop::protocols;
35use crate::desktop::tracing::trace_winit_event;
36use crate::desktop::window_trait::WindowPortsMethods;
37use crate::parser::{get_default_url, location_bar_input_to_url};
38use crate::prefs::ServoShellPreferences;
39use crate::running_app_state::RunningAppStateTrait;
40
41pub struct App {
42 opts: Opts,
43 preferences: Preferences,
44 servoshell_preferences: ServoShellPreferences,
45 suspended: Cell<bool>,
46 minibrowser: Option<Minibrowser>,
47 waker: Box<dyn EventLoopWaker>,
48 proxy: Option<EventLoopProxy>,
49 initial_url: ServoUrl,
50 t_start: Instant,
51 t: Instant,
52 state: AppState,
53
54 windows: HashMap<WindowId, Rc<dyn WindowPortsMethods>>,
58}
59
60pub(crate) enum PumpResult {
62 Shutdown,
64 Continue {
65 need_update: bool,
66 need_window_redraw: bool,
67 },
68}
69
70impl App {
71 pub fn new(
72 opts: Opts,
73 preferences: Preferences,
74 servo_shell_preferences: ServoShellPreferences,
75 events_loop: &EventsLoop,
76 ) -> Self {
77 let initial_url = get_default_url(
78 servo_shell_preferences.url.as_deref(),
79 env::current_dir().unwrap(),
80 |path| fs::metadata(path).is_ok(),
81 &servo_shell_preferences,
82 );
83
84 let t = Instant::now();
85 App {
86 opts,
87 preferences,
88 servoshell_preferences: servo_shell_preferences,
89 suspended: Cell::new(false),
90 windows: HashMap::new(),
91 minibrowser: None,
92 waker: events_loop.create_event_loop_waker(),
93 proxy: events_loop.event_loop_proxy(),
94 initial_url: initial_url.clone(),
95 t_start: t,
96 t,
97 state: AppState::Initializing,
98 }
99 }
100
101 pub fn init(&mut self, event_loop: Option<&ActiveEventLoop>) {
103 let headless = self.servoshell_preferences.headless;
104
105 assert_eq!(headless, event_loop.is_none());
106 let window = match event_loop {
107 Some(event_loop) => {
108 let proxy = self.proxy.take().expect("Must have a proxy available");
109 let window = headed_window::Window::new(&self.servoshell_preferences, event_loop);
110 self.minibrowser = Some(Minibrowser::new(
111 &window,
112 event_loop,
113 proxy,
114 self.initial_url.clone(),
115 &self.servoshell_preferences,
116 ));
117 Rc::new(window)
118 },
119 None => headless_window::Window::new(&self.servoshell_preferences),
120 };
121
122 self.windows.insert(window.id(), window);
123
124 self.suspended.set(false);
125 let (_, window) = self.windows.iter().next().unwrap();
126
127 let mut user_content_manager = UserContentManager::new();
128 for script in load_userscripts(self.servoshell_preferences.userscripts_directory.as_deref())
129 .expect("Loading userscripts failed")
130 {
131 user_content_manager.add_script(script);
132 }
133
134 let mut protocol_registry = ProtocolRegistry::default();
135 let _ = protocol_registry.register(
136 "urlinfo",
137 protocols::urlinfo::UrlInfoProtocolHander::default(),
138 );
139 let _ =
140 protocol_registry.register("servo", protocols::servo::ServoProtocolHandler::default());
141 let _ = protocol_registry.register(
142 "resource",
143 protocols::resource::ResourceProtocolHandler::default(),
144 );
145
146 let servo_builder = ServoBuilder::new(window.rendering_context())
147 .opts(self.opts.clone())
148 .preferences(self.preferences.clone())
149 .user_content_manager(user_content_manager)
150 .protocol_registry(protocol_registry)
151 .event_loop_waker(self.waker.clone());
152
153 #[cfg(feature = "webxr")]
154 let servo_builder =
155 servo_builder.webxr_registry(super::webxr::XrDiscoveryWebXrRegistry::new_boxed(
156 window.clone(),
157 event_loop,
158 &self.preferences,
159 ));
160
161 let servo = servo_builder.build();
162 servo.setup_logging();
163
164 let webdriver_receiver = self.servoshell_preferences.webdriver_port.map(|port| {
166 let (embedder_sender, embedder_receiver) = unbounded();
167 webdriver_server::start_server(port, embedder_sender, self.waker.clone());
168 embedder_receiver
169 });
170
171 let running_state = Rc::new(RunningAppState::new(
172 servo,
173 window.clone(),
174 self.servoshell_preferences.clone(),
175 webdriver_receiver,
176 ));
177 running_state.create_and_focus_toplevel_webview(self.initial_url.clone().into_url());
178 if let Some(ref mut minibrowser) = self.minibrowser {
179 minibrowser.update(window.as_ref(), &running_state, "init");
180 }
181
182 self.state = AppState::Running(running_state);
183 }
184
185 pub(crate) fn animating(&self) -> bool {
186 match self.state {
187 AppState::Initializing => false,
188 AppState::Running(ref running_app_state) => running_app_state.servo().animating(),
189 AppState::ShuttingDown => false,
190 }
191 }
192
193 pub fn handle_events_with_winit(
195 &mut self,
196 event_loop: &ActiveEventLoop,
197 window: Rc<dyn WindowPortsMethods>,
198 ) {
199 let AppState::Running(state) = &self.state else {
200 return;
201 };
202
203 match state.pump_event_loop() {
204 PumpResult::Shutdown => {
205 state.shutdown();
206 self.state = AppState::ShuttingDown;
207 },
208 PumpResult::Continue {
209 need_update: update,
210 need_window_redraw,
211 } => {
212 let updated = match (update, &mut self.minibrowser) {
213 (true, Some(minibrowser)) => {
214 minibrowser.update_webview_data(state, window.clone())
215 },
216 _ => false,
217 };
218
219 if updated || need_window_redraw {
221 if let Some(window) = window.winit_window() {
222 window.request_redraw();
223 }
224 }
225 },
226 }
227
228 if matches!(self.state, AppState::ShuttingDown) {
229 event_loop.exit();
230 }
231 }
232
233 pub fn handle_events_with_headless(&mut self) -> bool {
236 let now = Instant::now();
237 let event = winit::event::Event::UserEvent(AppEvent::Waker);
238 trace_winit_event!(
239 event,
240 "@{:?} (+{:?}) {event:?}",
241 now - self.t_start,
242 now - self.t
243 );
244 self.t = now;
245
246 let AppState::Running(state) = &self.state else {
248 return false;
249 };
250
251 match state.pump_event_loop() {
252 PumpResult::Shutdown => {
253 state.shutdown();
254 self.state = AppState::ShuttingDown;
255 },
256 PumpResult::Continue { .. } => state.repaint_servo_if_necessary(),
257 }
258
259 !matches!(self.state, AppState::ShuttingDown)
260 }
261
262 fn handle_servoshell_ui_events(&mut self) {
264 let Some(minibrowser) = self.minibrowser.as_mut() else {
265 return;
266 };
267 let AppState::Running(state) = &self.state else {
269 return;
270 };
271
272 for event in minibrowser.take_events() {
273 match event {
274 MinibrowserEvent::Go(location) => {
275 minibrowser.update_location_dirty(false);
276 let Some(url) = location_bar_input_to_url(
277 &location.clone(),
278 &self.servoshell_preferences.searchpage,
279 ) else {
280 warn!("failed to parse location");
281 break;
282 };
283 if let Some(focused_webview) = state.focused_webview() {
284 focused_webview.load(url.into_url());
285 }
286 },
287 MinibrowserEvent::Back => {
288 if let Some(focused_webview) = state.focused_webview() {
289 focused_webview.go_back(1);
290 }
291 },
292 MinibrowserEvent::Forward => {
293 if let Some(focused_webview) = state.focused_webview() {
294 focused_webview.go_forward(1);
295 }
296 },
297 MinibrowserEvent::Reload => {
298 minibrowser.update_location_dirty(false);
299 if let Some(focused_webview) = state.focused_webview() {
300 focused_webview.reload();
301 }
302 },
303 MinibrowserEvent::ReloadAll => {
304 minibrowser.update_location_dirty(false);
305 for (_, webview) in state.webviews() {
306 webview.reload();
307 }
308 },
309 MinibrowserEvent::NewWebView => {
310 minibrowser.update_location_dirty(false);
311 state.create_and_focus_toplevel_webview(Url::parse("servo:newtab").unwrap());
312 },
313 MinibrowserEvent::CloseWebView(id) => {
314 minibrowser.update_location_dirty(false);
315 state.close_webview(id);
316 },
317 }
318 }
319 }
320
321 pub(crate) fn handle_webdriver_messages(&self) {
322 let AppState::Running(running_state) = &self.state else {
323 return;
324 };
325
326 let Some(webdriver_receiver) = running_state.webdriver_receiver() else {
327 return;
328 };
329
330 while let Ok(msg) = webdriver_receiver.try_recv() {
331 match msg {
332 WebDriverCommandMsg::Shutdown => {
333 running_state.servo().start_shutting_down();
334 },
335 WebDriverCommandMsg::IsWebViewOpen(webview_id, sender) => {
336 let context = running_state.webview_by_id(webview_id);
337
338 if let Err(error) = sender.send(context.is_some()) {
339 warn!("Failed to send response of IsWebViewOpen: {error}");
340 }
341 },
342 WebDriverCommandMsg::IsBrowsingContextOpen(..) => {
343 running_state.servo().execute_webdriver_command(msg);
344 },
345 WebDriverCommandMsg::NewWebView(response_sender, load_status_sender) => {
346 let new_webview =
347 running_state.create_toplevel_webview(Url::parse("about:blank").unwrap());
348
349 if let Err(error) = response_sender.send(new_webview.id()) {
350 warn!("Failed to send response of NewWebview: {error}");
351 }
352 if let Some(load_status_sender) = load_status_sender {
353 running_state.set_load_status_sender(new_webview.id(), load_status_sender);
354 }
355 },
356 WebDriverCommandMsg::CloseWebView(webview_id, response_sender) => {
357 running_state.close_webview(webview_id);
358 if let Err(error) = response_sender.send(()) {
359 warn!("Failed to send response of CloseWebView: {error}");
360 }
361 },
362 WebDriverCommandMsg::FocusWebView(webview_id) => {
363 if let Some(webview) = running_state.webview_by_id(webview_id) {
364 webview.focus_and_raise_to_top(true);
365 }
366 },
367 WebDriverCommandMsg::FocusBrowsingContext(..) => {
368 running_state.servo().execute_webdriver_command(msg);
369 },
370 WebDriverCommandMsg::GetAllWebViews(response_sender) => {
371 let webviews = running_state.webviews().iter().map(|(id, _)| *id).collect();
372
373 if let Err(error) = response_sender.send(webviews) {
374 warn!("Failed to send response of GetAllWebViews: {error}");
375 }
376 },
377 WebDriverCommandMsg::GetWindowRect(_webview_id, response_sender) => {
378 let window = self
379 .windows
380 .values()
381 .next()
382 .expect("Should have at least one window in servoshell");
383
384 if let Err(error) = response_sender.send(window.window_rect()) {
385 warn!("Failed to send response of GetWindowSize: {error}");
386 }
387 },
388 WebDriverCommandMsg::MaximizeWebView(webview_id, response_sender) => {
389 let window = self
390 .windows
391 .values()
392 .next()
393 .expect("Should have at least one window in servoshell");
394 window.maximize(
395 &running_state
396 .webview_by_id(webview_id)
397 .expect("Webview must exists as we just verified"),
398 );
399
400 if let Err(error) = response_sender.send(window.window_rect()) {
401 warn!("Failed to send response of GetWindowSize: {error}");
402 }
403 },
404 WebDriverCommandMsg::SetWindowRect(webview_id, requested_rect, size_sender) => {
405 let Some(webview) = running_state.webview_by_id(webview_id) else {
406 continue;
407 };
408
409 let window = self
410 .windows
411 .values()
412 .next()
413 .expect("Should have at least one window in servoshell");
414 let scale = window.hidpi_scale_factor();
415
416 let requested_physical_rect =
417 (requested_rect.to_f32() * scale).round().to_i32();
418
419 window.request_resize(&webview, requested_physical_rect.size());
421
422 window.set_position(requested_physical_rect.min);
424
425 if let Err(error) = size_sender.send(window.window_rect()) {
426 warn!("Failed to send window size: {error}");
427 }
428 },
429 WebDriverCommandMsg::GetViewportSize(_webview_id, response_sender) => {
430 let window = self
431 .windows
432 .values()
433 .next()
434 .expect("Should have at least one window in servoshell");
435
436 let size = window.rendering_context().size2d();
437
438 if let Err(error) = response_sender.send(size) {
439 warn!("Failed to send response of GetViewportSize: {error}");
440 }
441 },
442 WebDriverCommandMsg::GetFocusedWebView(sender) => {
444 let focused_webview = running_state.focused_webview();
445
446 if let Err(error) = sender.send(focused_webview.map(|w| w.id())) {
447 warn!("Failed to send response of GetFocusedWebView: {error}");
448 };
449 },
450 WebDriverCommandMsg::LoadUrl(webview_id, url, load_status_sender) => {
451 running_state.handle_webdriver_load_url(webview_id, url, load_status_sender);
452 },
453 WebDriverCommandMsg::Refresh(webview_id, load_status_sender) => {
454 if let Some(webview) = running_state.webview_by_id(webview_id) {
455 running_state.set_load_status_sender(webview_id, load_status_sender);
456 webview.reload();
457 }
458 },
459 WebDriverCommandMsg::GoBack(webview_id, load_status_sender) => {
460 if let Some(webview) = running_state.webview_by_id(webview_id) {
461 let traversal_id = webview.go_back(1);
462 running_state.set_pending_traversal(traversal_id, load_status_sender);
463 }
464 },
465 WebDriverCommandMsg::GoForward(webview_id, load_status_sender) => {
466 if let Some(webview) = running_state.webview_by_id(webview_id) {
467 let traversal_id = webview.go_forward(1);
468 running_state.set_pending_traversal(traversal_id, load_status_sender);
469 }
470 },
471 WebDriverCommandMsg::InputEvent(webview_id, input_event, response_sender) => {
472 running_state.handle_webdriver_input_event(
473 webview_id,
474 input_event,
475 response_sender,
476 );
477 },
478 WebDriverCommandMsg::ScriptCommand(_, ref webdriver_script_command) => {
479 running_state.handle_webdriver_script_command(webdriver_script_command);
480 running_state.servo().execute_webdriver_command(msg);
481 },
482 WebDriverCommandMsg::CurrentUserPrompt(webview_id, response_sender) => {
483 let current_dialog =
484 running_state.get_current_active_dialog_webdriver_type(webview_id);
485 if let Err(error) = response_sender.send(current_dialog) {
486 warn!("Failed to send response of CurrentUserPrompt: {error}");
487 };
488 },
489 WebDriverCommandMsg::HandleUserPrompt(webview_id, action, response_sender) => {
490 let response = if running_state.webview_has_active_dialog(webview_id) {
491 let alert_text = running_state.alert_text_of_newest_dialog(webview_id);
492
493 match action {
494 WebDriverUserPromptAction::Accept => {
495 running_state.accept_active_dialogs(webview_id)
496 },
497 WebDriverUserPromptAction::Dismiss => {
498 running_state.dismiss_active_dialogs(webview_id)
499 },
500 WebDriverUserPromptAction::Ignore => {},
501 };
502
503 Ok(alert_text)
505 } else {
506 Err(())
509 };
510
511 if let Err(error) = response_sender.send(response) {
512 warn!("Failed to send response of HandleUserPrompt: {error}");
513 };
514 },
515 WebDriverCommandMsg::GetAlertText(webview_id, response_sender) => {
516 let response = match running_state.alert_text_of_newest_dialog(webview_id) {
517 Some(text) => Ok(text),
518 None => Err(()),
519 };
520
521 if let Err(error) = response_sender.send(response) {
522 warn!("Failed to send response of GetAlertText: {error}");
523 };
524 },
525 WebDriverCommandMsg::SendAlertText(webview_id, text) => {
526 running_state.set_alert_text_of_newest_dialog(webview_id, text);
527 },
528 WebDriverCommandMsg::TakeScreenshot(webview_id, rect, result_sender) => {
529 running_state.handle_webdriver_screenshot(webview_id, rect, result_sender);
530 },
531 };
532 }
533 }
534}
535
536impl ApplicationHandler<AppEvent> for App {
537 fn resumed(&mut self, event_loop: &ActiveEventLoop) {
538 self.init(Some(event_loop));
539 }
540
541 fn window_event(
542 &mut self,
543 event_loop: &ActiveEventLoop,
544 window_id: WindowId,
545 event: WindowEvent,
546 ) {
547 let now = Instant::now();
548 trace_winit_event!(
549 event,
550 "@{:?} (+{:?}) {event:?}",
551 now - self.t_start,
552 now - self.t
553 );
554 self.t = now;
555
556 let AppState::Running(state) = &self.state else {
557 return;
558 };
559
560 let Some(window) = self.windows.get(&window_id) else {
561 return;
562 };
563
564 let window = window.clone();
565 if event == WindowEvent::RedrawRequested {
566 trace!("RedrawRequested");
568
569 if let Some(ref mut minibrowser) = self.minibrowser {
572 minibrowser.update(window.as_ref(), state, "RedrawRequested");
573 minibrowser.paint(window.winit_window().unwrap());
574 }
575 }
576
577 let mut consumed = false;
579 if let Some(ref mut minibrowser) = self.minibrowser {
580 match event {
581 WindowEvent::ScaleFactorChanged { scale_factor, .. } => {
582 let desired_scale_factor = window.hidpi_scale_factor().get();
586 let effective_egui_zoom_factor = desired_scale_factor / scale_factor as f32;
587
588 info!(
589 "window scale factor changed to {}, setting egui zoom factor to {}",
590 scale_factor, effective_egui_zoom_factor
591 );
592
593 minibrowser.set_zoom_factor(effective_egui_zoom_factor);
594
595 state.hidpi_scale_factor_changed();
596
597 window.winit_window().unwrap().request_redraw();
600 },
601 ref event => {
602 let response =
603 minibrowser.on_window_event(window.winit_window().unwrap(), state, event);
604 if let WindowEvent::Resized(_) = event {
606 minibrowser.update(
607 window.as_ref(),
608 state,
609 "Sync WebView size with Window Resize event",
610 );
611 }
612 if response.repaint && *event != WindowEvent::RedrawRequested {
613 window.winit_window().unwrap().request_redraw();
616 }
617
618 consumed = response.consumed;
621 },
622 }
623 }
624 if !consumed {
625 window.handle_winit_event(state.clone(), event);
626 }
627
628 if !self.animating() || self.suspended.get() {
630 event_loop.set_control_flow(ControlFlow::Wait);
631 } else {
632 event_loop.set_control_flow(ControlFlow::Poll);
633 }
634
635 self.handle_servoshell_ui_events();
637
638 self.handle_events_with_winit(event_loop, window);
639 }
640
641 fn user_event(&mut self, event_loop: &ActiveEventLoop, event: AppEvent) {
642 if let AppEvent::Accessibility(ref event) = event {
643 let Some(ref mut minibrowser) = self.minibrowser else {
644 return;
645 };
646 if !minibrowser.handle_accesskit_event(&event.window_event) {
647 return;
648 }
649 if let Some(window) = self.windows.get(&event.window_id) {
650 window.winit_window().unwrap().request_redraw();
651 }
652 return;
653 }
654
655 let now = Instant::now();
656 let event = winit::event::Event::UserEvent(event);
657 trace_winit_event!(
658 event,
659 "@{:?} (+{:?}) {event:?}",
660 now - self.t_start,
661 now - self.t
662 );
663 self.t = now;
664
665 if !matches!(self.state, AppState::Running(..)) {
666 return;
667 };
668 let Some(window) = self.windows.values().next() else {
669 return;
670 };
671 let window = window.clone();
672
673 if !self.animating() || self.suspended.get() {
675 event_loop.set_control_flow(ControlFlow::Wait);
676 } else {
677 event_loop.set_control_flow(ControlFlow::Poll);
678 }
679
680 self.handle_servoshell_ui_events();
682
683 self.handle_webdriver_messages();
685
686 self.handle_events_with_winit(event_loop, window);
687 }
688
689 fn suspended(&mut self, _: &ActiveEventLoop) {
690 self.suspended.set(true);
691 }
692}
693
694fn load_userscripts(userscripts_directory: Option<&Path>) -> std::io::Result<Vec<UserScript>> {
695 let mut userscripts = Vec::new();
696 if let Some(userscripts_directory) = &userscripts_directory {
697 let mut files = std::fs::read_dir(userscripts_directory)?
698 .map(|e| e.map(|entry| entry.path()))
699 .collect::<Result<Vec<_>, _>>()?;
700 files.sort_unstable();
701 for file in files {
702 userscripts.push(UserScript {
703 script: std::fs::read_to_string(&file)?,
704 source_file: Some(file),
705 });
706 }
707 }
708 Ok(userscripts)
709}