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 constellation_traits::EmbedderToConstellationMessage;
16use crossbeam_channel::unbounded;
17use euclid::{Point2D, Vector2D};
18use ipc_channel::ipc;
19use log::{info, trace, warn};
20use net::protocols::ProtocolRegistry;
21use servo::config::opts::Opts;
22use servo::config::prefs::Preferences;
23use servo::servo_url::ServoUrl;
24use servo::user_content_manager::{UserContentManager, UserScript};
25use servo::webrender_api::ScrollLocation;
26use servo::{
27    EventLoopWaker, ImeEvent, InputEvent, KeyboardEvent, MouseButtonEvent, MouseMoveEvent,
28    WebDriverCommandMsg, WebDriverScriptCommand, WebDriverUserPromptAction, WheelDelta, WheelEvent,
29    WheelMode,
30};
31use url::Url;
32use winit::application::ApplicationHandler;
33use winit::event::WindowEvent;
34use winit::event_loop::{ActiveEventLoop, ControlFlow};
35use winit::window::WindowId;
36
37use super::app_state::AppState;
38use super::events_loop::{AppEvent, EventLoopProxy, EventsLoop};
39use super::minibrowser::{Minibrowser, MinibrowserEvent};
40use super::{headed_window, headless_window};
41use crate::desktop::app_state::RunningAppState;
42use crate::desktop::protocols;
43use crate::desktop::tracing::trace_winit_event;
44use crate::desktop::window_trait::WindowPortsMethods;
45use crate::parser::{get_default_url, location_bar_input_to_url};
46use crate::prefs::ServoShellPreferences;
47
48pub struct App {
49    opts: Opts,
50    preferences: Preferences,
51    servoshell_preferences: ServoShellPreferences,
52    suspended: Cell<bool>,
53    minibrowser: Option<Minibrowser>,
54    waker: Box<dyn EventLoopWaker>,
55    proxy: Option<EventLoopProxy>,
56    initial_url: ServoUrl,
57    t_start: Instant,
58    t: Instant,
59    state: AppState,
60
61    // This is the last field of the struct to ensure that windows are dropped *after* all other
62    // references to the relevant rendering contexts have been destroyed.
63    // (https://github.com/servo/servo/issues/36711)
64    windows: HashMap<WindowId, Rc<dyn WindowPortsMethods>>,
65}
66
67/// Action to be taken by the caller of [`App::handle_events`].
68pub(crate) enum PumpResult {
69    /// The caller should shut down Servo and its related context.
70    Shutdown,
71    Continue {
72        need_update: bool,
73        need_window_redraw: bool,
74    },
75}
76
77impl App {
78    pub fn new(
79        opts: Opts,
80        preferences: Preferences,
81        servo_shell_preferences: ServoShellPreferences,
82        events_loop: &EventsLoop,
83    ) -> Self {
84        let initial_url = get_default_url(
85            servo_shell_preferences.url.as_deref(),
86            env::current_dir().unwrap(),
87            |path| fs::metadata(path).is_ok(),
88            &servo_shell_preferences,
89        );
90
91        let t = Instant::now();
92        App {
93            opts,
94            preferences,
95            servoshell_preferences: servo_shell_preferences,
96            suspended: Cell::new(false),
97            windows: HashMap::new(),
98            minibrowser: None,
99            waker: events_loop.create_event_loop_waker(),
100            proxy: events_loop.event_loop_proxy(),
101            initial_url: initial_url.clone(),
102            t_start: t,
103            t,
104            state: AppState::Initializing,
105        }
106    }
107
108    /// Initialize Application once event loop start running.
109    pub fn init(&mut self, event_loop: Option<&ActiveEventLoop>) {
110        let headless = self.servoshell_preferences.headless;
111
112        assert_eq!(headless, event_loop.is_none());
113        let window = match event_loop {
114            Some(event_loop) => {
115                let proxy = self.proxy.take().expect("Must have a proxy available");
116                let window = headed_window::Window::new(&self.servoshell_preferences, event_loop);
117                self.minibrowser = Some(Minibrowser::new(
118                    &window,
119                    event_loop,
120                    proxy,
121                    self.initial_url.clone(),
122                ));
123                Rc::new(window)
124            },
125            None => headless_window::Window::new(&self.servoshell_preferences),
126        };
127
128        self.windows.insert(window.id(), window);
129
130        self.suspended.set(false);
131        let (_, window) = self.windows.iter().next().unwrap();
132
133        let mut user_content_manager = UserContentManager::new();
134        for script in load_userscripts(self.servoshell_preferences.userscripts_directory.as_deref())
135            .expect("Loading userscripts failed")
136        {
137            user_content_manager.add_script(script);
138        }
139
140        let mut protocol_registry = ProtocolRegistry::default();
141        let _ = protocol_registry.register(
142            "urlinfo",
143            protocols::urlinfo::UrlInfoProtocolHander::default(),
144        );
145        let _ =
146            protocol_registry.register("servo", protocols::servo::ServoProtocolHandler::default());
147        let _ = protocol_registry.register(
148            "resource",
149            protocols::resource::ResourceProtocolHandler::default(),
150        );
151
152        let servo_builder = ServoBuilder::new(window.rendering_context())
153            .opts(self.opts.clone())
154            .preferences(self.preferences.clone())
155            .user_content_manager(user_content_manager)
156            .protocol_registry(protocol_registry)
157            .event_loop_waker(self.waker.clone());
158
159        #[cfg(feature = "webxr")]
160        let servo_builder =
161            servo_builder.webxr_registry(super::webxr::XrDiscoveryWebXrRegistry::new_boxed(
162                window.clone(),
163                event_loop,
164                &self.preferences,
165            ));
166
167        let servo = servo_builder.build();
168        servo.setup_logging();
169
170        // Initialize WebDriver server here before `servo` is moved.
171        let webdriver_receiver = self.servoshell_preferences.webdriver_port.map(|port| {
172            let (embedder_sender, embedder_receiver) = unbounded();
173            let (webdriver_response_sender, webdriver_response_receiver) = ipc::channel().unwrap();
174
175            // Set the WebDriver response sender to constellation.
176            // TODO: consider using Servo API to notify embedder about input events completions
177            servo
178                .constellation_sender()
179                .send(EmbedderToConstellationMessage::SetWebDriverResponseSender(
180                    webdriver_response_sender,
181                ))
182                .expect("Failed to set WebDriver response sender in constellation when initing");
183
184            webdriver_server::start_server(
185                port,
186                embedder_sender,
187                self.waker.clone(),
188                webdriver_response_receiver,
189            );
190
191            embedder_receiver
192        });
193
194        let running_state = Rc::new(RunningAppState::new(
195            servo,
196            window.clone(),
197            self.servoshell_preferences.clone(),
198            webdriver_receiver,
199        ));
200        running_state.create_and_focus_toplevel_webview(self.initial_url.clone().into_url());
201        if let Some(ref mut minibrowser) = self.minibrowser {
202            minibrowser.update(window.as_ref(), &running_state, "init");
203        }
204
205        self.state = AppState::Running(running_state);
206    }
207
208    pub(crate) fn animating(&self) -> bool {
209        match self.state {
210            AppState::Initializing => false,
211            AppState::Running(ref running_app_state) => running_app_state.servo().animating(),
212            AppState::ShuttingDown => false,
213        }
214    }
215
216    /// Handle events with winit contexts
217    pub fn handle_events_with_winit(
218        &mut self,
219        event_loop: &ActiveEventLoop,
220        window: Rc<dyn WindowPortsMethods>,
221    ) {
222        let AppState::Running(state) = &self.state else {
223            return;
224        };
225
226        match state.pump_event_loop() {
227            PumpResult::Shutdown => {
228                state.shutdown();
229                self.state = AppState::ShuttingDown;
230            },
231            PumpResult::Continue {
232                need_update: update,
233                need_window_redraw,
234            } => {
235                let updated = match (update, &mut self.minibrowser) {
236                    (true, Some(minibrowser)) => minibrowser.update_webview_data(state),
237                    _ => false,
238                };
239
240                // If in headed mode, request a winit redraw event, so we can paint the minibrowser.
241                if updated || need_window_redraw {
242                    if let Some(window) = window.winit_window() {
243                        window.request_redraw();
244                    }
245                }
246            },
247        }
248
249        if matches!(self.state, AppState::ShuttingDown) {
250            event_loop.exit();
251        }
252    }
253
254    /// Handle all servo events with headless mode. Return true if the application should
255    /// continue.
256    pub fn handle_events_with_headless(&mut self) -> bool {
257        let now = Instant::now();
258        let event = winit::event::Event::UserEvent(AppEvent::Waker);
259        trace_winit_event!(
260            event,
261            "@{:?} (+{:?}) {event:?}",
262            now - self.t_start,
263            now - self.t
264        );
265        self.t = now;
266
267        // We should always be in the running state.
268        let AppState::Running(state) = &self.state else {
269            return false;
270        };
271
272        match state.pump_event_loop() {
273            PumpResult::Shutdown => {
274                state.shutdown();
275                self.state = AppState::ShuttingDown;
276            },
277            PumpResult::Continue { .. } => state.repaint_servo_if_necessary(),
278        }
279
280        !matches!(self.state, AppState::ShuttingDown)
281    }
282
283    /// Takes any events generated during `egui` updates and performs their actions.
284    fn handle_servoshell_ui_events(&mut self) {
285        let Some(minibrowser) = self.minibrowser.as_ref() else {
286            return;
287        };
288        // We should always be in the running state.
289        let AppState::Running(state) = &self.state else {
290            return;
291        };
292
293        for event in minibrowser.take_events() {
294            match event {
295                MinibrowserEvent::Go(location) => {
296                    minibrowser.update_location_dirty(false);
297                    let Some(url) = location_bar_input_to_url(
298                        &location.clone(),
299                        &self.servoshell_preferences.searchpage,
300                    ) else {
301                        warn!("failed to parse location");
302                        break;
303                    };
304                    if let Some(focused_webview) = state.focused_webview() {
305                        focused_webview.load(url.into_url());
306                    }
307                },
308                MinibrowserEvent::Back => {
309                    if let Some(focused_webview) = state.focused_webview() {
310                        focused_webview.go_back(1);
311                    }
312                },
313                MinibrowserEvent::Forward => {
314                    if let Some(focused_webview) = state.focused_webview() {
315                        focused_webview.go_forward(1);
316                    }
317                },
318                MinibrowserEvent::Reload => {
319                    minibrowser.update_location_dirty(false);
320                    if let Some(focused_webview) = state.focused_webview() {
321                        focused_webview.reload();
322                    }
323                },
324                MinibrowserEvent::NewWebView => {
325                    minibrowser.update_location_dirty(false);
326                    state.create_and_focus_toplevel_webview(Url::parse("servo:newtab").unwrap());
327                },
328                MinibrowserEvent::CloseWebView(id) => {
329                    minibrowser.update_location_dirty(false);
330                    state.close_webview(id);
331                },
332            }
333        }
334    }
335
336    pub fn handle_webdriver_messages(&self) {
337        let AppState::Running(running_state) = &self.state else {
338            return;
339        };
340
341        let Some(webdriver_receiver) = running_state.webdriver_receiver() else {
342            return;
343        };
344
345        while let Ok(msg) = webdriver_receiver.try_recv() {
346            match msg {
347                WebDriverCommandMsg::SetWebDriverResponseSender(..) => {
348                    running_state.servo().execute_webdriver_command(msg);
349                },
350                WebDriverCommandMsg::IsWebViewOpen(webview_id, sender) => {
351                    let context = running_state.webview_by_id(webview_id);
352
353                    if let Err(error) = sender.send(context.is_some()) {
354                        warn!("Failed to send response of IsWebViewOpen: {error}");
355                    }
356                },
357                WebDriverCommandMsg::IsBrowsingContextOpen(..) => {
358                    running_state.servo().execute_webdriver_command(msg);
359                },
360                WebDriverCommandMsg::NewWebView(response_sender, load_status_sender) => {
361                    let new_webview =
362                        running_state.create_toplevel_webview(Url::parse("about:blank").unwrap());
363
364                    if let Err(error) = response_sender.send(new_webview.id()) {
365                        warn!("Failed to send response of NewWebview: {error}");
366                    }
367                    if let Some(load_status_sender) = load_status_sender {
368                        running_state.set_load_status_sender(new_webview.id(), load_status_sender);
369                    }
370                },
371                WebDriverCommandMsg::CloseWebView(webview_id, response_sender) => {
372                    running_state.close_webview(webview_id);
373                    if let Err(error) = response_sender.send(()) {
374                        warn!("Failed to send response of CloseWebView: {error}");
375                    }
376                },
377                WebDriverCommandMsg::FocusWebView(webview_id, response_sender) => {
378                    if let Some(webview) = running_state.webview_by_id(webview_id) {
379                        let focus_id = webview.focus_and_raise_to_top(true);
380                        running_state.set_pending_focus(focus_id, response_sender);
381                    }
382                },
383                WebDriverCommandMsg::GetAllWebViews(response_sender) => {
384                    let webviews = running_state.webviews().iter().map(|(id, _)| *id).collect();
385
386                    if let Err(error) = response_sender.send(webviews) {
387                        warn!("Failed to send response of GetAllWebViews: {error}");
388                    }
389                },
390                WebDriverCommandMsg::GetWindowRect(_webview_id, response_sender) => {
391                    let window = self
392                        .windows
393                        .values()
394                        .next()
395                        .expect("Should have at least one window in servoshell");
396
397                    if let Err(error) = response_sender.send(window.window_rect()) {
398                        warn!("Failed to send response of GetWindowSize: {error}");
399                    }
400                },
401                WebDriverCommandMsg::MaximizeWebView(webview_id, response_sender) => {
402                    let window = self
403                        .windows
404                        .values()
405                        .next()
406                        .expect("Should have at least one window in servoshell");
407                    window.maximize(
408                        &running_state
409                            .webview_by_id(webview_id)
410                            .expect("Webview must exists as we just verified"),
411                    );
412
413                    if let Err(error) = response_sender.send(window.window_rect()) {
414                        warn!("Failed to send response of GetWindowSize: {error}");
415                    }
416                },
417                WebDriverCommandMsg::SetWindowRect(webview_id, requested_rect, size_sender) => {
418                    let Some(webview) = running_state.webview_by_id(webview_id) else {
419                        continue;
420                    };
421
422                    let window = self
423                        .windows
424                        .values()
425                        .next()
426                        .expect("Should have at least one window in servoshell");
427                    let scale = window.hidpi_scale_factor();
428
429                    let requested_physical_rect =
430                        (requested_rect.to_f32() * scale).round().to_i32();
431
432                    // Step 17. Set Width/Height.
433                    window.request_resize(&webview, requested_physical_rect.size());
434
435                    // Step 18. Set position of the window.
436                    window.set_position(requested_physical_rect.min);
437
438                    if let Err(error) = size_sender.send(window.window_rect()) {
439                        warn!("Failed to send window size: {error}");
440                    }
441                },
442                WebDriverCommandMsg::GetViewportSize(_webview_id, response_sender) => {
443                    let window = self
444                        .windows
445                        .values()
446                        .next()
447                        .expect("Should have at least one window in servoshell");
448
449                    let size = window.rendering_context().size2d();
450
451                    if let Err(error) = response_sender.send(size) {
452                        warn!("Failed to send response of GetViewportSize: {error}");
453                    }
454                },
455                // This is only received when start new session.
456                WebDriverCommandMsg::GetFocusedWebView(sender) => {
457                    let focused_webview = running_state.focused_webview();
458
459                    if let Err(error) = sender.send(focused_webview.map(|w| w.id())) {
460                        warn!("Failed to send response of GetFocusedWebView: {error}");
461                    };
462                },
463                WebDriverCommandMsg::LoadUrl(webview_id, url, load_status_sender) => {
464                    if let Some(webview) = running_state.webview_by_id(webview_id) {
465                        running_state.set_load_status_sender(webview_id, load_status_sender);
466                        webview.load(url.into_url());
467                    }
468                },
469                WebDriverCommandMsg::Refresh(webview_id, load_status_sender) => {
470                    if let Some(webview) = running_state.webview_by_id(webview_id) {
471                        running_state.set_load_status_sender(webview_id, load_status_sender);
472                        webview.reload();
473                    }
474                },
475                WebDriverCommandMsg::GoBack(webview_id, load_status_sender) => {
476                    if let Some(webview) = running_state.webview_by_id(webview_id) {
477                        let traversal_id = webview.go_back(1);
478                        running_state.set_pending_traversal(traversal_id, load_status_sender);
479                    }
480                },
481                WebDriverCommandMsg::GoForward(webview_id, load_status_sender) => {
482                    if let Some(webview) = running_state.webview_by_id(webview_id) {
483                        let traversal_id = webview.go_forward(1);
484                        running_state.set_pending_traversal(traversal_id, load_status_sender);
485                    }
486                },
487                // Key events don't need hit test so can be forwarded to constellation for now
488                WebDriverCommandMsg::DispatchComposition(webview_id, composition_event) => {
489                    if let Some(webview) = running_state.webview_by_id(webview_id) {
490                        webview.notify_input_event(InputEvent::Ime(ImeEvent::Composition(
491                            composition_event,
492                        )));
493                    }
494                },
495                WebDriverCommandMsg::KeyboardAction(webview_id, key_event, msg_id) => {
496                    // TODO: We should do processing like in `headed_window:handle_keyboard_input`.
497                    if let Some(webview) = running_state.webview_by_id(webview_id) {
498                        webview.notify_input_event(
499                            InputEvent::Keyboard(KeyboardEvent::new(key_event))
500                                .with_webdriver_message_id(msg_id),
501                        );
502                    }
503                },
504                WebDriverCommandMsg::MouseButtonAction(
505                    webview_id,
506                    mouse_event_type,
507                    mouse_button,
508                    x,
509                    y,
510                    webdriver_message_id,
511                ) => {
512                    if let Some(webview) = running_state.webview_by_id(webview_id) {
513                        webview.notify_input_event(
514                            InputEvent::MouseButton(MouseButtonEvent::new(
515                                mouse_event_type,
516                                mouse_button,
517                                Point2D::new(x, y),
518                            ))
519                            .with_webdriver_message_id(webdriver_message_id),
520                        );
521                    }
522                },
523                WebDriverCommandMsg::MouseMoveAction(webview_id, x, y, webdriver_message_id) => {
524                    if let Some(webview) = running_state.webview_by_id(webview_id) {
525                        webview.notify_input_event(
526                            InputEvent::MouseMove(MouseMoveEvent::new(Point2D::new(x, y)))
527                                .with_webdriver_message_id(webdriver_message_id),
528                        );
529                    }
530                },
531                WebDriverCommandMsg::WheelScrollAction(
532                    webview_id,
533                    x,
534                    y,
535                    dx,
536                    dy,
537                    webdriver_message_id,
538                ) => {
539                    if let Some(webview) = running_state.webview_by_id(webview_id) {
540                        let delta = WheelDelta {
541                            x: -dx,
542                            y: -dy,
543                            z: 0.0,
544                            mode: WheelMode::DeltaPixel,
545                        };
546
547                        let point = Point2D::new(x, y);
548                        let scroll_location =
549                            ScrollLocation::Delta(Vector2D::new(dx as f32, dy as f32));
550
551                        webview.notify_input_event(
552                            InputEvent::Wheel(WheelEvent::new(delta, point.to_f32()))
553                                .with_webdriver_message_id(webdriver_message_id),
554                        );
555
556                        webview.notify_scroll_event(scroll_location, point.to_i32());
557                    }
558                },
559                WebDriverCommandMsg::ScriptCommand(_, ref webdriver_script_command) => {
560                    self.handle_webdriver_script_command(webdriver_script_command, running_state);
561                    running_state.servo().execute_webdriver_command(msg);
562                },
563                WebDriverCommandMsg::CurrentUserPrompt(webview_id, response_sender) => {
564                    let current_dialog =
565                        running_state.get_current_active_dialog_webdriver_type(webview_id);
566                    if let Err(error) = response_sender.send(current_dialog) {
567                        warn!("Failed to send response of CurrentUserPrompt: {error}");
568                    };
569                },
570                WebDriverCommandMsg::HandleUserPrompt(webview_id, action, response_sender) => {
571                    let response = if running_state.webview_has_active_dialog(webview_id) {
572                        let alert_text = running_state.alert_text_of_newest_dialog(webview_id);
573
574                        match action {
575                            WebDriverUserPromptAction::Accept => {
576                                running_state.accept_active_dialogs(webview_id)
577                            },
578                            WebDriverUserPromptAction::Dismiss => {
579                                running_state.dismiss_active_dialogs(webview_id)
580                            },
581                            WebDriverUserPromptAction::Ignore => {},
582                        };
583
584                        // Return success for AcceptAlert and DismissAlert commands.
585                        Ok(alert_text)
586                    } else {
587                        // Return error for AcceptAlert and DismissAlert commands
588                        // if there is no active dialog.
589                        Err(())
590                    };
591
592                    if let Err(error) = response_sender.send(response) {
593                        warn!("Failed to send response of HandleUserPrompt: {error}");
594                    };
595                },
596                WebDriverCommandMsg::GetAlertText(webview_id, response_sender) => {
597                    let response = match running_state.alert_text_of_newest_dialog(webview_id) {
598                        Some(text) => Ok(text),
599                        None => Err(()),
600                    };
601
602                    if let Err(error) = response_sender.send(response) {
603                        warn!("Failed to send response of GetAlertText: {error}");
604                    };
605                },
606                WebDriverCommandMsg::SendAlertText(webview_id, text) => {
607                    running_state.set_alert_text_of_newest_dialog(webview_id, text);
608                },
609                WebDriverCommandMsg::TakeScreenshot(..) => {
610                    running_state.servo().execute_webdriver_command(msg);
611                },
612            };
613        }
614    }
615
616    fn handle_webdriver_script_command(
617        &self,
618        msg: &WebDriverScriptCommand,
619        running_state: &RunningAppState,
620    ) {
621        match msg {
622            WebDriverScriptCommand::ExecuteScript(_webview_id, response_sender) |
623            WebDriverScriptCommand::ExecuteAsyncScript(_webview_id, response_sender) => {
624                // Give embedder a chance to interrupt the script command.
625                // Webdriver only handles 1 script command at a time, so we can
626                // safely set a new interrupt sender and remove the previous one here.
627                running_state.set_script_command_interrupt_sender(Some(response_sender.clone()));
628            },
629            WebDriverScriptCommand::AddLoadStatusSender(webview_id, load_status_sender) => {
630                running_state.set_load_status_sender(*webview_id, load_status_sender.clone());
631            },
632            WebDriverScriptCommand::RemoveLoadStatusSender(webview_id) => {
633                running_state.remove_load_status_sender(*webview_id);
634            },
635            _ => {
636                running_state.set_script_command_interrupt_sender(None);
637            },
638        }
639    }
640}
641
642impl ApplicationHandler<AppEvent> for App {
643    fn resumed(&mut self, event_loop: &ActiveEventLoop) {
644        self.init(Some(event_loop));
645    }
646
647    fn window_event(
648        &mut self,
649        event_loop: &ActiveEventLoop,
650        window_id: WindowId,
651        event: WindowEvent,
652    ) {
653        let now = Instant::now();
654        trace_winit_event!(
655            event,
656            "@{:?} (+{:?}) {event:?}",
657            now - self.t_start,
658            now - self.t
659        );
660        self.t = now;
661
662        let AppState::Running(state) = &self.state else {
663            return;
664        };
665
666        let Some(window) = self.windows.get(&window_id) else {
667            return;
668        };
669
670        let window = window.clone();
671        if event == WindowEvent::RedrawRequested {
672            // We need to redraw the window for some reason.
673            trace!("RedrawRequested");
674
675            // WARNING: do not defer painting or presenting to some later tick of the event
676            // loop or servoshell may become unresponsive! (servo#30312)
677            if let Some(ref mut minibrowser) = self.minibrowser {
678                minibrowser.update(window.as_ref(), state, "RedrawRequested");
679                minibrowser.paint(window.winit_window().unwrap());
680            }
681        }
682
683        // Handle the event
684        let mut consumed = false;
685        if let Some(ref mut minibrowser) = self.minibrowser {
686            match event {
687                WindowEvent::ScaleFactorChanged { scale_factor, .. } => {
688                    // Intercept any ScaleFactorChanged events away from EguiGlow::on_window_event, so
689                    // we can use our own logic for calculating the scale factor and set egui’s
690                    // scale factor to that value manually.
691                    let desired_scale_factor = window.hidpi_scale_factor().get();
692                    let effective_egui_zoom_factor = desired_scale_factor / scale_factor as f32;
693
694                    info!(
695                        "window scale factor changed to {}, setting egui zoom factor to {}",
696                        scale_factor, effective_egui_zoom_factor
697                    );
698
699                    minibrowser
700                        .context
701                        .egui_ctx
702                        .set_zoom_factor(effective_egui_zoom_factor);
703
704                    state.hidpi_scale_factor_changed();
705
706                    // Request a winit redraw event, so we can recomposite, update and paint
707                    // the minibrowser, and present the new frame.
708                    window.winit_window().unwrap().request_redraw();
709                },
710                ref event => {
711                    let response =
712                        minibrowser.on_window_event(window.winit_window().unwrap(), state, event);
713                    // Update minibrowser if there's resize event to sync up with window.
714                    if let WindowEvent::Resized(_) = event {
715                        minibrowser.update(
716                            window.as_ref(),
717                            state,
718                            "Sync WebView size with Window Resize event",
719                        );
720                    }
721                    if response.repaint && *event != WindowEvent::RedrawRequested {
722                        // Request a winit redraw event, so we can recomposite, update and paint
723                        // the minibrowser, and present the new frame.
724                        window.winit_window().unwrap().request_redraw();
725                    }
726
727                    // TODO how do we handle the tab key? (see doc for consumed)
728                    // Note that servo doesn’t yet support tabbing through links and inputs
729                    consumed = response.consumed;
730                },
731            }
732        }
733        if !consumed {
734            window.handle_winit_event(state.clone(), event);
735        }
736
737        // Block until the window gets an event
738        if !self.animating() || self.suspended.get() {
739            event_loop.set_control_flow(ControlFlow::Wait);
740        } else {
741            event_loop.set_control_flow(ControlFlow::Poll);
742        }
743
744        // Consume and handle any events from the servoshell UI.
745        self.handle_servoshell_ui_events();
746
747        self.handle_events_with_winit(event_loop, window);
748    }
749
750    fn user_event(&mut self, event_loop: &ActiveEventLoop, event: AppEvent) {
751        if let AppEvent::Accessibility(ref event) = event {
752            let Some(ref mut minibrowser) = self.minibrowser else {
753                return;
754            };
755            if !minibrowser.handle_accesskit_event(&event.window_event) {
756                return;
757            }
758            if let Some(window) = self.windows.get(&event.window_id) {
759                window.winit_window().unwrap().request_redraw();
760            }
761            return;
762        }
763
764        let now = Instant::now();
765        let event = winit::event::Event::UserEvent(event);
766        trace_winit_event!(
767            event,
768            "@{:?} (+{:?}) {event:?}",
769            now - self.t_start,
770            now - self.t
771        );
772        self.t = now;
773
774        if !matches!(self.state, AppState::Running(..)) {
775            return;
776        };
777        let Some(window) = self.windows.values().next() else {
778            return;
779        };
780        let window = window.clone();
781
782        // Block until the window gets an event
783        if !self.animating() || self.suspended.get() {
784            event_loop.set_control_flow(ControlFlow::Wait);
785        } else {
786            event_loop.set_control_flow(ControlFlow::Poll);
787        }
788
789        // Consume and handle any events from the Minibrowser.
790        self.handle_servoshell_ui_events();
791
792        // Consume and handle any events from the WebDriver.
793        self.handle_webdriver_messages();
794
795        self.handle_events_with_winit(event_loop, window);
796    }
797
798    fn suspended(&mut self, _: &ActiveEventLoop) {
799        self.suspended.set(true);
800    }
801}
802
803fn load_userscripts(userscripts_directory: Option<&Path>) -> std::io::Result<Vec<UserScript>> {
804    let mut userscripts = Vec::new();
805    if let Some(userscripts_directory) = &userscripts_directory {
806        let mut files = std::fs::read_dir(userscripts_directory)?
807            .map(|e| e.map(|entry| entry.path()))
808            .collect::<Result<Vec<_>, _>>()?;
809        files.sort_unstable();
810        for file in files {
811            userscripts.push(UserScript {
812                script: std::fs::read_to_string(&file)?,
813                source_file: Some(file),
814            });
815        }
816    }
817    Ok(userscripts)
818}