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