Skip to main content

servoshell/desktop/
headed_window.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//! A winit window implementation.
6
7#![deny(clippy::panic)]
8#![deny(clippy::unwrap_used)]
9
10use std::cell::{Cell, RefCell};
11use std::collections::HashMap;
12use std::env;
13use std::rc::Rc;
14use std::time::Duration;
15
16use euclid::{Angle, Length, Point2D, Rect, Rotation3D, Scale, Size2D, UnknownUnit, Vector3D};
17use keyboard_types::ShortcutMatcher;
18use log::{debug, info};
19use raw_window_handle::{HasDisplayHandle, HasWindowHandle, RawWindowHandle};
20use servo::{
21    AuthenticationRequest, BluetoothDeviceSelectionRequest, Cursor, DeviceIndependentIntRect,
22    DeviceIndependentPixel, DeviceIntPoint, DeviceIntRect, DeviceIntSize, DevicePixel, DevicePoint,
23    EmbedderControl, EmbedderControlId, ImeEvent, InputEvent, InputEventId, InputEventResult,
24    InputMethodControl, Key, KeyState, KeyboardEvent, Modifiers, MouseButton as ServoMouseButton,
25    MouseButtonAction, MouseButtonEvent, MouseLeftViewportEvent, MouseMoveEvent, NamedKey,
26    OffscreenRenderingContext, PermissionRequest, RenderingContext, ScreenGeometry, Theme,
27    TouchEvent, TouchEventType, TouchId, WebRenderDebugOption, WebView, WebViewId, WheelDelta,
28    WheelEvent, WheelMode, WindowRenderingContext, convert_rect_to_css_pixel,
29};
30use url::Url;
31use winit::dpi::{LogicalPosition, LogicalSize, PhysicalPosition, PhysicalSize};
32use winit::event::{
33    ElementState, Ime, KeyEvent, MouseButton, MouseScrollDelta, TouchPhase, WindowEvent,
34};
35use winit::event_loop::{ActiveEventLoop, EventLoopProxy};
36use winit::keyboard::{Key as LogicalKey, ModifiersState, NamedKey as WinitNamedKey};
37#[cfg(target_os = "linux")]
38use winit::platform::wayland::WindowAttributesExtWayland;
39#[cfg(any(target_os = "linux", target_os = "windows"))]
40use winit::window::Icon;
41#[cfg(target_os = "macos")]
42use {
43    objc2_app_kit::{NSColorSpace, NSView},
44    objc2_foundation::MainThreadMarker,
45};
46
47use super::geometry::{winit_position_to_euclid_point, winit_size_to_euclid_size};
48use super::keyutils::{CMD_OR_ALT, keyboard_event_from_winit};
49use crate::desktop::accelerated_gl_media::setup_gl_accelerated_media;
50use crate::desktop::dialog::Dialog;
51use crate::desktop::event_loop::AppEvent;
52use crate::desktop::gui::Gui;
53use crate::desktop::keyutils::CMD_OR_CONTROL;
54use crate::prefs::ServoShellPreferences;
55use crate::running_app_state::{RunningAppState, UserInterfaceCommand};
56use crate::window::{
57    LINE_HEIGHT, LINE_WIDTH, MIN_WINDOW_INNER_SIZE, PlatformWindow, ServoShellWindow,
58    ServoShellWindowId,
59};
60
61pub(crate) const INITIAL_WINDOW_TITLE: &str = "Servo";
62
63pub struct HeadedWindow {
64    /// The egui interface that is responsible for showing the user interface elements of
65    /// this headed `Window`.
66    gui: RefCell<Gui>,
67    screen_size: Size2D<u32, DeviceIndependentPixel>,
68    monitor: winit::monitor::MonitorHandle,
69    webview_relative_mouse_point: Cell<Point2D<f32, DevicePixel>>,
70    /// The inner size of the window in physical pixels which excludes OS decorations.
71    /// It equals viewport size + (0, toolbar height).
72    inner_size: Cell<PhysicalSize<u32>>,
73    fullscreen: Cell<bool>,
74    device_pixel_ratio_override: Option<f32>,
75    xr_window_poses: RefCell<Vec<Rc<XRWindowPose>>>,
76    modifiers_state: Cell<ModifiersState>,
77    /// The `RenderingContext` of Servo itself. This is used to render Servo results
78    /// temporarily until they can be blitted into the egui scene.
79    rendering_context: Rc<OffscreenRenderingContext>,
80    /// The RenderingContext that renders directly onto the Window. This is used as
81    /// the target of egui rendering and also where Servo rendering results are finally
82    /// blitted.
83    window_rendering_context: Rc<WindowRenderingContext>,
84    /// A helper that simulates touch events when the `--simulate-touch-events` flag
85    /// is enabled.
86    touch_event_simulator: Option<TouchEventSimulator>,
87    /// Keyboard events that have been sent to Servo that have still not been handled yet.
88    /// When these are handled, they will optionally be used to trigger keybindings that
89    /// are overridable by web content.
90    pending_keyboard_events: RefCell<HashMap<InputEventId, KeyboardEvent>>,
91    // Keep this as the last field of the struct to ensure that the rendering context is
92    // dropped first.
93    // (https://github.com/servo/servo/issues/36711)
94    winit_window: winit::window::Window,
95    /// The last title set on this window. We need to store this value here, as `winit::Window::title`
96    /// is not supported very many platforms.
97    last_title: RefCell<String>,
98    /// The current set of open dialogs.
99    dialogs: RefCell<HashMap<WebViewId, Vec<Dialog>>>,
100    /// The [`EmbedderControlId`] of the currently showing [`InputMethod`] interfaces,
101    /// if one is showing.
102    visible_input_method: Cell<Option<EmbedderControlId>>,
103    /// The position of the mouse cursor after the most recent `MouseMove` event.
104    last_mouse_position: Cell<Option<Point2D<f32, DeviceIndependentPixel>>>,
105}
106
107impl HeadedWindow {
108    #[servo::servo_tracing::instrument(level = "debug", name = "HeadedWindow::new", skip_all)]
109    pub(crate) fn new(
110        servoshell_preferences: &ServoShellPreferences,
111        event_loop: &ActiveEventLoop,
112        event_loop_proxy: EventLoopProxy<AppEvent>,
113        initial_url: Url,
114    ) -> Rc<Self> {
115        let no_native_titlebar = servoshell_preferences.no_native_titlebar;
116        let inner_size = servoshell_preferences.initial_window_size;
117        let window_attr = winit::window::Window::default_attributes()
118            .with_title(INITIAL_WINDOW_TITLE.to_string())
119            .with_decorations(!no_native_titlebar)
120            .with_transparent(no_native_titlebar)
121            .with_inner_size(LogicalSize::new(inner_size.width, inner_size.height))
122            .with_min_inner_size(LogicalSize::new(
123                MIN_WINDOW_INNER_SIZE.width,
124                MIN_WINDOW_INNER_SIZE.height,
125            ))
126            // Must be invisible at startup; accesskit_winit setup needs to
127            // happen before the window is shown for the first time.
128            .with_visible(false);
129
130        // Set a name so it can be pinned to taskbars in Linux.
131        #[cfg(target_os = "linux")]
132        let window_attr = window_attr.with_name("org.servo.Servo", "Servo");
133
134        #[allow(deprecated)]
135        let winit_window = event_loop
136            .create_window(window_attr)
137            .expect("Failed to create window.");
138
139        #[cfg(any(target_os = "linux", target_os = "windows"))]
140        {
141            let icon_bytes = include_bytes!("../../../resources/servo_64.png");
142            winit_window.set_window_icon(Some(load_icon(icon_bytes)));
143        }
144
145        let window_handle = winit_window
146            .window_handle()
147            .expect("winit window did not have a window handle");
148        HeadedWindow::force_srgb_color_space(window_handle.as_raw());
149
150        let monitor = winit_window
151            .current_monitor()
152            .or_else(|| winit_window.available_monitors().nth(0))
153            .expect("No monitor detected");
154
155        let (screen_size, screen_scale) = servoshell_preferences.screen_size_override.map_or_else(
156            || (monitor.size(), winit_window.scale_factor()),
157            |size| (PhysicalSize::new(size.width, size.height), 1.0),
158        );
159        let screen_scale: Scale<f64, DeviceIndependentPixel, DevicePixel> =
160            Scale::new(screen_scale);
161        let screen_size = (winit_size_to_euclid_size(screen_size).to_f64() / screen_scale).to_u32();
162        let inner_size = winit_window.inner_size();
163
164        let display_handle = event_loop
165            .display_handle()
166            .expect("could not get display handle from window");
167        let window_handle = winit_window
168            .window_handle()
169            .expect("could not get window handle from window");
170        let window_rendering_context = Rc::new(
171            WindowRenderingContext::new(display_handle, window_handle, inner_size)
172                .expect("Could not create RenderingContext for Window"),
173        );
174
175        // Setup for GL accelerated media handling. This is only active on certain Linux platforms
176        // and Windows.
177        {
178            let details = window_rendering_context.surfman_details();
179            setup_gl_accelerated_media(details.0, details.1);
180        }
181
182        // Make sure the gl context is made current.
183        window_rendering_context
184            .make_current()
185            .expect("Could not make window RenderingContext current");
186
187        let rendering_context = Rc::new(window_rendering_context.offscreen_context(inner_size));
188        let gui = RefCell::new(Gui::new(
189            &winit_window,
190            event_loop,
191            event_loop_proxy,
192            rendering_context.clone(),
193            initial_url,
194        ));
195
196        debug!("Created window {:?}", winit_window.id());
197        Rc::new(HeadedWindow {
198            gui,
199            winit_window,
200            webview_relative_mouse_point: Cell::new(Point2D::zero()),
201            fullscreen: Cell::new(false),
202            inner_size: Cell::new(inner_size),
203            monitor,
204            screen_size,
205            device_pixel_ratio_override: servoshell_preferences.device_pixel_ratio_override,
206            xr_window_poses: RefCell::new(vec![]),
207            modifiers_state: Cell::new(ModifiersState::empty()),
208            window_rendering_context,
209            touch_event_simulator: servoshell_preferences
210                .simulate_touch_events
211                .then(Default::default),
212            pending_keyboard_events: Default::default(),
213            rendering_context,
214            last_title: RefCell::new(String::from(INITIAL_WINDOW_TITLE)),
215            dialogs: Default::default(),
216            visible_input_method: Default::default(),
217            last_mouse_position: Default::default(),
218        })
219    }
220
221    pub(crate) fn winit_window(&self) -> &winit::window::Window {
222        &self.winit_window
223    }
224
225    fn handle_keyboard_input(
226        &self,
227        state: Rc<RunningAppState>,
228        window: &ServoShellWindow,
229        winit_event: KeyEvent,
230    ) {
231        // First, handle servoshell key bindings that are not overridable by, or visible to, the page.
232        let keyboard_event = keyboard_event_from_winit(&winit_event, self.modifiers_state.get());
233        if self.handle_intercepted_key_bindings(state, window, &keyboard_event) {
234            return;
235        }
236
237        // Then we deliver character and keyboard events to the page in the active webview.
238        let Some(webview) = window.active_webview() else {
239            return;
240        };
241
242        for xr_window_pose in self.xr_window_poses.borrow().iter() {
243            xr_window_pose.handle_xr_rotation(&winit_event, self.modifiers_state.get());
244            xr_window_pose.handle_xr_translation(&keyboard_event);
245        }
246
247        let id = webview.notify_input_event(InputEvent::Keyboard(keyboard_event.clone()));
248        self.pending_keyboard_events
249            .borrow_mut()
250            .insert(id, keyboard_event);
251    }
252
253    /// Helper function to handle a click
254    fn handle_mouse_button_event(
255        &self,
256        webview: &WebView,
257        button: MouseButton,
258        action: ElementState,
259    ) {
260        // `point` can be outside viewport, such as at toolbar with negative y-coordinate.
261        let point = self.webview_relative_mouse_point.get();
262        let webview_rect: Rect<_, _> = webview.size().into();
263        if !webview_rect.contains(point) {
264            return;
265        }
266
267        if self
268            .touch_event_simulator
269            .as_ref()
270            .is_some_and(|touch_event_simulator| {
271                touch_event_simulator
272                    .maybe_consume_move_button_event(webview, button, action, point)
273            })
274        {
275            return;
276        }
277
278        let mouse_button = match &button {
279            MouseButton::Left => ServoMouseButton::Left,
280            MouseButton::Right => ServoMouseButton::Right,
281            MouseButton::Middle => ServoMouseButton::Middle,
282            MouseButton::Back => ServoMouseButton::Back,
283            MouseButton::Forward => ServoMouseButton::Forward,
284            MouseButton::Other(value) => ServoMouseButton::Other(*value),
285        };
286
287        let action = match action {
288            ElementState::Pressed => MouseButtonAction::Down,
289            ElementState::Released => MouseButtonAction::Up,
290        };
291
292        webview.notify_input_event(InputEvent::MouseButton(MouseButtonEvent::new(
293            action,
294            mouse_button,
295            point.into(),
296        )));
297    }
298
299    /// Helper function to handle mouse move events.
300    fn handle_mouse_move_event(&self, webview: &WebView, position: PhysicalPosition<f64>) {
301        let mut point = winit_position_to_euclid_point(position).to_f32();
302        point.y -= (self.toolbar_height() * self.hidpi_scale_factor()).0;
303
304        let previous_point = self.webview_relative_mouse_point.get();
305        self.webview_relative_mouse_point.set(point);
306
307        let webview_rect: Rect<_, _> = webview.size().into();
308        if !webview_rect.contains(point) {
309            if webview_rect.contains(previous_point) {
310                webview.notify_input_event(InputEvent::MouseLeftViewport(
311                    MouseLeftViewportEvent::default(),
312                ));
313            }
314            return;
315        }
316
317        if self
318            .touch_event_simulator
319            .as_ref()
320            .is_some_and(|touch_event_simulator| {
321                touch_event_simulator.maybe_consume_mouse_move_event(webview, point)
322            })
323        {
324            return;
325        }
326
327        webview.notify_input_event(InputEvent::MouseMove(MouseMoveEvent::new(point.into())));
328    }
329
330    /// Handle key events before sending them to Servo.
331    fn handle_intercepted_key_bindings(
332        &self,
333        state: Rc<RunningAppState>,
334        window: &ServoShellWindow,
335        key_event: &KeyboardEvent,
336    ) -> bool {
337        let Some(active_webview) = window.active_webview() else {
338            return false;
339        };
340
341        let mut handled = true;
342        ShortcutMatcher::from_event(key_event.event.clone())
343            .shortcut(CMD_OR_CONTROL, 'W', || {
344                window.close_webview(active_webview.id());
345            })
346            .shortcut(CMD_OR_CONTROL, 'P', || {
347                let rate = env::var("SAMPLING_RATE")
348                    .ok()
349                    .and_then(|s| s.parse().ok())
350                    .unwrap_or(10);
351                let duration = env::var("SAMPLING_DURATION")
352                    .ok()
353                    .and_then(|s| s.parse().ok())
354                    .unwrap_or(10);
355                active_webview.toggle_sampling_profiler(
356                    Duration::from_millis(rate),
357                    Duration::from_secs(duration),
358                );
359            })
360            .shortcut(CMD_OR_CONTROL, 'X', || {
361                active_webview
362                    .notify_input_event(InputEvent::EditingAction(servo::EditingActionEvent::Cut));
363            })
364            .shortcut(CMD_OR_CONTROL, 'C', || {
365                active_webview
366                    .notify_input_event(InputEvent::EditingAction(servo::EditingActionEvent::Copy));
367            })
368            .shortcut(CMD_OR_CONTROL, 'V', || {
369                active_webview.notify_input_event(InputEvent::EditingAction(
370                    servo::EditingActionEvent::Paste,
371                ));
372            })
373            .shortcut(Modifiers::CONTROL, Key::Named(NamedKey::F9), || {
374                active_webview.capture_webrender();
375            })
376            .shortcut(Modifiers::CONTROL, Key::Named(NamedKey::F10), || {
377                active_webview.toggle_webrender_debugging(WebRenderDebugOption::RenderTargetDebug);
378            })
379            .shortcut(Modifiers::CONTROL, Key::Named(NamedKey::F11), || {
380                active_webview.toggle_webrender_debugging(WebRenderDebugOption::TextureCacheDebug);
381            })
382            .shortcut(Modifiers::CONTROL, Key::Named(NamedKey::F12), || {
383                active_webview.toggle_webrender_debugging(WebRenderDebugOption::Profiler);
384            })
385            .shortcut(CMD_OR_ALT, Key::Named(NamedKey::ArrowRight), || {
386                active_webview.go_forward(1);
387            })
388            .optional_shortcut(
389                cfg!(not(target_os = "windows")),
390                CMD_OR_CONTROL,
391                ']',
392                || {
393                    active_webview.go_forward(1);
394                },
395            )
396            .shortcut(CMD_OR_ALT, Key::Named(NamedKey::ArrowLeft), || {
397                active_webview.go_back(1);
398            })
399            .optional_shortcut(
400                cfg!(not(target_os = "windows")),
401                CMD_OR_CONTROL,
402                '[',
403                || {
404                    active_webview.go_back(1);
405                },
406            )
407            .optional_shortcut(
408                self.get_fullscreen(),
409                Modifiers::empty(),
410                Key::Named(NamedKey::Escape),
411                || active_webview.exit_fullscreen(),
412            )
413            // Select the first 8 tabs via shortcuts
414            .shortcut(CMD_OR_CONTROL, '1', || window.activate_webview_by_index(0))
415            .shortcut(CMD_OR_CONTROL, '2', || window.activate_webview_by_index(1))
416            .shortcut(CMD_OR_CONTROL, '3', || window.activate_webview_by_index(2))
417            .shortcut(CMD_OR_CONTROL, '4', || window.activate_webview_by_index(3))
418            .shortcut(CMD_OR_CONTROL, '5', || window.activate_webview_by_index(4))
419            .shortcut(CMD_OR_CONTROL, '6', || window.activate_webview_by_index(5))
420            .shortcut(CMD_OR_CONTROL, '7', || window.activate_webview_by_index(6))
421            .shortcut(CMD_OR_CONTROL, '8', || window.activate_webview_by_index(7))
422            // Cmd/Ctrl 9 is a bit different in that it focuses the last tab instead of the 9th
423            .shortcut(CMD_OR_CONTROL, '9', || {
424                let len = window.webviews().len();
425                if len > 0 {
426                    window.activate_webview_by_index(len - 1)
427                }
428            })
429            .shortcut(Modifiers::CONTROL, Key::Named(NamedKey::PageDown), || {
430                if let Some(index) = window.get_active_webview_index() {
431                    window.activate_webview_by_index((index + 1) % window.webviews().len())
432                }
433            })
434            .shortcut(Modifiers::CONTROL, Key::Named(NamedKey::PageUp), || {
435                if let Some(index) = window.get_active_webview_index() {
436                    let len = window.webviews().len();
437                    window.activate_webview_by_index((index + len - 1) % len);
438                }
439            })
440            .shortcut(CMD_OR_CONTROL, 'T', || {
441                window.create_and_activate_toplevel_webview(
442                    state.clone(),
443                    Url::parse("servo:newtab")
444                        .expect("Should be able to unconditionally parse 'servo:newtab' as URL"),
445                );
446            })
447            .shortcut(CMD_OR_CONTROL, 'Q', || state.schedule_exit())
448            .otherwise(|| handled = false);
449        handled
450    }
451
452    #[cfg_attr(not(target_os = "macos"), expect(unused_variables))]
453    fn force_srgb_color_space(window_handle: RawWindowHandle) {
454        #[cfg(target_os = "macos")]
455        {
456            if let RawWindowHandle::AppKit(handle) = window_handle {
457                assert!(MainThreadMarker::new().is_some());
458                unsafe {
459                    let view = handle.ns_view.cast::<NSView>().as_ref();
460                    view.window()
461                        .expect("Should have a window")
462                        .setColorSpace(Some(&NSColorSpace::sRGBColorSpace()));
463                }
464            }
465        }
466    }
467
468    fn show_ime(&self, control_id: EmbedderControlId, input_method: InputMethodControl) {
469        self.visible_input_method.set(Some(control_id));
470
471        let position = input_method.position();
472        self.winit_window.set_ime_allowed(true);
473        self.winit_window.set_ime_cursor_area(
474            LogicalPosition::new(
475                position.min.x,
476                position.min.y + (self.toolbar_height().0 as i32),
477            ),
478            LogicalSize::new(
479                position.max.x - position.min.x,
480                position.max.y - position.min.y,
481            ),
482        );
483    }
484
485    pub(crate) fn for_each_active_dialog(
486        &self,
487        window: &ServoShellWindow,
488        callback: impl Fn(&mut Dialog) -> bool,
489    ) {
490        let Some(active_webview) = window.active_webview() else {
491            return;
492        };
493        let mut dialogs = self.dialogs.borrow_mut();
494        let Some(dialogs) = dialogs.get_mut(&active_webview.id()) else {
495            return;
496        };
497        if dialogs.is_empty() {
498            return;
499        }
500
501        // If a dialog is open, clear any Servo cursor. TODO: This should restore the
502        // cursor too, when all dialogs close. In general, we need a better cursor
503        // management strategy.
504        self.set_cursor(Cursor::Default);
505
506        let length = dialogs.len();
507        dialogs.retain_mut(callback);
508        if length != dialogs.len() {
509            window.set_needs_repaint();
510        }
511    }
512
513    fn add_dialog(&self, webview_id: WebViewId, dialog: Dialog) {
514        self.dialogs
515            .borrow_mut()
516            .entry(webview_id)
517            .or_default()
518            .push(dialog)
519    }
520
521    fn remove_dialog(&self, webview_id: WebViewId, embedder_control_id: EmbedderControlId) {
522        let mut dialogs = self.dialogs.borrow_mut();
523        if let Some(dialogs) = dialogs.get_mut(&webview_id) {
524            dialogs.retain(|dialog| dialog.embedder_control_id() != Some(embedder_control_id));
525        }
526        dialogs.retain(|_, dialogs| !dialogs.is_empty());
527    }
528
529    fn has_active_dialog_for_webview(&self, webview_id: WebViewId) -> bool {
530        // First lazily clean up any empty dialog vectors.
531        let mut dialogs = self.dialogs.borrow_mut();
532        dialogs.retain(|_, dialogs| !dialogs.is_empty());
533        dialogs.contains_key(&webview_id)
534    }
535
536    fn toolbar_height(&self) -> Length<f32, DeviceIndependentPixel> {
537        self.gui.borrow().toolbar_height()
538    }
539
540    pub(crate) fn handle_winit_window_event(
541        &self,
542        state: Rc<RunningAppState>,
543        window: Rc<ServoShellWindow>,
544        event: WindowEvent,
545    ) {
546        if event == WindowEvent::RedrawRequested {
547            // WARNING: do not defer painting or presenting to some later tick of the event
548            // loop or servoshell may become unresponsive! (servo#30312)
549            let mut gui = self.gui.borrow_mut();
550            gui.update(&state, &window, self);
551            gui.paint(&self.winit_window);
552        }
553
554        let forward_mouse_event_to_egui = |point: Option<PhysicalPosition<f64>>| {
555            if window
556                .active_webview()
557                .is_some_and(|webview| self.has_active_dialog_for_webview(webview.id()))
558            {
559                return true;
560            }
561
562            let Some(point) = point
563                .map(|point| {
564                    winit_position_to_euclid_point(point).to_f32() / self.hidpi_scale_factor()
565                })
566                .or(self.last_mouse_position.get())
567            else {
568                return true;
569            };
570
571            self.last_mouse_position.set(Some(point));
572            self.gui.borrow().is_in_egui_toolbar_rect(point)
573        };
574
575        // Handle the event
576        let mut consumed = false;
577        match event {
578            WindowEvent::ScaleFactorChanged { scale_factor, .. } => {
579                // Intercept any ScaleFactorChanged events away from EguiGlow::on_window_event, so
580                // we can use our own logic for calculating the scale factor and set egui’s
581                // scale factor to that value manually.
582                let desired_scale_factor = self.hidpi_scale_factor().get();
583                let effective_egui_zoom_factor = desired_scale_factor / scale_factor as f32;
584
585                info!(
586                    "window scale factor changed to {}, setting egui zoom factor to {}",
587                    scale_factor, effective_egui_zoom_factor
588                );
589
590                self.gui
591                    .borrow()
592                    .set_zoom_factor(effective_egui_zoom_factor);
593
594                window.hidpi_scale_factor_changed();
595
596                // Request a winit redraw event, so we can recomposite, update and paint
597                // the GUI, and present the new frame.
598                self.winit_window.request_redraw();
599            },
600            WindowEvent::CursorMoved { position, .. }
601                if !forward_mouse_event_to_egui(Some(position)) => {},
602            WindowEvent::MouseInput {
603                state: ElementState::Pressed,
604                button: MouseButton::Forward,
605                ..
606            } => {
607                window.queue_user_interface_command(UserInterfaceCommand::Forward);
608                consumed = true;
609            },
610            WindowEvent::MouseInput {
611                state: ElementState::Pressed,
612                button: MouseButton::Back,
613                ..
614            } => {
615                window.queue_user_interface_command(UserInterfaceCommand::Back);
616                consumed = true;
617            },
618            WindowEvent::MouseWheel { .. } | WindowEvent::MouseInput { .. }
619                if !forward_mouse_event_to_egui(None) =>
620            {
621                self.gui.borrow().surrender_focus();
622            },
623            WindowEvent::KeyboardInput { .. } if !self.gui.borrow().has_keyboard_focus() => {
624                // Keyboard events should go to the WebView unless some other GUI
625                // component has keyboard focus.
626            },
627            ref event => {
628                let response = self
629                    .gui
630                    .borrow_mut()
631                    .on_window_event(&self.winit_window, event);
632
633                if let WindowEvent::Resized(_) = event {
634                    self.rebuild_user_interface(&state, &window);
635                }
636
637                if let WindowEvent::Focused(true) = event {
638                    state.handle_focused(window.clone());
639                }
640
641                if response.repaint && *event != WindowEvent::RedrawRequested {
642                    self.winit_window.request_redraw();
643                }
644
645                // TODO how do we handle the tab key? (see doc for consumed)
646                // Note that servo doesn’t yet support tabbing through links and inputs
647                consumed = response.consumed;
648            },
649        }
650
651        if !consumed {
652            // Make sure to handle early resize events even when there are no webviews yet
653            if let WindowEvent::Resized(new_inner_size) = event &&
654                self.inner_size.get() != new_inner_size
655            {
656                self.inner_size.set(new_inner_size);
657                // This should always be set to inner size
658                // because we are resizing `SurfmanRenderingContext`.
659                // See https://github.com/servo/servo/issues/38369#issuecomment-3138378527
660                self.window_rendering_context.resize(new_inner_size);
661            }
662
663            if let Some(webview) = window.active_webview() {
664                match event {
665                    WindowEvent::KeyboardInput { event, .. } => {
666                        self.handle_keyboard_input(state, &window, event)
667                    },
668                    WindowEvent::ModifiersChanged(modifiers) => {
669                        self.modifiers_state.set(modifiers.state())
670                    },
671                    WindowEvent::MouseInput { state, button, .. } => {
672                        self.handle_mouse_button_event(&webview, button, state);
673                    },
674                    WindowEvent::CursorMoved { position, .. } => {
675                        self.handle_mouse_move_event(&webview, position);
676                    },
677                    WindowEvent::CursorLeft { .. } => {
678                        let webview_rect: Rect<_, _> = webview.size().into();
679                        if webview_rect.contains(self.webview_relative_mouse_point.get()) {
680                            webview.notify_input_event(InputEvent::MouseLeftViewport(
681                                MouseLeftViewportEvent::default(),
682                            ));
683                        }
684                    },
685                    WindowEvent::MouseWheel { delta, .. } => {
686                        let (delta_x, delta_y, mode) = match delta {
687                            MouseScrollDelta::LineDelta(delta_x, delta_y) => (
688                                (delta_x * LINE_WIDTH) as f64,
689                                (delta_y * LINE_HEIGHT) as f64,
690                                WheelMode::DeltaPixel,
691                            ),
692                            MouseScrollDelta::PixelDelta(delta) => {
693                                (delta.x, delta.y, WheelMode::DeltaPixel)
694                            },
695                        };
696
697                        // Create wheel event before snapping to the major axis of movement
698                        let delta = WheelDelta {
699                            x: delta_x,
700                            y: delta_y,
701                            z: 0.0,
702                            mode,
703                        };
704                        let point = self.webview_relative_mouse_point.get();
705                        webview.notify_input_event(InputEvent::Wheel(WheelEvent::new(
706                            delta,
707                            point.into(),
708                        )));
709                    },
710                    WindowEvent::Touch(touch) => {
711                        webview.notify_input_event(InputEvent::Touch(TouchEvent::new(
712                            winit_phase_to_touch_event_type(touch.phase),
713                            TouchId(touch.id as i32),
714                            DevicePoint::new(touch.location.x as f32, touch.location.y as f32)
715                                .into(),
716                        )));
717                    },
718                    WindowEvent::PinchGesture { delta, .. } => {
719                        webview.adjust_pinch_zoom(
720                            delta as f32 + 1.0,
721                            self.webview_relative_mouse_point.get(),
722                        );
723                    },
724                    WindowEvent::CloseRequested => {
725                        window.schedule_close();
726                    },
727                    WindowEvent::ThemeChanged(theme) => {
728                        webview.notify_theme_change(match theme {
729                            winit::window::Theme::Light => Theme::Light,
730                            winit::window::Theme::Dark => Theme::Dark,
731                        });
732                    },
733                    WindowEvent::Ime(ime) => match ime {
734                        Ime::Enabled => {
735                            webview.notify_input_event(InputEvent::Ime(ImeEvent::Composition(
736                                servo::CompositionEvent {
737                                    state: servo::CompositionState::Start,
738                                    data: String::new(),
739                                },
740                            )));
741                        },
742                        Ime::Preedit(text, _) => {
743                            webview.notify_input_event(InputEvent::Ime(ImeEvent::Composition(
744                                servo::CompositionEvent {
745                                    state: servo::CompositionState::Update,
746                                    data: text,
747                                },
748                            )));
749                        },
750                        Ime::Commit(text) => {
751                            webview.notify_input_event(InputEvent::Ime(ImeEvent::Composition(
752                                servo::CompositionEvent {
753                                    state: servo::CompositionState::End,
754                                    data: text,
755                                },
756                            )));
757                        },
758                        Ime::Disabled => {
759                            // There are two reasons we receive this message from winit:
760                            //
761                            // 1. The user dismissed the IME. In that case we want to inform Servo
762                            //    so it can unfocus the current editable element.
763                            // 2. Servo changed focus and requested that we dismiss the IME, which
764                            //    in turn triggers this message. We know this is the case when we don't
765                            //    expect any IME to be open and shouldn't send any more messages to
766                            //    Servo as it might cause unexpected blurring of the newly focused
767                            //    element.
768                            if self.visible_input_method.take().is_some() {
769                                webview.notify_input_event(InputEvent::Ime(ImeEvent::Dismissed));
770                            }
771                        },
772                    },
773                    _ => {},
774                }
775            }
776        }
777    }
778
779    pub(crate) fn handle_winit_app_event(&self, state: Rc<RunningAppState>, app_event: AppEvent) {
780        if let AppEvent::Accessibility(ref event) = app_event {
781            match &event.window_event {
782                egui_winit::accesskit_winit::WindowEvent::InitialTreeRequested => {
783                    state.set_accessibility_active(true);
784                },
785                egui_winit::accesskit_winit::WindowEvent::ActionRequested(req) => {
786                    if req.target_tree != accesskit::TreeId::ROOT {
787                        // TODO(#4344): Forward action to Servo
788                    }
789                },
790                egui_winit::accesskit_winit::WindowEvent::AccessibilityDeactivated => {
791                    state.set_accessibility_active(false);
792                },
793            }
794
795            if self
796                .gui
797                .borrow_mut()
798                .handle_accesskit_event(&event.window_event)
799            {
800                self.winit_window.request_redraw();
801            }
802        }
803    }
804}
805
806impl PlatformWindow for HeadedWindow {
807    fn as_headed_window(&self) -> Option<&Self> {
808        Some(self)
809    }
810
811    fn screen_geometry(&self) -> ScreenGeometry {
812        let hidpi_factor = self.hidpi_scale_factor();
813        let toolbar_size = Size2D::new(0.0, (self.toolbar_height() * self.hidpi_scale_factor()).0);
814        let screen_size = self.screen_size.to_f32() * hidpi_factor;
815
816        // FIXME: In reality, this should subtract screen space used by the system interface
817        // elements, but it is difficult to get this value with `winit` currently. See:
818        // See https://github.com/rust-windowing/winit/issues/2494
819        let available_screen_size = screen_size - toolbar_size;
820
821        let window_rect = DeviceIntRect::from_origin_and_size(
822            winit_position_to_euclid_point(self.winit_window.outer_position().unwrap_or_default()),
823            winit_size_to_euclid_size(self.winit_window.outer_size()).to_i32(),
824        );
825
826        ScreenGeometry {
827            size: screen_size.to_i32(),
828            available_size: available_screen_size.to_i32(),
829            window_rect,
830        }
831    }
832
833    fn device_hidpi_scale_factor(&self) -> Scale<f32, DeviceIndependentPixel, DevicePixel> {
834        Scale::new(self.winit_window.scale_factor() as f32)
835    }
836
837    fn hidpi_scale_factor(&self) -> Scale<f32, DeviceIndependentPixel, DevicePixel> {
838        self.device_pixel_ratio_override
839            .map(Scale::new)
840            .unwrap_or_else(|| self.device_hidpi_scale_factor())
841    }
842
843    fn rebuild_user_interface(&self, state: &RunningAppState, window: &ServoShellWindow) {
844        self.gui.borrow_mut().update(state, window, self);
845    }
846
847    fn update_user_interface_state(&self, _: &RunningAppState, window: &ServoShellWindow) -> bool {
848        let title = window
849            .active_webview()
850            .and_then(|webview| {
851                webview
852                    .page_title()
853                    .filter(|title| !title.is_empty())
854                    .or_else(|| webview.url().map(|url| url.to_string()))
855            })
856            .unwrap_or_else(|| INITIAL_WINDOW_TITLE.to_string());
857        if title != *self.last_title.borrow() {
858            self.winit_window.set_title(&title);
859            *self.last_title.borrow_mut() = title;
860        }
861
862        self.gui.borrow_mut().update_webview_data(window)
863    }
864
865    fn request_repaint(&self, window: &ServoShellWindow) {
866        self.winit_window.request_redraw();
867
868        // FIXME: This is a workaround for dialogs, which do not seem to animate, unless we
869        // constantly repaint the egui scene.
870        if window
871            .active_webview()
872            .is_some_and(|webview| self.has_active_dialog_for_webview(webview.id()))
873        {
874            window.set_needs_repaint();
875        }
876    }
877
878    fn request_resize(&self, _: &WebView, new_outer_size: DeviceIntSize) -> Option<DeviceIntSize> {
879        // Allocate space for the window deocrations, but do not let the inner size get
880        // smaller than `MIN_WINDOW_INNER_SIZE` or larger than twice the screen size.
881        let inner_size = self.winit_window.inner_size();
882        let outer_size = self.winit_window.outer_size();
883        let decoration_size: DeviceIntSize = Size2D::new(
884            outer_size.height - inner_size.height,
885            outer_size.width - inner_size.width,
886        )
887        .cast();
888
889        let screen_size = (self.screen_size.to_f32() * self.hidpi_scale_factor()).to_i32();
890        let new_outer_size =
891            new_outer_size.clamp(MIN_WINDOW_INNER_SIZE + decoration_size, screen_size * 2);
892
893        if outer_size.width == new_outer_size.width as u32 &&
894            outer_size.height == new_outer_size.height as u32
895        {
896            return Some(new_outer_size);
897        }
898
899        let new_inner_size = new_outer_size - decoration_size;
900        self.winit_window
901            .request_inner_size(PhysicalSize::new(
902                new_inner_size.width,
903                new_inner_size.height,
904            ))
905            .map(|resulting_size| {
906                DeviceIntSize::new(
907                    resulting_size.width as i32 + decoration_size.width,
908                    resulting_size.height as i32 + decoration_size.height,
909                )
910            })
911    }
912
913    fn window_rect(&self) -> DeviceIndependentIntRect {
914        let outer_size = self.winit_window.outer_size();
915        let scale = self.hidpi_scale_factor();
916
917        let outer_size = winit_size_to_euclid_size(outer_size).to_i32();
918
919        let origin = self
920            .winit_window
921            .outer_position()
922            .map(winit_position_to_euclid_point)
923            .unwrap_or_default();
924        convert_rect_to_css_pixel(
925            DeviceIntRect::from_origin_and_size(origin, outer_size),
926            scale,
927        )
928    }
929
930    fn set_position(&self, point: DeviceIntPoint) {
931        self.winit_window
932            .set_outer_position::<PhysicalPosition<i32>>(PhysicalPosition::new(point.x, point.y))
933    }
934
935    fn set_fullscreen(&self, state: bool) {
936        if self.fullscreen.get() != state {
937            self.winit_window.set_fullscreen(if state {
938                Some(winit::window::Fullscreen::Borderless(Some(
939                    self.monitor.clone(),
940                )))
941            } else {
942                None
943            });
944        }
945        self.fullscreen.set(state);
946    }
947
948    fn get_fullscreen(&self) -> bool {
949        self.fullscreen.get()
950    }
951
952    fn set_cursor(&self, cursor: Cursor) {
953        use winit::window::CursorIcon;
954
955        let winit_cursor = match cursor {
956            Cursor::Default => CursorIcon::Default,
957            Cursor::Pointer => CursorIcon::Pointer,
958            Cursor::ContextMenu => CursorIcon::ContextMenu,
959            Cursor::Help => CursorIcon::Help,
960            Cursor::Progress => CursorIcon::Progress,
961            Cursor::Wait => CursorIcon::Wait,
962            Cursor::Cell => CursorIcon::Cell,
963            Cursor::Crosshair => CursorIcon::Crosshair,
964            Cursor::Text => CursorIcon::Text,
965            Cursor::VerticalText => CursorIcon::VerticalText,
966            Cursor::Alias => CursorIcon::Alias,
967            Cursor::Copy => CursorIcon::Copy,
968            Cursor::Move => CursorIcon::Move,
969            Cursor::NoDrop => CursorIcon::NoDrop,
970            Cursor::NotAllowed => CursorIcon::NotAllowed,
971            Cursor::Grab => CursorIcon::Grab,
972            Cursor::Grabbing => CursorIcon::Grabbing,
973            Cursor::EResize => CursorIcon::EResize,
974            Cursor::NResize => CursorIcon::NResize,
975            Cursor::NeResize => CursorIcon::NeResize,
976            Cursor::NwResize => CursorIcon::NwResize,
977            Cursor::SResize => CursorIcon::SResize,
978            Cursor::SeResize => CursorIcon::SeResize,
979            Cursor::SwResize => CursorIcon::SwResize,
980            Cursor::WResize => CursorIcon::WResize,
981            Cursor::EwResize => CursorIcon::EwResize,
982            Cursor::NsResize => CursorIcon::NsResize,
983            Cursor::NeswResize => CursorIcon::NeswResize,
984            Cursor::NwseResize => CursorIcon::NwseResize,
985            Cursor::ColResize => CursorIcon::ColResize,
986            Cursor::RowResize => CursorIcon::RowResize,
987            Cursor::AllScroll => CursorIcon::AllScroll,
988            Cursor::ZoomIn => CursorIcon::ZoomIn,
989            Cursor::ZoomOut => CursorIcon::ZoomOut,
990            Cursor::None => {
991                self.winit_window.set_cursor_visible(false);
992                return;
993            },
994        };
995        self.winit_window.set_cursor(winit_cursor);
996        self.winit_window.set_cursor_visible(true);
997    }
998
999    fn id(&self) -> ServoShellWindowId {
1000        let id: u64 = self.winit_window.id().into();
1001        id.into()
1002    }
1003
1004    #[cfg(feature = "webxr")]
1005    fn new_glwindow(&self, event_loop: &ActiveEventLoop) -> Rc<dyn servo::webxr::GlWindow> {
1006        let size = self.winit_window.outer_size();
1007
1008        let window_attr = winit::window::Window::default_attributes()
1009            .with_title("Servo XR".to_string())
1010            .with_inner_size(size)
1011            .with_visible(false);
1012
1013        let winit_window = event_loop
1014            .create_window(window_attr)
1015            .expect("Failed to create window.");
1016
1017        let pose = Rc::new(XRWindowPose {
1018            xr_rotation: Cell::new(Rotation3D::identity()),
1019            xr_translation: Cell::new(Vector3D::zero()),
1020        });
1021        self.xr_window_poses.borrow_mut().push(pose.clone());
1022        Rc::new(XRWindow { winit_window, pose })
1023    }
1024
1025    fn rendering_context(&self) -> Rc<dyn RenderingContext> {
1026        self.rendering_context.clone()
1027    }
1028
1029    fn theme(&self) -> servo::Theme {
1030        match self.winit_window.theme() {
1031            Some(winit::window::Theme::Dark) => servo::Theme::Dark,
1032            Some(winit::window::Theme::Light) | None => servo::Theme::Light,
1033        }
1034    }
1035
1036    fn maximize(&self, _webview: &WebView) {
1037        self.winit_window.set_maximized(true);
1038    }
1039
1040    /// Handle servoshell key bindings that may have been prevented by the page in the active webview.
1041    fn notify_input_event_handled(
1042        &self,
1043        webview: &WebView,
1044        id: InputEventId,
1045        result: InputEventResult,
1046    ) {
1047        let Some(keyboard_event) = self.pending_keyboard_events.borrow_mut().remove(&id) else {
1048            return;
1049        };
1050        if result.intersects(InputEventResult::DefaultPrevented | InputEventResult::Consumed) {
1051            return;
1052        }
1053
1054        ShortcutMatcher::from_event(keyboard_event.event)
1055            .shortcut(CMD_OR_CONTROL, '=', || {
1056                webview.set_page_zoom(webview.page_zoom() + 0.1);
1057            })
1058            .shortcut(CMD_OR_CONTROL, '+', || {
1059                webview.set_page_zoom(webview.page_zoom() + 0.1);
1060            })
1061            .shortcut(CMD_OR_CONTROL, '-', || {
1062                webview.set_page_zoom(webview.page_zoom() - 0.1);
1063            })
1064            .shortcut(CMD_OR_CONTROL, '0', || {
1065                webview.set_page_zoom(1.0);
1066            })
1067            .shortcut(CMD_OR_CONTROL, 'R', || webview.reload())
1068            .shortcut(Modifiers::empty(), Key::Named(NamedKey::F5), || {
1069                webview.reload()
1070            });
1071    }
1072
1073    fn focus(&self) {
1074        self.winit_window.focus_window();
1075    }
1076
1077    fn has_platform_focus(&self) -> bool {
1078        self.winit_window.has_focus()
1079    }
1080
1081    fn show_embedder_control(&self, webview_id: WebViewId, embedder_control: EmbedderControl) {
1082        let control_id = embedder_control.id();
1083        match embedder_control {
1084            EmbedderControl::SelectElement(prompt) => {
1085                // FIXME: Reading the toolbar height is needed here to properly position the select dialog.
1086                // But if the toolbar height changes while the dialog is open then the position won't be updated
1087                let offset = self.gui.borrow().toolbar_height();
1088                self.add_dialog(
1089                    webview_id,
1090                    Dialog::new_select_element_dialog(prompt, offset),
1091                );
1092            },
1093            EmbedderControl::ColorPicker(color_picker) => {
1094                // FIXME: Reading the toolbar height is needed here to properly position the select dialog.
1095                // But if the toolbar height changes while the dialog is open then the position won't be updated
1096                let offset = self.gui.borrow().toolbar_height();
1097                self.add_dialog(
1098                    webview_id,
1099                    Dialog::new_color_picker_dialog(color_picker, offset),
1100                );
1101            },
1102            EmbedderControl::InputMethod(input_method_control) => {
1103                self.show_ime(control_id, input_method_control);
1104            },
1105            EmbedderControl::FilePicker(file_picker) => {
1106                self.add_dialog(webview_id, Dialog::new_file_dialog(file_picker));
1107            },
1108            EmbedderControl::SimpleDialog(simple_dialog) => {
1109                self.add_dialog(webview_id, Dialog::new_simple_dialog(simple_dialog));
1110            },
1111            EmbedderControl::ContextMenu(prompt) => {
1112                let offset = self.gui.borrow().toolbar_height();
1113                self.add_dialog(webview_id, Dialog::new_context_menu(prompt, offset));
1114            },
1115        }
1116    }
1117
1118    fn hide_embedder_control(&self, webview_id: WebViewId, embedder_control_id: EmbedderControlId) {
1119        if self.visible_input_method.get() == Some(embedder_control_id) {
1120            self.visible_input_method.set(None);
1121            self.winit_window.set_ime_allowed(false);
1122            return;
1123        }
1124        self.remove_dialog(webview_id, embedder_control_id);
1125    }
1126
1127    fn show_bluetooth_device_dialog(
1128        &self,
1129        webview_id: WebViewId,
1130        request: BluetoothDeviceSelectionRequest,
1131    ) {
1132        self.add_dialog(webview_id, Dialog::new_device_selection_dialog(request));
1133    }
1134
1135    fn show_permission_dialog(&self, webview_id: WebViewId, permission_request: PermissionRequest) {
1136        self.add_dialog(
1137            webview_id,
1138            Dialog::new_permission_request_dialog(permission_request),
1139        );
1140    }
1141
1142    fn show_http_authentication_dialog(
1143        &self,
1144        webview_id: WebViewId,
1145        authentication_request: AuthenticationRequest,
1146    ) {
1147        self.add_dialog(
1148            webview_id,
1149            Dialog::new_authentication_dialog(authentication_request),
1150        );
1151    }
1152
1153    fn dismiss_embedder_controls_for_webview(&self, webview_id: WebViewId) {
1154        self.dialogs.borrow_mut().remove(&webview_id);
1155    }
1156
1157    fn show_console_message(&self, level: servo::ConsoleLogLevel, message: &str) {
1158        println!("{message}");
1159        log::log!(level.into(), "{message}");
1160    }
1161
1162    fn notify_accessibility_tree_update(
1163        &self,
1164        _webview: WebView,
1165        tree_update: accesskit::TreeUpdate,
1166    ) {
1167        self.gui
1168            .borrow_mut()
1169            .notify_accessibility_tree_update(tree_update);
1170    }
1171}
1172
1173fn winit_phase_to_touch_event_type(phase: TouchPhase) -> TouchEventType {
1174    match phase {
1175        TouchPhase::Started => TouchEventType::Down,
1176        TouchPhase::Moved => TouchEventType::Move,
1177        TouchPhase::Ended => TouchEventType::Up,
1178        TouchPhase::Cancelled => TouchEventType::Cancel,
1179    }
1180}
1181
1182#[cfg(any(target_os = "linux", target_os = "windows"))]
1183fn load_icon(icon_bytes: &[u8]) -> Icon {
1184    let (icon_rgba, icon_width, icon_height) = {
1185        use image::{GenericImageView, Pixel};
1186        let image = image::load_from_memory(icon_bytes).expect("Failed to load icon");
1187        let (width, height) = image.dimensions();
1188        let mut rgba = Vec::with_capacity((width * height) as usize * 4);
1189        for (_, _, pixel) in image.pixels() {
1190            rgba.extend_from_slice(&pixel.to_rgba().0);
1191        }
1192        (rgba, width, height)
1193    };
1194    Icon::from_rgba(icon_rgba, icon_width, icon_height).expect("Failed to load icon")
1195}
1196
1197#[cfg(feature = "webxr")]
1198struct XRWindow {
1199    winit_window: winit::window::Window,
1200    pose: Rc<XRWindowPose>,
1201}
1202
1203struct XRWindowPose {
1204    xr_rotation: Cell<Rotation3D<f32, UnknownUnit, UnknownUnit>>,
1205    xr_translation: Cell<Vector3D<f32, UnknownUnit>>,
1206}
1207
1208#[cfg(feature = "webxr")]
1209impl servo::webxr::GlWindow for XRWindow {
1210    fn get_render_target(
1211        &self,
1212        device: &mut surfman::Device,
1213        _context: &mut surfman::Context,
1214    ) -> servo::webxr::GlWindowRenderTarget {
1215        self.winit_window.set_visible(true);
1216        let window_handle = self
1217            .winit_window
1218            .window_handle()
1219            .expect("could not get window handle from window");
1220        let size = self.winit_window.inner_size();
1221        let size = Size2D::new(size.width as i32, size.height as i32);
1222        let native_widget = device
1223            .connection()
1224            .create_native_widget_from_window_handle(window_handle, size)
1225            .expect("Failed to create native widget");
1226        servo::webxr::GlWindowRenderTarget::NativeWidget(native_widget)
1227    }
1228
1229    fn get_rotation(&self) -> Rotation3D<f32, UnknownUnit, UnknownUnit> {
1230        self.pose.xr_rotation.get()
1231    }
1232
1233    fn get_translation(&self) -> Vector3D<f32, UnknownUnit> {
1234        self.pose.xr_translation.get()
1235    }
1236
1237    fn get_mode(&self) -> servo::webxr::GlWindowMode {
1238        use servo::pref;
1239        if pref!(dom_webxr_glwindow_red_cyan) {
1240            servo::webxr::GlWindowMode::StereoRedCyan
1241        } else if pref!(dom_webxr_glwindow_left_right) {
1242            servo::webxr::GlWindowMode::StereoLeftRight
1243        } else if pref!(dom_webxr_glwindow_spherical) {
1244            servo::webxr::GlWindowMode::Spherical
1245        } else if pref!(dom_webxr_glwindow_cubemap) {
1246            servo::webxr::GlWindowMode::Cubemap
1247        } else {
1248            servo::webxr::GlWindowMode::Blit
1249        }
1250    }
1251
1252    fn display_handle(&self) -> raw_window_handle::DisplayHandle<'_> {
1253        self.winit_window
1254            .display_handle()
1255            .expect("Every window should have a display handle")
1256    }
1257}
1258
1259impl XRWindowPose {
1260    fn handle_xr_translation(&self, input: &KeyboardEvent) {
1261        if input.event.state != KeyState::Down {
1262            return;
1263        }
1264        const NORMAL_TRANSLATE: f32 = 0.1;
1265        const QUICK_TRANSLATE: f32 = 1.0;
1266        let mut x = 0.0;
1267        let mut z = 0.0;
1268        match input.event.key {
1269            Key::Character(ref k) => match &**k {
1270                "w" => z = -NORMAL_TRANSLATE,
1271                "W" => z = -QUICK_TRANSLATE,
1272                "s" => z = NORMAL_TRANSLATE,
1273                "S" => z = QUICK_TRANSLATE,
1274                "a" => x = -NORMAL_TRANSLATE,
1275                "A" => x = -QUICK_TRANSLATE,
1276                "d" => x = NORMAL_TRANSLATE,
1277                "D" => x = QUICK_TRANSLATE,
1278                _ => return,
1279            },
1280            _ => return,
1281        };
1282        let (old_x, old_y, old_z) = self.xr_translation.get().to_tuple();
1283        let vec = Vector3D::new(x + old_x, old_y, z + old_z);
1284        self.xr_translation.set(vec);
1285    }
1286
1287    fn handle_xr_rotation(&self, input: &KeyEvent, modifiers: ModifiersState) {
1288        if input.state != ElementState::Pressed {
1289            return;
1290        }
1291        let mut x = 0.0;
1292        let mut y = 0.0;
1293        match input.logical_key {
1294            LogicalKey::Named(WinitNamedKey::ArrowUp) => x = 1.0,
1295            LogicalKey::Named(WinitNamedKey::ArrowDown) => x = -1.0,
1296            LogicalKey::Named(WinitNamedKey::ArrowLeft) => y = 1.0,
1297            LogicalKey::Named(WinitNamedKey::ArrowRight) => y = -1.0,
1298            _ => return,
1299        };
1300        if modifiers.shift_key() {
1301            x *= 10.0;
1302            y *= 10.0;
1303        }
1304        let x: Rotation3D<_, UnknownUnit, UnknownUnit> = Rotation3D::around_x(Angle::degrees(x));
1305        let y: Rotation3D<_, UnknownUnit, UnknownUnit> = Rotation3D::around_y(Angle::degrees(y));
1306        let rotation = self.xr_rotation.get().then(&x).then(&y);
1307        self.xr_rotation.set(rotation);
1308    }
1309}
1310
1311#[derive(Default)]
1312pub struct TouchEventSimulator {
1313    pub left_mouse_button_down: Cell<bool>,
1314}
1315
1316impl TouchEventSimulator {
1317    fn maybe_consume_move_button_event(
1318        &self,
1319        webview: &WebView,
1320        button: MouseButton,
1321        action: ElementState,
1322        point: DevicePoint,
1323    ) -> bool {
1324        if button != MouseButton::Left {
1325            return false;
1326        }
1327
1328        if action == ElementState::Pressed && !self.left_mouse_button_down.get() {
1329            webview.notify_input_event(InputEvent::Touch(TouchEvent::new(
1330                TouchEventType::Down,
1331                TouchId(0),
1332                point.into(),
1333            )));
1334            self.left_mouse_button_down.set(true);
1335        } else if action == ElementState::Released {
1336            webview.notify_input_event(InputEvent::Touch(TouchEvent::new(
1337                TouchEventType::Up,
1338                TouchId(0),
1339                point.into(),
1340            )));
1341            self.left_mouse_button_down.set(false);
1342        }
1343
1344        true
1345    }
1346
1347    fn maybe_consume_mouse_move_event(
1348        &self,
1349        webview: &WebView,
1350        point: Point2D<f32, DevicePixel>,
1351    ) -> bool {
1352        if !self.left_mouse_button_down.get() {
1353            return false;
1354        }
1355
1356        webview.notify_input_event(InputEvent::Touch(TouchEvent::new(
1357            TouchEventType::Move,
1358            TouchId(0),
1359            point.into(),
1360        )));
1361        true
1362    }
1363}