diff --git a/components/compositing/compositor.rs b/components/compositing/compositor.rs index bf7d67ec803..fe7ef0d9878 100644 --- a/components/compositing/compositor.rs +++ b/components/compositing/compositor.rs @@ -117,9 +117,6 @@ pub struct ServoRenderer { /// Some XR devices want to run on the main thread. webxr_main_thread: webxr::MainThreadRegistry, - /// True to translate mouse input into touch events. - pub(crate) convert_mouse_to_touch: bool, - /// The last position in the rendered view that the mouse moved over. This becomes `None` /// when the mouse leaves the rendered view. pub(crate) last_mouse_move_position: Option, @@ -306,7 +303,7 @@ impl ServoRenderer { } impl IOCompositor { - pub fn new(state: InitialCompositorState, convert_mouse_to_touch: bool) -> Self { + pub fn new(state: InitialCompositorState) -> Self { let registration = state.mem_profiler_chan.prepare_memory_reporting( "compositor".into(), state.sender.clone(), @@ -327,7 +324,6 @@ impl IOCompositor { webrender_gl: state.webrender_gl, #[cfg(feature = "webxr")] webxr_main_thread: state.webxr_main_thread, - convert_mouse_to_touch, last_mouse_move_position: None, frame_delayer: Default::default(), })), diff --git a/components/compositing/touch.rs b/components/compositing/touch.rs index 40b4727a8e0..086544fbb74 100644 --- a/components/compositing/touch.rs +++ b/components/compositing/touch.rs @@ -283,10 +283,6 @@ impl TouchHandler { debug_assert!(old.is_some(), "Sequence already removed?"); } - pub fn try_get_current_touch_sequence(&self) -> Option<&TouchSequenceInfo> { - self.touch_sequence_map.get(&self.current_sequence_id) - } - pub fn get_current_touch_sequence_mut(&mut self) -> &mut TouchSequenceInfo { self.touch_sequence_map .get_mut(&self.current_sequence_id) diff --git a/components/compositing/webview_renderer.rs b/components/compositing/webview_renderer.rs index 8ad68a99d59..79346555e4b 100644 --- a/components/compositing/webview_renderer.rs +++ b/components/compositing/webview_renderer.rs @@ -16,7 +16,7 @@ use constellation_traits::{EmbedderToConstellationMessage, WindowSizeType}; use embedder_traits::{ AnimationState, CompositorHitTestResult, InputEvent, MouseButton, MouseButtonAction, MouseButtonEvent, MouseMoveEvent, ScrollEvent as EmbedderScrollEvent, ShutdownState, - TouchEvent, TouchEventResult, TouchEventType, TouchId, ViewportDetails, + TouchEvent, TouchEventResult, TouchEventType, ViewportDetails, }; use euclid::{Point2D, Scale, Vector2D}; use log::{debug, warn}; @@ -365,62 +365,6 @@ impl WebViewRenderer { return; } - if self.global.borrow().convert_mouse_to_touch { - match event { - InputEvent::MouseButton(event) => { - match (event.button, event.action) { - (MouseButton::Left, MouseButtonAction::Down) => self.on_touch_down( - TouchEvent::new(TouchEventType::Down, TouchId(0), event.point), - ), - (MouseButton::Left, MouseButtonAction::Up) => self.on_touch_up( - TouchEvent::new(TouchEventType::Up, TouchId(0), event.point), - ), - _ => {}, - } - return; - }, - InputEvent::MouseMove(event) => { - if let Some(state) = self.touch_handler.try_get_current_touch_sequence() { - // We assume that the debug option `-Z convert-mouse-to-touch` will only - // be used on devices without native touch input, so we can directly - // reuse the touch handler for tracking the state of pressed buttons. - match state.state { - TouchSequenceState::Touching | TouchSequenceState::Panning { .. } => { - self.on_touch_move(TouchEvent::new( - TouchEventType::Move, - TouchId(0), - event.point, - )); - }, - TouchSequenceState::MultiTouch => { - // Multitouch simulation currently is not implemented. - // Since we only get one mouse move event, we would need to - // dispatch one mouse move event per currently pressed mouse button. - }, - TouchSequenceState::Pinching => { - // We only have one mouse button, so Pinching should be impossible. - #[cfg(debug_assertions)] - log::error!( - "Touch handler is in Pinching state, which should be unreachable with \ - -Z convert-mouse-to-touch debug option." - ); - }, - TouchSequenceState::PendingFling { .. } | - TouchSequenceState::Flinging { .. } | - TouchSequenceState::PendingClick(_) | - TouchSequenceState::Finished => { - // Mouse movement without a button being pressed is not - // translated to touch events. - }, - } - } - // We don't want to (directly) dispatch mouse events when simulating touch input. - return; - }, - _ => {}, - } - } - self.dispatch_input_event_with_hit_testing(event); } diff --git a/components/config/opts.rs b/components/config/opts.rs index 64194db4ae9..9cf28ef13e0 100644 --- a/components/config/opts.rs +++ b/components/config/opts.rs @@ -128,9 +128,6 @@ pub struct DebugOptions { /// Whether to show in stdout style sharing cache stats after a restyle. pub dump_style_statistics: bool, - /// Translate mouse input into touch events. - pub convert_mouse_to_touch: bool, - /// Log GC passes and their durations. pub gc_profile: bool, } @@ -140,7 +137,6 @@ impl DebugOptions { for option in debug_string.split(',') { match option { "help" => self.help = true, - "convert-mouse-to-touch" => self.convert_mouse_to_touch = true, "disable-share-style-cache" => self.disable_share_style_cache = true, "dump-display-list" => self.dump_display_list = true, "dump-stacking-context-tree" => self.dump_stacking_context_tree = true, diff --git a/components/servo/lib.rs b/components/servo/lib.rs index 3b34d9c5c02..7c504f08b29 100644 --- a/components/servo/lib.rs +++ b/components/servo/lib.rs @@ -463,25 +463,22 @@ impl Servo { // The compositor coordinates with the client window to create the final // rendered page and display it somewhere. let shutdown_state = Rc::new(Cell::new(ShutdownState::NotShuttingDown)); - let compositor = IOCompositor::new( - InitialCompositorState { - sender: compositor_proxy, - receiver: compositor_receiver, - constellation_chan: constellation_chan.clone(), - time_profiler_chan, - mem_profiler_chan, - webrender, - webrender_document, - webrender_api, - rendering_context, - webrender_gl, - #[cfg(feature = "webxr")] - webxr_main_thread, - shutdown_state: shutdown_state.clone(), - event_loop_waker, - }, - opts.debug.convert_mouse_to_touch, - ); + let compositor = IOCompositor::new(InitialCompositorState { + sender: compositor_proxy, + receiver: compositor_receiver, + constellation_chan: constellation_chan.clone(), + time_profiler_chan, + mem_profiler_chan, + webrender, + webrender_document, + webrender_api, + rendering_context, + webrender_gl, + #[cfg(feature = "webxr")] + webxr_main_thread, + shutdown_state: shutdown_state.clone(), + event_loop_waker, + }); let constellation_proxy = ConstellationProxy::new(constellation_chan); Self { diff --git a/ports/servoshell/desktop/headed_window.rs b/ports/servoshell/desktop/headed_window.rs index 544895b6226..32ae5b5a9e7 100644 --- a/ports/servoshell/desktop/headed_window.rs +++ b/ports/servoshell/desktop/headed_window.rs @@ -79,6 +79,9 @@ pub struct Window { /// the target of egui rendering and also where Servo rendering results are finally /// blitted. window_rendering_context: Rc, + /// A helper that simulates touch events when the `--simulate-touch-events` flag + /// is enabled. + touch_event_simulator: Option, // Keep this as the last field of the struct to ensure that the rendering context is // dropped first. // (https://github.com/servo/servo/issues/36711) @@ -179,6 +182,9 @@ impl Window { modifiers_state: Cell::new(ModifiersState::empty()), toolbar_height: Cell::new(Default::default()), window_rendering_context, + touch_event_simulator: servoshell_preferences + .simulate_touch_events + .then(Default::default), rendering_context, } } @@ -275,7 +281,29 @@ impl Window { } /// Helper function to handle a click - fn handle_mouse(&self, webview: &WebView, button: MouseButton, action: ElementState) { + fn handle_mouse_button_event( + &self, + webview: &WebView, + button: MouseButton, + action: ElementState, + ) { + // `point` can be outside viewport, such as at toolbar with negative y-coordinate. + let point = self.webview_relative_mouse_point.get(); + if !webview.rect().contains(point) { + return; + } + + if self + .touch_event_simulator + .as_ref() + .is_some_and(|touch_event_simulator| { + touch_event_simulator + .maybe_consume_move_button_event(webview, button, action, point) + }) + { + return; + } + let mouse_button = match &button { MouseButton::Left => ServoMouseButton::Left, MouseButton::Right => ServoMouseButton::Right, @@ -285,11 +313,6 @@ impl Window { MouseButton::Other(value) => ServoMouseButton::Other(*value), }; - let point = self.webview_relative_mouse_point.get(); - // `point` can be outside viewport, such as at toolbar with negative y-coordinate. - if !webview.rect().contains(point) { - return; - } let action = match action { ElementState::Pressed => MouseButtonAction::Down, ElementState::Released => MouseButtonAction::Up, @@ -302,6 +325,36 @@ impl Window { ))); } + /// Helper function to handle mouse move events. + fn handle_mouse_move_event(&self, webview: &WebView, position: PhysicalPosition) { + let mut point = winit_position_to_euclid_point(position).to_f32(); + point.y -= (self.toolbar_height() * self.hidpi_scale_factor()).0; + + let previous_point = self.webview_relative_mouse_point.get(); + self.webview_relative_mouse_point.set(point); + + if !webview.rect().contains(point) { + if webview.rect().contains(previous_point) { + webview.notify_input_event(InputEvent::MouseLeftViewport( + MouseLeftViewportEvent::default(), + )); + } + return; + } + + if self + .touch_event_simulator + .as_ref() + .is_some_and(|touch_event_simulator| { + touch_event_simulator.maybe_consume_mouse_move_event(webview, point) + }) + { + return; + } + + webview.notify_input_event(InputEvent::MouseMove(MouseMoveEvent::new(point))); + } + /// Handle key events before sending them to Servo. fn handle_intercepted_key_bindings( &self, @@ -623,22 +676,10 @@ impl WindowPortsMethods for Window { WindowEvent::KeyboardInput { event, .. } => self.handle_keyboard_input(state, event), WindowEvent::ModifiersChanged(modifiers) => self.modifiers_state.set(modifiers.state()), WindowEvent::MouseInput { state, button, .. } => { - self.handle_mouse(&webview, button, state); + self.handle_mouse_button_event(&webview, button, state); }, WindowEvent::CursorMoved { position, .. } => { - let mut point = winit_position_to_euclid_point(position).to_f32(); - point.y -= (self.toolbar_height() * self.hidpi_scale_factor()).0; - - let previous_point = self.webview_relative_mouse_point.get(); - if webview.rect().contains(point) { - webview.notify_input_event(InputEvent::MouseMove(MouseMoveEvent::new(point))); - } else if webview.rect().contains(previous_point) { - webview.notify_input_event(InputEvent::MouseLeftViewport( - MouseLeftViewportEvent::default(), - )); - } - - self.webview_relative_mouse_point.set(point); + self.handle_mouse_move_event(&webview, position); }, WindowEvent::CursorLeft { .. } => { if webview @@ -967,3 +1008,57 @@ impl XRWindowPose { self.xr_rotation.set(rotation); } } + +#[derive(Default)] +pub struct TouchEventSimulator { + pub left_mouse_button_down: Cell, +} + +impl TouchEventSimulator { + fn maybe_consume_move_button_event( + &self, + webview: &WebView, + button: MouseButton, + action: ElementState, + point: Point2D, + ) -> bool { + if button != MouseButton::Left { + return false; + } + + if action == ElementState::Pressed && !self.left_mouse_button_down.get() { + webview.notify_input_event(InputEvent::Touch(TouchEvent::new( + TouchEventType::Down, + TouchId(0), + point, + ))); + self.left_mouse_button_down.set(true); + } else if action == ElementState::Released { + webview.notify_input_event(InputEvent::Touch(TouchEvent::new( + TouchEventType::Up, + TouchId(0), + point, + ))); + self.left_mouse_button_down.set(false); + } + + true + } + + fn maybe_consume_mouse_move_event( + &self, + webview: &WebView, + point: Point2D, + ) -> bool { + if !self.left_mouse_button_down.get() { + return false; + } + + webview.notify_input_event(InputEvent::Touch(TouchEvent::new( + TouchEventType::Move, + TouchId(0), + point, + ))); + true + } +} diff --git a/ports/servoshell/prefs.rs b/ports/servoshell/prefs.rs index b7b6c3f7a4f..fc161f816fe 100644 --- a/ports/servoshell/prefs.rs +++ b/ports/servoshell/prefs.rs @@ -71,6 +71,8 @@ pub(crate) struct ServoShellPreferences { /// An override for the screen resolution. This is useful for testing behavior on different screen sizes, /// such as the screen of a mobile device. pub screen_size_override: Option>, + /// Whether or not to simulate touch events using mouse events. + pub simulate_touch_events: bool, /// If not-None, the path to a file to output the default WebView's rendered output /// after waiting for a stable image, this implies `Self::exit_after_load`. pub output_image_path: Option, @@ -82,15 +84,12 @@ pub(crate) struct ServoShellPreferences { /// `None` to disable WebDriver or `Some` with a port number to start a server to listen to /// remote WebDriver commands. pub webdriver_port: Option, - /// Whether the CLI option to enable experimental prefs was present at startup. pub experimental_prefs_enabled: bool, - /// Log filter given in the `log_filter` spec as a String, if any. /// If a filter is passed, the logger should adjust accordingly. #[cfg(target_env = "ohos")] pub log_filter: Option, - /// Log also to a file #[cfg(target_env = "ohos")] pub log_to_file: bool, @@ -106,6 +105,7 @@ impl Default for ServoShellPreferences { initial_window_size: Size2D::new(1024, 740), no_native_titlebar: true, screen_size_override: None, + simulate_touch_events: false, searchpage: "https://duckduckgo.com/html/?q=%s".into(), tracing_filter: None, url: None, @@ -505,6 +505,11 @@ struct CmdArgs { parse(parse_resolution_string), fallback(None))] screen_size_override: Option>, + /// Use mouse events to simulate touch events. Left button presses will be converted to touch + /// and mouse movements while the left button is pressed will be converted to touch movements. + #[bpaf(long("simulate-touch-events"))] + simulate_touch_events: bool, + /// Define a custom filter for traces. Overrides `SERVO_TRACING` if set. #[bpaf(long("tracing-filter"), argument("FILTER"))] tracing_filter: Option, @@ -663,6 +668,7 @@ pub(crate) fn parse_command_line_arguments(args: Vec) -> ArgumentParsing tracing_filter: cmd_args.tracing_filter, initial_window_size: cmd_args.window_size.unwrap_or(default_window_size), screen_size_override: cmd_args.screen_size_override, + simulate_touch_events: cmd_args.simulate_touch_events, webdriver_port: cmd_args.webdriver_port, output_image_path: cmd_args.output.map(|p| p.to_string_lossy().into_owned()), exit_after_stable_image: cmd_args.exit, @@ -728,10 +734,6 @@ fn print_debug_options_usage(app: &str) { "Usage: {} debug option,[options,...]\n\twhere options include\n\nOptions:", app ); - print_option( - "convert-mouse-to-touch", - "Send touch events instead of mouse events", - ); print_option( "disable-share-style-cache", "Disable the style sharing cache.",