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::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    // This is the last field of the struct to ensure that windows are dropped *after* all other
55    // references to the relevant rendering contexts have been destroyed.
56    // (https://github.com/servo/servo/issues/36711)
57    windows: HashMap<WindowId, Rc<dyn WindowPortsMethods>>,
58}
59
60/// Action to be taken by the caller of [`App::handle_events`].
61pub(crate) enum PumpResult {
62    /// The caller should shut down Servo and its related context.
63    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    /// Initialize Application once event loop start running.
102    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        // Initialize WebDriver server here before `servo` is moved.
165        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    /// Handle events with winit contexts
194    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 in headed mode, request a winit redraw event, so we can paint the minibrowser.
220                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    /// Handle all servo events with headless mode. Return true if the application should
234    /// continue.
235    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        // We should always be in the running state.
247        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    /// Takes any events generated during `egui` updates and performs their actions.
263    fn handle_servoshell_ui_events(&mut self) {
264        let Some(minibrowser) = self.minibrowser.as_mut() else {
265            return;
266        };
267        // We should always be in the running state.
268        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                    // Step 17. Set Width/Height.
420                    window.request_resize(&webview, requested_physical_rect.size());
421
422                    // Step 18. Set position of the window.
423                    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                // This is only received when start new session.
443                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                        // Return success for AcceptAlert and DismissAlert commands.
504                        Ok(alert_text)
505                    } else {
506                        // Return error for AcceptAlert and DismissAlert commands
507                        // if there is no active dialog.
508                        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            // We need to redraw the window for some reason.
567            trace!("RedrawRequested");
568
569            // WARNING: do not defer painting or presenting to some later tick of the event
570            // loop or servoshell may become unresponsive! (servo#30312)
571            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        // Handle the event
578        let mut consumed = false;
579        if let Some(ref mut minibrowser) = self.minibrowser {
580            match event {
581                WindowEvent::ScaleFactorChanged { scale_factor, .. } => {
582                    // Intercept any ScaleFactorChanged events away from EguiGlow::on_window_event, so
583                    // we can use our own logic for calculating the scale factor and set egui’s
584                    // scale factor to that value manually.
585                    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                    // Request a winit redraw event, so we can recomposite, update and paint
598                    // the minibrowser, and present the new frame.
599                    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                    // Update minibrowser if there's resize event to sync up with window.
605                    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                        // Request a winit redraw event, so we can recomposite, update and paint
614                        // the minibrowser, and present the new frame.
615                        window.winit_window().unwrap().request_redraw();
616                    }
617
618                    // TODO how do we handle the tab key? (see doc for consumed)
619                    // Note that servo doesn’t yet support tabbing through links and inputs
620                    consumed = response.consumed;
621                },
622            }
623        }
624        if !consumed {
625            window.handle_winit_event(state.clone(), event);
626        }
627
628        // Block until the window gets an event
629        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        // Consume and handle any events from the servoshell UI.
636        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        // Block until the window gets an event
674        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        // Consume and handle any events from the Minibrowser.
681        self.handle_servoshell_ui_events();
682
683        // Consume and handle any events from the WebDriver.
684        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}