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