From f3a8bf8ca20ddfeb843e842e2d1721018cb4b920 Mon Sep 17 00:00:00 2001 From: Bi Fuguo <1782765876@qq.com> Date: Tue, 25 Feb 2025 15:13:16 +0800 Subject: [PATCH] Touch handler: Fix race condition and rate-limit move events (#35537) * TouchSequenceInfo is added to store information about a touch sequence. For details about TouchSequenceInfo, see the code comments. The handling_touch_move attribute is added to the TouchHandler, indicating that the script is processing the touch move event. When handling_touch_move is set to true, the touch move event does not need to be sent to the script thread. Signed-off-by: kongbai1996 <1782765876@qq.com> * move touch state, active_touch_point and handling_touch_move to TouchSequenceInfo form TouchHandler. remove TouchSequenceInfo end_sequence property, add Finished state mark sequence end. if preventDefault on touchup, do not prevent Fling. Signed-off-by: kongbai1996 <1782765876@qq.com> * Refactor Touchhandler - Add a newtype wrapper for the TouchSequenceId - Move more state back into the TouchSequenceState - Rename TouchAction to TouchMoveAction, since it only covers immediate actions now. Everything else is handled via state, since it needs to wait on the handler. Signed-off-by: Jonathan Schwender * Fix test-tidy Signed-off-by: Jonathan Schwender * Fix clippy missing-default lint Signed-off-by: Jonathan Schwender * Fix remaining clippy lints Signed-off-by: Jonathan Schwender * Remove accidental committed test file Signed-off-by: Jonathan Schwender * Remove wrong todo comment (move events that are sent to script are just raw touchpoints, no merging needed) Signed-off-by: Jonathan Schwender * Fix preventdefault after long touch_down handler Signed-off-by: Jonathan Schwender --------- Signed-off-by: kongbai1996 <1782765876@qq.com> Signed-off-by: Jonathan Schwender Co-authored-by: Jonathan Schwender --- components/compositing/compositor.rs | 286 ++++++++--- components/compositing/touch.rs | 541 ++++++++++++++++----- components/script/script_thread.rs | 43 +- components/shared/compositing/lib.rs | 4 +- components/shared/embedder/input_events.rs | 68 ++- components/shared/script/lib.rs | 5 +- components/shared/script/script_msg.rs | 12 +- ports/servoshell/desktop/headed_window.rs | 15 +- ports/servoshell/egl/app_state.rs | 50 +- 9 files changed, 746 insertions(+), 278 deletions(-) diff --git a/components/compositing/compositor.rs b/components/compositing/compositor.rs index 6d34e8b96b1..ba7380f61d4 100644 --- a/components/compositing/compositor.rs +++ b/components/compositing/compositor.rs @@ -22,7 +22,7 @@ use compositing_traits::{ use crossbeam_channel::Sender; use embedder_traits::{ Cursor, InputEvent, MouseButton, MouseButtonAction, MouseButtonEvent, MouseMoveEvent, - ShutdownState, TouchAction, TouchEvent, TouchEventType, TouchId, + ShutdownState, TouchEvent, TouchEventType, TouchId, }; use euclid::{Point2D, Rect, Scale, Size2D, Transform3D, Vector2D}; use fnv::{FnvHashMap, FnvHashSet}; @@ -33,7 +33,7 @@ use pixels::{CorsStatus, Image, PixelFormat}; use profile_traits::time::{self as profile_time, ProfilerCategory}; use profile_traits::time_profile; use script_traits::{ - AnimationState, AnimationTickType, EventResult, ScriptThreadMessage, ScrollState, + AnimationState, AnimationTickType, ScriptThreadMessage, ScrollState, TouchEventResult, WindowSizeData, WindowSizeType, }; use servo_config::opts; @@ -57,7 +57,7 @@ use webrender_traits::{ CompositorHitTestResult, CrossProcessCompositorMessage, ImageUpdate, UntrustedNodeAddress, }; -use crate::touch::TouchHandler; +use crate::touch::{TouchHandler, TouchMoveAction, TouchMoveAllowed, TouchSequenceState}; use crate::webview::{UnknownWebView, WebView, WebViewAlreadyExists, WebViewManager}; use crate::windowing::{self, EmbedderCoordinates, WebRenderDebugOption, WindowMethods}; use crate::InitialCompositorState; @@ -1303,28 +1303,25 @@ impl IOCompositor { InputEvent::MouseButton(event) => { match event.action { MouseButtonAction::Click => {}, - MouseButtonAction::Down => self.on_touch_down(TouchEvent { - event_type: TouchEventType::Down, - id: TouchId(0), - point: event.point, - action: TouchAction::NoAction, - }), - MouseButtonAction::Up => self.on_touch_up(TouchEvent { - event_type: TouchEventType::Up, - id: TouchId(0), - point: event.point, - action: TouchAction::NoAction, - }), + MouseButtonAction::Down => self.on_touch_down(TouchEvent::new( + TouchEventType::Down, + TouchId(0), + event.point, + )), + MouseButtonAction::Up => self.on_touch_up(TouchEvent::new( + TouchEventType::Up, + TouchId(0), + event.point, + )), } return; }, InputEvent::MouseMove(event) => { - self.on_touch_move(TouchEvent { - event_type: TouchEventType::Move, - id: TouchId(0), - point: event.point, - action: TouchAction::NoAction, - }); + self.on_touch_move(TouchEvent::new( + TouchEventType::Move, + TouchId(0), + event.point, + )); return; }, _ => {}, @@ -1386,11 +1383,12 @@ impl IOCompositor { .collect() } - fn send_touch_event(&self, event: TouchEvent) { + fn send_touch_event(&self, mut event: TouchEvent) -> bool { let Some(result) = self.hit_test_at_point(event.point) else { - return; + return false; }; + event.init_sequence_id(self.touch_handler.current_sequence_id); let event = InputEvent::Touch(event); if let Err(e) = self .global @@ -1398,6 +1396,9 @@ impl IOCompositor { .send(ConstellationMsg::ForwardInputEvent(event, Some(result))) { warn!("Sending event to constellation failed ({:?}).", e); + false + } else { + true } } @@ -1419,16 +1420,21 @@ impl IOCompositor { self.send_touch_event(event); } - fn on_touch_move(&mut self, mut event: TouchEvent) { - let action: TouchAction = self.touch_handler.on_touch_move(event.id, event.point); - if TouchAction::NoAction != action { - if !self.touch_handler.prevent_move { + fn on_touch_move(&mut self, event: TouchEvent) { + let action: TouchMoveAction = self.touch_handler.on_touch_move(event.id, event.point); + if TouchMoveAction::NoAction != action { + // if first move processed and allowed, we directly process the move event, + // without waiting for the script handler. + if self + .touch_handler + .move_allowed(self.touch_handler.current_sequence_id) + { match action { - TouchAction::Scroll(delta, point) => self.on_scroll_window_event( + TouchMoveAction::Scroll(delta, point) => self.on_scroll_window_event( ScrollLocation::Delta(LayoutVector2D::from_untyped(delta.to_untyped())), point.cast(), ), - TouchAction::Zoom(magnification, scroll_delta) => { + TouchMoveAction::Zoom(magnification, scroll_delta) => { let cursor = Point2D::new(-1, -1); // Make sure this hits the base layer. // The order of these events doesn't matter, because zoom is handled by @@ -1447,69 +1453,203 @@ impl IOCompositor { }, _ => {}, } - } else { - event.action = action; } - self.send_touch_event(event); + // When the event is touchmove, if the script thread is processing the touch + // move event, we skip sending the event to the script thread. + // This prevents the script thread from stacking up for a large amount of time. + if !self + .touch_handler + .is_handling_touch_move(self.touch_handler.current_sequence_id) && + self.send_touch_event(event) + { + self.touch_handler + .set_handling_touch_move(self.touch_handler.current_sequence_id, true); + } } } - fn on_touch_up(&mut self, mut event: TouchEvent) { - let action = self.touch_handler.on_touch_up(event.id, event.point); - event.action = action; + fn on_touch_up(&mut self, event: TouchEvent) { + self.touch_handler.on_touch_up(event.id, event.point); self.send_touch_event(event); } fn on_touch_cancel(&mut self, event: TouchEvent) { // Send the event to script. self.touch_handler.on_touch_cancel(event.id, event.point); - self.send_touch_event(event) + self.send_touch_event(event); } - fn on_touch_event_processed(&mut self, result: EventResult) { + fn on_touch_event_processed(&mut self, result: TouchEventResult) { match result { - EventResult::DefaultPrevented(event_type) => { + TouchEventResult::DefaultPrevented(sequence_id, event_type) => { + debug!( + "Touch event {:?} in sequence {:?} prevented!", + event_type, sequence_id + ); match event_type { - TouchEventType::Down | TouchEventType::Move => { - self.touch_handler.prevent_move = true; + TouchEventType::Down => { + // prevents both click and move + self.touch_handler.prevent_click(sequence_id); + self.touch_handler.prevent_move(sequence_id); + self.touch_handler + .remove_pending_touch_move_action(sequence_id); }, - _ => {}, - } - self.touch_handler.prevent_click = true; - }, - EventResult::DefaultAllowed(action) => { - self.touch_handler.prevent_move = false; - match action { - TouchAction::Click(point) => { - if !self.touch_handler.prevent_click { - self.simulate_mouse_click(point); + TouchEventType::Move => { + // script thread processed the touch move event, mark this false. + let info = self.touch_handler.get_touch_sequence_mut(sequence_id); + info.prevent_move = TouchMoveAllowed::Prevented; + if let TouchSequenceState::PendingFling { .. } = info.state { + info.state = TouchSequenceState::Finished; + } + self.touch_handler.prevent_move(sequence_id); + self.touch_handler + .set_handling_touch_move(self.touch_handler.current_sequence_id, false); + self.touch_handler + .remove_pending_touch_move_action(sequence_id); + }, + TouchEventType::Up => { + // Note: We don't have to consider PendingFling here, since we handle that + // in the DefaultAllowed case of the touch_move event. + // Note: Removing can and should fail, if we still have an active Fling, + let Some(info) = + &mut self.touch_handler.touch_sequence_map.get_mut(&sequence_id) + else { + // The sequence ID could already be removed, e.g. if Fling finished, + // before the touch_up event was handled (since fling can start + // immediately if move was previously allowed, and clicks are anyway not + // happening from fling). + return; + }; + match info.state { + TouchSequenceState::PendingClick(_) => { + info.state = TouchSequenceState::Finished; + self.touch_handler.remove_touch_sequence(sequence_id); + }, + TouchSequenceState::Flinging { .. } => { + // We can't remove the touch sequence yet + }, + TouchSequenceState::Finished => { + self.touch_handler.remove_touch_sequence(sequence_id); + }, + TouchSequenceState::Touching | + TouchSequenceState::Panning { .. } | + TouchSequenceState::Pinching | + TouchSequenceState::MultiTouch | + TouchSequenceState::PendingFling { .. } => { + // It's possible to transition from Pinch to pan, Which means that + // a touch_up event for a pinch might have arrived here, but we + // already transitioned to pan or even PendingFling. + // We don't need to do anything in these cases though. + }, } }, - TouchAction::Flinging(velocity, point) => { - self.touch_handler.on_fling(velocity, point); + TouchEventType::Cancel => { + // We could still have pending event handlers, so we remove the pending + // actions, and try to remove the touch sequence. + self.touch_handler + .remove_pending_touch_move_action(sequence_id); + // Todo: Perhaps we need to check how many fingers are still active. + self.touch_handler.get_touch_sequence_mut(sequence_id).state = + TouchSequenceState::Finished; + // Cancel should be the last event for a given sequence_id. + self.touch_handler.try_remove_touch_sequence(sequence_id); }, - TouchAction::Scroll(delta, point) => self.on_scroll_window_event( - ScrollLocation::Delta(LayoutVector2D::from_untyped(delta.to_untyped())), - point.cast(), - ), - TouchAction::Zoom(magnification, scroll_delta) => { - let cursor = Point2D::new(-1, -1); // Make sure this hits the base layer. - - // The order of these events doesn't matter, because zoom is handled by - // a root display list and the scroll event here is handled by the scroll - // applied to the content display list. - self.pending_scroll_zoom_events - .push(ScrollZoomEvent::PinchZoom(magnification)); - self.pending_scroll_zoom_events - .push(ScrollZoomEvent::Scroll(ScrollEvent { - scroll_location: ScrollLocation::Delta( - LayoutVector2D::from_untyped(scroll_delta.to_untyped()), - ), - cursor, - event_count: 1, - })); + } + }, + TouchEventResult::DefaultAllowed(sequence_id, event_type) => { + debug!( + "Touch event {:?} in sequence {:?} allowed", + event_type, sequence_id + ); + match event_type { + TouchEventType::Down => {}, + TouchEventType::Move => { + if let Some(action) = + self.touch_handler.pending_touch_move_action(sequence_id) + { + match action { + TouchMoveAction::Scroll(delta, point) => self + .on_scroll_window_event( + ScrollLocation::Delta(LayoutVector2D::from_untyped( + delta.to_untyped(), + )), + point.cast(), + ), + TouchMoveAction::Zoom(magnification, scroll_delta) => { + let cursor = Point2D::new(-1, -1); + // Make sure this hits the base layer. + // The order of these events doesn't matter, because zoom is handled by + // a root display list and the scroll event here is handled by the scroll + // applied to the content display list. + self.pending_scroll_zoom_events + .push(ScrollZoomEvent::PinchZoom(magnification)); + self.pending_scroll_zoom_events + .push(ScrollZoomEvent::Scroll(ScrollEvent { + scroll_location: ScrollLocation::Delta( + LayoutVector2D::from_untyped( + scroll_delta.to_untyped(), + ), + ), + cursor, + event_count: 1, + })); + }, + TouchMoveAction::NoAction => { + // This shouldn't happen, but we can also just ignore it. + }, + } + self.touch_handler + .remove_pending_touch_move_action(sequence_id); + } + self.touch_handler + .set_handling_touch_move(self.touch_handler.current_sequence_id, false); + let info = self.touch_handler.get_touch_sequence_mut(sequence_id); + info.prevent_move = TouchMoveAllowed::Allowed; + if let TouchSequenceState::PendingFling { velocity, cursor } = info.state { + info.state = TouchSequenceState::Flinging { velocity, cursor } + } + }, + TouchEventType::Up => { + let Some(info) = + self.touch_handler.touch_sequence_map.get_mut(&sequence_id) + else { + // The sequence was already removed because there is no default action. + return; + }; + match info.state { + TouchSequenceState::PendingClick(point) => { + info.state = TouchSequenceState::Finished; + // PreventDefault from touch_down may have been processed after + // touch_up already occurred. + if !info.prevent_click { + self.simulate_mouse_click(point); + } + self.touch_handler.remove_touch_sequence(sequence_id); + }, + TouchSequenceState::Flinging { .. } => { + // We can't remove the touch sequence yet + }, + TouchSequenceState::Finished => { + self.touch_handler.remove_touch_sequence(sequence_id); + }, + TouchSequenceState::Panning { .. } | + TouchSequenceState::Pinching | + TouchSequenceState::PendingFling { .. } => { + // It's possible to transition from Pinch to pan, Which means that + // a touch_up event for a pinch might have arrived here, but we + // already transitioned to pan or even PendingFling. + // We don't need to do anything in these cases though. + }, + TouchSequenceState::MultiTouch | TouchSequenceState::Touching => { + // We transitioned to touching from multi-touch or pinching. + }, + } + }, + TouchEventType::Cancel => { + self.touch_handler + .remove_pending_touch_move_action(sequence_id); + self.touch_handler.remove_touch_sequence(sequence_id); }, - _ => {}, } }, } diff --git a/components/compositing/touch.rs b/components/compositing/touch.rs index ef87323a0b0..be6f1189560 100644 --- a/components/compositing/touch.rs +++ b/components/compositing/touch.rs @@ -2,12 +2,14 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -use embedder_traits::{TouchAction, TouchId}; -use euclid::{Point2D, Scale, Vector2D}; -use log::{debug, warn}; -use webrender_api::units::{DeviceIntPoint, DevicePixel, LayoutVector2D}; +use std::collections::HashMap; -use self::TouchState::*; +use embedder_traits::{TouchId, TouchSequenceId}; +use euclid::{Point2D, Scale, Vector2D}; +use log::{debug, error, warn}; +use webrender_api::units::{DeviceIntPoint, DevicePixel, DevicePoint, LayoutVector2D}; + +use self::TouchSequenceState::*; // TODO: All `_SCREEN_PX` units below are currently actually used as `DevicePixel` // without multiplying with the `hidpi_factor`. This should be fixed and the @@ -22,10 +24,124 @@ const FLING_MIN_SCREEN_PX: f32 = 3.0; const FLING_MAX_SCREEN_PX: f32 = 4000.0; pub struct TouchHandler { - pub state: TouchState, - pub active_touch_points: Vec, - pub prevent_move: bool, + pub current_sequence_id: TouchSequenceId, + // todo: VecDeque + modulo arithmetic would be more efficient. + pub touch_sequence_map: HashMap, +} + +/// Whether the default move action is allowed or not. +#[derive(Debug, Eq, PartialEq)] +pub enum TouchMoveAllowed { + /// The default move action is prevented by script + Prevented, + /// The default move action is allowed + Allowed, + /// The initial move handler result is still pending + Pending, +} + +pub struct TouchSequenceInfo { + /// touch sequence state + pub(crate) state: TouchSequenceState, + /// touch sequence active touch points + active_touch_points: Vec, + /// The script thread is already processing a touchmove operation. + /// + /// We use this to skip sending the event to the script thread, + /// to prevent overloading script. + handling_touch_move: bool, + /// Do not perform a click action. + /// + /// This happens when + /// - We had a touch move larger than the minimum distance OR + /// - We had multiple active touchpoints OR + /// - `preventDefault()` was called in a touch_down or touch_up handler pub prevent_click: bool, + /// Whether move is allowed, prevented or the result is still pending. + /// Once the first move has been processed by script, we can transition to + /// non-cancellable events, and directly perform the pan without waiting for script. + pub prevent_move: TouchMoveAllowed, + /// Move operation waiting to be processed in the touch sequence. + /// + /// This is only used while the first touch move is processed in script. + /// Todo: It would be nice to merge this into the TouchSequenceState, but + /// this requires some additional work to handle the merging of pending + /// touch move events. Presumably if we keep a history of previous touch points, + /// this would allow a better fling algorithm and easier merging of zoom events. + pending_touch_move_action: Option, +} + +impl TouchSequenceInfo { + fn touch_count(&self) -> usize { + self.active_touch_points.len() + } + + fn pinch_distance_and_center(&self) -> (f32, Point2D) { + debug_assert_eq!(self.touch_count(), 2); + let p0 = self.active_touch_points[0].point; + let p1 = self.active_touch_points[1].point; + let center = p0.lerp(p1, 0.5); + let distance = (p0 - p1).length(); + + (distance, center) + } + + fn update_pending_touch_move_action(&mut self, action: TouchMoveAction) { + debug_assert!(self.prevent_move == TouchMoveAllowed::Pending); + + if let Some(pre_action) = self.pending_touch_move_action { + let combine_action = match (pre_action, action) { + (TouchMoveAction::NoAction, _) | (_, TouchMoveAction::NoAction) => action, + // Combine touch move action. + (TouchMoveAction::Scroll(delta, point), TouchMoveAction::Scroll(delta_new, _)) => { + TouchMoveAction::Scroll(delta + delta_new, point) + }, + ( + TouchMoveAction::Scroll(delta, _), + TouchMoveAction::Zoom(magnification, scroll_delta), + ) | + ( + TouchMoveAction::Zoom(magnification, scroll_delta), + TouchMoveAction::Scroll(delta, _), + ) => { + // Todo: It's unclear what the best action would be. Should we keep both + // scroll and zoom? + TouchMoveAction::Zoom(magnification, delta + scroll_delta) + }, + ( + TouchMoveAction::Zoom(magnification, scroll_delta), + TouchMoveAction::Zoom(magnification_new, scroll_delta_new), + ) => TouchMoveAction::Zoom( + magnification * magnification_new, + scroll_delta + scroll_delta_new, + ), + }; + self.pending_touch_move_action = Some(combine_action); + } else { + self.pending_touch_move_action = Some(action); + } + } + + /// Returns true when all touch events of a sequence have been received. + /// This does not mean that all event handlers have finished yet. + fn is_finished(&self) -> bool { + matches!( + self.state, + Finished | Flinging { .. } | PendingFling { .. } | PendingClick(_) + ) + } +} + +/// An action that can be immediately performed in response to a touch move event +/// without waiting for script. +#[derive(Clone, Copy, Debug, PartialEq)] +pub enum TouchMoveAction { + /// Scroll by the provided offset. + Scroll(Vector2D, DevicePoint), + /// Zoom by a magnification factor and scroll by the provided offset. + Zoom(f32, Vector2D), + /// Don't do anything. + NoAction, } #[derive(Clone, Copy, Debug)] @@ -42,25 +158,34 @@ impl TouchPoint { /// The states of the touch input state machine. #[derive(Clone, Copy, Debug, PartialEq)] -pub enum TouchState { - /// Not tracking any touch point - Nothing, - /// A single touch point is active and may perform click or pan default actions. - /// Contains the initial touch location. +pub(crate) enum TouchSequenceState { + /// touch point is active but does not start moving Touching, /// A single touch point is active and has started panning. Panning { velocity: Vector2D, }, + /// A two-finger pinch zoom gesture is active. + Pinching, + /// A multi-touch gesture is in progress. + MultiTouch, + // All states below here are reached after a touch-up, i.e. all events of the sequence + // have already been received. + /// The initial touch move handler has not finished processing yet, so we need to wait + /// for the result in order to transition to fling. + PendingFling { + velocity: Vector2D, + cursor: DeviceIntPoint, + }, /// No active touch points, but there is still scrolling velocity Flinging { velocity: Vector2D, cursor: DeviceIntPoint, }, - /// A two-finger pinch zoom gesture is active. - Pinching, - /// A multi-touch gesture is in progress. Contains the number of active touch points. - MultiTouch, + /// The touch sequence is finished, but a click is still pending, waiting on script. + PendingClick(DevicePoint), + /// touch sequence finished. + Finished, } pub(crate) struct FlingAction { @@ -70,180 +195,356 @@ pub(crate) struct FlingAction { impl TouchHandler { pub fn new() -> Self { - TouchHandler { - state: Nothing, - active_touch_points: Vec::new(), - prevent_move: true, + let finished_info = TouchSequenceInfo { + state: TouchSequenceState::Finished, + active_touch_points: vec![], + handling_touch_move: false, prevent_click: false, + prevent_move: TouchMoveAllowed::Pending, + pending_touch_move_action: None, + }; + TouchHandler { + current_sequence_id: TouchSequenceId::new(), + // We insert a simulated initial touch sequence, which is already finished, + // so that we always have one element in the map, which simplifies creating + // a new touch sequence on touch_down. + touch_sequence_map: HashMap::from([(TouchSequenceId::new(), finished_info)]), } } + pub(crate) fn set_handling_touch_move(&mut self, sequence_id: TouchSequenceId, flag: bool) { + self.touch_sequence_map + .get_mut(&sequence_id) + .unwrap() + .handling_touch_move = flag; + } + + pub(crate) fn is_handling_touch_move(&self, sequence_id: TouchSequenceId) -> bool { + self.touch_sequence_map + .get(&sequence_id) + .unwrap() + .handling_touch_move + } + + pub(crate) fn prevent_click(&mut self, sequence_id: TouchSequenceId) { + self.touch_sequence_map + .get_mut(&sequence_id) + .unwrap() + .prevent_click = true; + } + + pub(crate) fn prevent_move(&mut self, sequence_id: TouchSequenceId) { + self.touch_sequence_map + .get_mut(&sequence_id) + .unwrap() + .prevent_move = TouchMoveAllowed::Prevented; + } + + /// Returns true if default move actions are allowed, false if prevented or the result + /// is still pending., + pub(crate) fn move_allowed(&mut self, sequence_id: TouchSequenceId) -> bool { + self.touch_sequence_map + .get(&sequence_id) + .unwrap() + .prevent_move == + TouchMoveAllowed::Allowed + } + + pub(crate) fn pending_touch_move_action( + &mut self, + sequence_id: TouchSequenceId, + ) -> Option { + match self.touch_sequence_map.get(&sequence_id) { + Some(sequence) => sequence.pending_touch_move_action, + None => None, + } + } + + pub(crate) fn remove_pending_touch_move_action(&mut self, sequence_id: TouchSequenceId) { + if let Some(sequence) = self.touch_sequence_map.get_mut(&sequence_id) { + sequence.pending_touch_move_action = None; + } + } + + // try to remove touch sequence, if touch sequence end and not has pending action. + pub(crate) fn try_remove_touch_sequence(&mut self, sequence_id: TouchSequenceId) { + if let Some(sequence) = self.touch_sequence_map.get(&sequence_id) { + if sequence.pending_touch_move_action.is_none() && sequence.state == Finished { + self.touch_sequence_map.remove(&sequence_id); + } + } + } + + pub(crate) fn remove_touch_sequence(&mut self, sequence_id: TouchSequenceId) { + let old = self.touch_sequence_map.remove(&sequence_id); + debug_assert!(old.is_some(), "Sequence already removed?"); + } + + pub fn get_current_touch_sequence_mut(&mut self) -> &mut TouchSequenceInfo { + self.touch_sequence_map + .get_mut(&self.current_sequence_id) + .expect("Current Touch sequence does not exist") + } + + pub(crate) fn get_touch_sequence(&self, sequence_id: TouchSequenceId) -> &TouchSequenceInfo { + self.touch_sequence_map + .get(&sequence_id) + .expect("Touch sequence not found.") + } + pub(crate) fn get_touch_sequence_mut( + &mut self, + sequence_id: TouchSequenceId, + ) -> &mut TouchSequenceInfo { + self.touch_sequence_map + .get_mut(&sequence_id) + .expect("Touch sequence not found.") + } + pub fn on_touch_down(&mut self, id: TouchId, point: Point2D) { - let point = TouchPoint::new(id, point); - self.active_touch_points.push(point); - self.state = Touching; - self.prevent_click = match self.touch_count() { - 1 => { - self.prevent_move = true; - false - }, - _ => true, - }; + // if the current sequence ID does not exist in the map, then it was already handled + if !self + .touch_sequence_map + .contains_key(&self.current_sequence_id) || + self.get_touch_sequence(self.current_sequence_id) + .is_finished() + { + self.current_sequence_id.next(); + debug!("Entered new touch sequence: {:?}", self.current_sequence_id); + let active_touch_points = vec![TouchPoint::new(id, point)]; + self.touch_sequence_map.insert( + self.current_sequence_id, + TouchSequenceInfo { + state: Touching, + active_touch_points, + handling_touch_move: false, + prevent_click: false, + prevent_move: TouchMoveAllowed::Pending, + pending_touch_move_action: None, + }, + ); + } else { + debug!("Touch down in sequence {:?}.", self.current_sequence_id); + let touch_sequence = self.get_current_touch_sequence_mut(); + touch_sequence + .active_touch_points + .push(TouchPoint::new(id, point)); + match touch_sequence.active_touch_points.len() { + 2 => { + touch_sequence.state = Pinching; + }, + 3.. => { + touch_sequence.state = MultiTouch; + }, + 0..2 => { + unreachable!("Secondary touch_down event with less than 2 fingers active?"); + }, + } + // Multiple fingers prevent a click. + touch_sequence.prevent_click = true; + } } pub fn on_vsync(&mut self) -> Option { + let touch_sequence = self.touch_sequence_map.get_mut(&self.current_sequence_id)?; + let Flinging { velocity, ref cursor, - } = &mut self.state + } = &mut touch_sequence.state else { return None; }; if velocity.length().abs() < FLING_MIN_SCREEN_PX { - self.state = Nothing; - return None; + touch_sequence.state = Finished; + // If we were flinging previously, there could still be a touch_up event result + // coming in after we stopped flinging + self.try_remove_touch_sequence(self.current_sequence_id); + None + } else { + // TODO: Probably we should multiply with the current refresh rate (and divide on each frame) + // or save a timestamp to account for a potentially changing display refresh rate. + *velocity *= FLING_SCALING_FACTOR; + debug_assert!(velocity.length() <= FLING_MAX_SCREEN_PX); + Some(FlingAction { + delta: LayoutVector2D::new(velocity.x, velocity.y), + cursor: *cursor, + }) } - // TODO: Probably we should multiply with the current refresh rate (and divide on each frame) - // or save a timestamp to account for a potentially changing display refresh rate. - *velocity *= FLING_SCALING_FACTOR; - debug_assert!(velocity.length() <= FLING_MAX_SCREEN_PX); - Some(FlingAction { - delta: LayoutVector2D::new(velocity.x, velocity.y), - cursor: *cursor, - }) } - pub fn on_touch_move(&mut self, id: TouchId, point: Point2D) -> TouchAction { - let idx = match self.active_touch_points.iter_mut().position(|t| t.id == id) { + pub fn on_touch_move( + &mut self, + id: TouchId, + point: Point2D, + ) -> TouchMoveAction { + let touch_sequence = self.get_touch_sequence_mut(self.current_sequence_id); + let idx = match touch_sequence + .active_touch_points + .iter_mut() + .position(|t| t.id == id) + { Some(i) => i, None => { - warn!("Got a touchmove event for a non-active touch point"); - return TouchAction::NoAction; + unreachable!("Got a touchmove event for a non-active touch point"); }, }; - let old_point = self.active_touch_points[idx].point; + let old_point = touch_sequence.active_touch_points[idx].point; let delta = point - old_point; - let action = match self.touch_count() { + let action = match touch_sequence.touch_count() { 1 => { - if let Panning { ref mut velocity } = self.state { + if let Panning { ref mut velocity } = touch_sequence.state { // TODO: Probably we should track 1-3 more points and use a smarter algorithm *velocity += delta; *velocity /= 2.0; - TouchAction::Scroll(delta, point) + // update the touch point every time when panning. + touch_sequence.active_touch_points[idx].point = point; + TouchMoveAction::Scroll(delta, point) } else if delta.x.abs() > TOUCH_PAN_MIN_SCREEN_PX || delta.y.abs() > TOUCH_PAN_MIN_SCREEN_PX { - self.state = Panning { + touch_sequence.state = Panning { velocity: Vector2D::new(delta.x, delta.y), }; - self.prevent_click = true; - TouchAction::Scroll(delta, point) + // No clicks should be issued after we transitioned to move. + touch_sequence.prevent_click = true; + // update the touch point + touch_sequence.active_touch_points[idx].point = point; + TouchMoveAction::Scroll(delta, point) } else { - TouchAction::NoAction + // We don't update the touchpoint, so multiple small moves can + // accumulate and merge into a larger move. + TouchMoveAction::NoAction } }, 2 => { - if self.state == Pinching || + if touch_sequence.state == Pinching || delta.x.abs() > TOUCH_PAN_MIN_SCREEN_PX || delta.y.abs() > TOUCH_PAN_MIN_SCREEN_PX { - self.state = Pinching; - let (d0, c0) = self.pinch_distance_and_center(); - self.active_touch_points[idx].point = point; - let (d1, c1) = self.pinch_distance_and_center(); + touch_sequence.state = Pinching; + let (d0, c0) = touch_sequence.pinch_distance_and_center(); + // update the touch point with the enough distance or pinching. + touch_sequence.active_touch_points[idx].point = point; + let (d1, c1) = touch_sequence.pinch_distance_and_center(); let magnification = d1 / d0; let scroll_delta = c1 - c0 * Scale::new(magnification); - TouchAction::Zoom(magnification, scroll_delta) + TouchMoveAction::Zoom(magnification, scroll_delta) } else { - TouchAction::NoAction + // We don't update the touchpoint, so multiple small moves can + // accumulate and merge into a larger move. + TouchMoveAction::NoAction } }, _ => { - self.state = MultiTouch; - TouchAction::NoAction + touch_sequence.active_touch_points[idx].point = point; + touch_sequence.state = MultiTouch; + TouchMoveAction::NoAction }, }; - - // update the touch point with the latest location. - if self.state != Touching { - self.active_touch_points[idx].point = point; + // If the touch action is not `NoAction` and the first move has not been processed, + // set pending_touch_move_action. + if TouchMoveAction::NoAction != action && + touch_sequence.prevent_move == TouchMoveAllowed::Pending + { + touch_sequence.update_pending_touch_move_action(action); } + action } - pub fn on_touch_up(&mut self, id: TouchId, point: Point2D) -> TouchAction { - let old = match self.active_touch_points.iter().position(|t| t.id == id) { - Some(i) => Some(self.active_touch_points.swap_remove(i).point), + pub fn on_touch_up(&mut self, id: TouchId, point: Point2D) { + let touch_sequence = self.get_touch_sequence_mut(self.current_sequence_id); + let old = match touch_sequence + .active_touch_points + .iter() + .position(|t| t.id == id) + { + Some(i) => Some(touch_sequence.active_touch_points.swap_remove(i).point), None => { warn!("Got a touch up event for a non-active touch point"); None }, }; - match self.touch_count() { - 0 => { - if let Panning { velocity } = self.state { - if velocity.length().abs() >= FLING_MIN_SCREEN_PX { - // TODO: point != old. Not sure which one is better to take as cursor for flinging. - debug!( - "Transitioning to Fling. Cursor is {point:?}. Old cursor was {old:?}. \ - Raw velocity is {velocity:?}." - ); - debug_assert!((point.x as i64) < (i32::MAX as i64)); - debug_assert!((point.y as i64) < (i32::MAX as i64)); - let cursor = DeviceIntPoint::new(point.x as i32, point.y as i32); - // Multiplying the initial velocity gives the fling a much more snappy feel - // and serves well as a poor-mans acceleration algorithm. - let velocity = (velocity * 2.0).with_max_length(FLING_MAX_SCREEN_PX); - TouchAction::Flinging(velocity, cursor) - } else { - self.state = Nothing; - TouchAction::NoAction - } + match touch_sequence.state { + Touching => { + if touch_sequence.prevent_click { + touch_sequence.state = Finished; } else { - self.state = Nothing; - if !self.prevent_click { - TouchAction::Click(point) - } else { - TouchAction::NoAction - } + touch_sequence.state = PendingClick(point); } }, - _ => { - self.state = Touching; - TouchAction::NoAction + Panning { velocity } => { + if velocity.length().abs() >= FLING_MIN_SCREEN_PX { + // TODO: point != old. Not sure which one is better to take as cursor for flinging. + debug!( + "Transitioning to Fling. Cursor is {point:?}. Old cursor was {old:?}. \ + Raw velocity is {velocity:?}." + ); + debug_assert!((point.x as i64) < (i32::MAX as i64)); + debug_assert!((point.y as i64) < (i32::MAX as i64)); + let cursor = DeviceIntPoint::new(point.x as i32, point.y as i32); + // Multiplying the initial velocity gives the fling a much more snappy feel + // and serves well as a poor-mans acceleration algorithm. + let velocity = (velocity * 2.0).with_max_length(FLING_MAX_SCREEN_PX); + match touch_sequence.prevent_move { + TouchMoveAllowed::Allowed => { + touch_sequence.state = Flinging { velocity, cursor } + // todo: return Touchaction here, or is it sufficient to just + // wait for the next vsync? + }, + TouchMoveAllowed::Pending => { + touch_sequence.state = PendingFling { velocity, cursor } + }, + TouchMoveAllowed::Prevented => touch_sequence.state = Finished, + } + } else { + touch_sequence.state = Finished; + } + }, + Pinching => { + touch_sequence.state = Touching; + }, + MultiTouch => { + // We stay in multi-touch mode once we entered it until all fingers are lifted. + if touch_sequence.active_touch_points.is_empty() { + touch_sequence.state = Finished; + } + }, + PendingFling { .. } | Flinging { .. } | PendingClick(_) | Finished => { + error!("Touch-up received, but touch handler already in post-touchup state.") }, } + #[cfg(debug_assertions)] + if touch_sequence.active_touch_points.is_empty() { + debug_assert!( + touch_sequence.is_finished(), + "Did not transition to a finished state: {:?}", + touch_sequence.state + ); + } + debug!( + "Touch up with remaining active touchpoints: {:?}, in sequence {:?}", + touch_sequence.active_touch_points.len(), + self.current_sequence_id + ); } pub fn on_touch_cancel(&mut self, id: TouchId, _point: Point2D) { - match self.active_touch_points.iter().position(|t| t.id == id) { + let touch_sequence = self.get_touch_sequence_mut(self.current_sequence_id); + match touch_sequence + .active_touch_points + .iter() + .position(|t| t.id == id) + { Some(i) => { - self.active_touch_points.swap_remove(i); + touch_sequence.active_touch_points.swap_remove(i); }, None => { warn!("Got a touchcancel event for a non-active touch point"); return; }, } - self.state = Nothing; - } - - pub fn on_fling(&mut self, velocity: Vector2D, cursor: DeviceIntPoint) { - self.state = Flinging { velocity, cursor }; - } - - fn touch_count(&self) -> usize { - self.active_touch_points.len() - } - - fn pinch_distance_and_center(&self) -> (f32, Point2D) { - debug_assert_eq!(self.touch_count(), 2); - let p0 = self.active_touch_points[0].point; - let p1 = self.active_touch_points[1].point; - let center = p0.lerp(p1, 0.5); - let distance = (p0 - p1).length(); - - (distance, center) + touch_sequence.state = Finished; } } diff --git a/components/script/script_thread.rs b/components/script/script_thread.rs index 9118b79aa62..12aaacc6424 100644 --- a/components/script/script_thread.rs +++ b/components/script/script_thread.rs @@ -80,11 +80,10 @@ use script_layout_interface::{ node_id_from_scroll_id, LayoutConfig, LayoutFactory, ReflowGoal, ScriptThreadFactory, }; use script_traits::{ - ConstellationInputEvent, DiscardBrowsingContext, DocumentActivity, EventResult, - InitialScriptState, JsEvalResult, LoadData, LoadOrigin, NavigationHistoryBehavior, - NewLayoutInfo, Painter, ProgressiveWebMetricType, ScriptMsg, ScriptThreadMessage, - ScriptToConstellationChan, ScrollState, StructuredSerializedData, UpdatePipelineIdReason, - WindowSizeData, WindowSizeType, + ConstellationInputEvent, DiscardBrowsingContext, DocumentActivity, InitialScriptState, + JsEvalResult, LoadData, LoadOrigin, NavigationHistoryBehavior, NewLayoutInfo, Painter, + ProgressiveWebMetricType, ScriptMsg, ScriptThreadMessage, ScriptToConstellationChan, + ScrollState, StructuredSerializedData, UpdatePipelineIdReason, WindowSizeData, WindowSizeType, }; use servo_atoms::Atom; use servo_config::opts; @@ -1096,22 +1095,24 @@ impl ScriptThread { InputEvent::Touch(touch_event) => { let touch_result = document.handle_touch_event(touch_event, event.hit_test_result, can_gc); - match touch_result { - TouchEventResult::Processed(handled) => { - let result = if handled { - EventResult::DefaultAllowed(touch_event.action) - } else { - EventResult::DefaultPrevented(touch_event.event_type) - }; - let message = ScriptMsg::TouchEventProcessed(result); - self.senders - .pipeline_to_constellation_sender - .send((pipeline_id, message)) - .unwrap(); - }, - _ => { - // TODO: Calling preventDefault on a touchup event should prevent clicks. - }, + if let TouchEventResult::Processed(handled) = touch_result { + let sequence_id = touch_event.expect_sequence_id(); + let result = if handled { + script_traits::TouchEventResult::DefaultAllowed( + sequence_id, + touch_event.event_type, + ) + } else { + script_traits::TouchEventResult::DefaultPrevented( + sequence_id, + touch_event.event_type, + ) + }; + let message = ScriptMsg::TouchEventProcessed(result); + self.senders + .pipeline_to_constellation_sender + .send((pipeline_id, message)) + .unwrap(); } }, InputEvent::Wheel(wheel_event) => { diff --git a/components/shared/compositing/lib.rs b/components/shared/compositing/lib.rs index 66bc59160fa..38390ecef31 100644 --- a/components/shared/compositing/lib.rs +++ b/components/shared/compositing/lib.rs @@ -17,7 +17,7 @@ use euclid::Rect; use ipc_channel::ipc::IpcSender; use log::warn; use pixels::Image; -use script_traits::{AnimationState, EventResult, ScriptThreadMessage}; +use script_traits::{AnimationState, ScriptThreadMessage, TouchEventResult}; use style_traits::CSSPixel; use webrender_api::DocumentId; use webrender_traits::{CrossProcessCompositorApi, CrossProcessCompositorMessage}; @@ -65,7 +65,7 @@ pub enum CompositorMsg { /// Remove a webview. RemoveWebView(TopLevelBrowsingContextId), /// Script has handled a touch event, and either prevented or allowed default actions. - TouchEventProcessed(EventResult), + TouchEventProcessed(TouchEventResult), /// Composite to a PNG file and return the Image over a passed channel. CreatePng(Option>, IpcSender>), /// A reply to the compositor asking if the output image is stable. diff --git a/components/shared/embedder/input_events.rs b/components/shared/embedder/input_events.rs index b7c56c21178..2775ac88c5f 100644 --- a/components/shared/embedder/input_events.rs +++ b/components/shared/embedder/input_events.rs @@ -2,11 +2,11 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -use euclid::Vector2D; use keyboard_types::{CompositionEvent, KeyboardEvent}; +use log::error; use malloc_size_of_derive::MallocSizeOf; use serde::{Deserialize, Serialize}; -use webrender_api::units::{DeviceIntPoint, DevicePixel, DevicePoint}; +use webrender_api::units::DevicePoint; /// An input event that is sent from the embedder to Servo. #[derive(Clone, Debug, Deserialize, Serialize)] @@ -116,33 +116,65 @@ pub enum TouchEventType { Cancel, } -/// The action to take in response to a touch event -#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)] -pub enum TouchAction { - /// Simulate a mouse click. - Click(DevicePoint), - /// Fling by the provided offset - Flinging(Vector2D, DeviceIntPoint), - /// Scroll by the provided offset. - Scroll(Vector2D, DevicePoint), - /// Zoom by a magnification factor and scroll by the provided offset. - Zoom(f32, Vector2D), - /// Don't do anything. - NoAction, -} - /// An opaque identifier for a touch point. /// /// #[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)] pub struct TouchId(pub i32); +/// An ID for a sequence of touch events between a `Down` and the `Up` or `Cancel` event. +#[repr(transparent)] +#[derive(Clone, Copy, Debug, Default, Deserialize, Eq, Hash, PartialEq, Serialize)] +pub struct TouchSequenceId(u32); + +impl TouchSequenceId { + pub const fn new() -> Self { + Self(0) + } + + /// Increments the ID for the next touch sequence. + /// + /// The increment is wrapping, since we can assume that the touch handler + /// script for touch sequence N will have finished processing by the time + /// we have wrapped around. + pub fn next(&mut self) { + self.0 = self.0.wrapping_add(1); + } +} + #[derive(Clone, Copy, Debug, Deserialize, Serialize)] pub struct TouchEvent { pub event_type: TouchEventType, pub id: TouchId, pub point: DevicePoint, - pub action: TouchAction, + /// The sequence_id will be set by servo's touch handler. + sequence_id: Option, +} + +impl TouchEvent { + pub fn new(event_type: TouchEventType, id: TouchId, point: DevicePoint) -> Self { + TouchEvent { + event_type, + id, + point, + sequence_id: None, + } + } + /// Embedders should ignore this. + #[doc(hidden)] + pub fn init_sequence_id(&mut self, sequence_id: TouchSequenceId) { + if self.sequence_id.is_none() { + self.sequence_id = Some(sequence_id); + } else { + // We could allow embedders to set the sequence ID. + error!("Sequence ID already set."); + } + } + + #[doc(hidden)] + pub fn expect_sequence_id(&self) -> TouchSequenceId { + self.sequence_id.expect("Sequence ID not initialized") + } } /// Mode to measure WheelDelta floats in diff --git a/components/shared/script/lib.rs b/components/shared/script/lib.rs index 5c85f473b6e..f022f4eeb35 100644 --- a/components/shared/script/lib.rs +++ b/components/shared/script/lib.rs @@ -62,8 +62,9 @@ use webrender_traits::{ }; pub use crate::script_msg::{ - DOMMessage, EventResult, IFrameSizeMsg, Job, JobError, JobResult, JobResultValue, JobType, - LayoutMsg, LogEntry, SWManagerMsg, SWManagerSenders, ScopeThings, ScriptMsg, ServiceWorkerMsg, + DOMMessage, IFrameSizeMsg, Job, JobError, JobResult, JobResultValue, JobType, LayoutMsg, + LogEntry, SWManagerMsg, SWManagerSenders, ScopeThings, ScriptMsg, ServiceWorkerMsg, + TouchEventResult, }; use crate::serializable::{BlobData, BlobImpl}; use crate::transferable::MessagePortImpl; diff --git a/components/shared/script/script_msg.rs b/components/shared/script/script_msg.rs index f3f3a417898..9321b623cbd 100644 --- a/components/shared/script/script_msg.rs +++ b/components/shared/script/script_msg.rs @@ -14,7 +14,7 @@ use base::Epoch; use canvas_traits::canvas::{CanvasId, CanvasMsg}; use devtools_traits::{ScriptToDevtoolsControlMsg, WorkerId}; use embedder_traits::{ - EmbedderMsg, MediaSessionEvent, TouchAction, TouchEventType, TraversalDirection, + EmbedderMsg, MediaSessionEvent, TouchEventType, TouchSequenceId, TraversalDirection, }; use euclid::default::Size2D as UntypedSize2D; use euclid::Size2D; @@ -62,13 +62,13 @@ impl fmt::Debug for LayoutMsg { } } -/// Whether a DOM event was prevented by web content +/// Whether the default action for a touch event was prevented by web content #[derive(Debug, Deserialize, Serialize)] -pub enum EventResult { +pub enum TouchEventResult { /// Allowed by web content - DefaultAllowed(TouchAction), + DefaultAllowed(TouchSequenceId, TouchEventType), /// Prevented by web content - DefaultPrevented(TouchEventType), + DefaultPrevented(TouchSequenceId, TouchEventType), } /// A log entry reported to the constellation @@ -217,7 +217,7 @@ pub enum ScriptMsg { /// Update the pipeline Url, which can change after redirections. SetFinalUrl(ServoUrl), /// Script has handled a touch event, and either prevented or allowed default actions. - TouchEventProcessed(EventResult), + TouchEventProcessed(TouchEventResult), /// A log entry, with the top-level browsing context id and thread name LogEntry(Option, LogEntry), /// Discard the document. diff --git a/ports/servoshell/desktop/headed_window.rs b/ports/servoshell/desktop/headed_window.rs index c0706570d09..c906dfbdffd 100644 --- a/ports/servoshell/desktop/headed_window.rs +++ b/ports/servoshell/desktop/headed_window.rs @@ -24,8 +24,8 @@ use servo::webrender_api::ScrollLocation; use servo::{ Cursor, ImeEvent, InputEvent, Key, KeyState, KeyboardEvent, MouseButton as ServoMouseButton, MouseButtonAction, MouseButtonEvent, MouseMoveEvent, OffscreenRenderingContext, - RenderingContext, Theme, TouchAction, TouchEvent, TouchEventType, TouchId, WebView, WheelDelta, - WheelEvent, WheelMode, WindowRenderingContext, + RenderingContext, Theme, TouchEvent, TouchEventType, TouchId, WebView, WheelDelta, WheelEvent, + WheelMode, WindowRenderingContext, }; use surfman::{Context, Device}; use url::Url; @@ -600,12 +600,11 @@ impl WindowPortsMethods for Window { ); }, WindowEvent::Touch(touch) => { - webview.notify_input_event(InputEvent::Touch(TouchEvent { - event_type: winit_phase_to_touch_event_type(touch.phase), - id: TouchId(touch.id as i32), - point: Point2D::new(touch.location.x as f32, touch.location.y as f32), - action: TouchAction::NoAction, - })); + webview.notify_input_event(InputEvent::Touch(TouchEvent::new( + winit_phase_to_touch_event_type(touch.phase), + TouchId(touch.id as i32), + Point2D::new(touch.location.x as f32, touch.location.y as f32), + ))); }, WindowEvent::PinchGesture { delta, .. } => { webview.set_pinch_zoom(delta as f32 + 1.0); diff --git a/ports/servoshell/egl/app_state.rs b/ports/servoshell/egl/app_state.rs index 3317b673be2..d231d84114d 100644 --- a/ports/servoshell/egl/app_state.rs +++ b/ports/servoshell/egl/app_state.rs @@ -23,8 +23,8 @@ use servo::{ InputMethodType, Key, KeyState, KeyboardEvent, LoadStatus, MediaSessionActionType, MediaSessionEvent, MouseButton, MouseButtonAction, MouseButtonEvent, MouseMoveEvent, NavigationRequest, PermissionRequest, PromptDefinition, PromptOrigin, PromptResult, - RenderingContext, Servo, ServoDelegate, ServoError, TouchAction, TouchEvent, TouchEventType, - TouchId, WebView, WebViewDelegate, WindowRenderingContext, + RenderingContext, Servo, ServoDelegate, ServoError, TouchEvent, TouchEventType, TouchId, + WebView, WebViewDelegate, WindowRenderingContext, }; use url::Url; @@ -378,12 +378,10 @@ impl RunningAppState { /// everytime wakeup is called or when embedder wants Servo /// to act on its pending events. pub fn perform_updates(&self) { - debug!("perform_updates"); let should_continue = self.servo.spin_event_loop(); if !should_continue { self.callbacks.host_callbacks.on_shutdown_complete(); } - debug!("done perform_updates"); } /// Load an URL. @@ -486,48 +484,44 @@ impl RunningAppState { /// Touch event: press down pub fn touch_down(&self, x: f32, y: f32, pointer_id: i32) { self.active_webview() - .notify_input_event(InputEvent::Touch(TouchEvent { - event_type: TouchEventType::Down, - id: TouchId(pointer_id), - point: Point2D::new(x, y), - action: TouchAction::NoAction, - })); + .notify_input_event(InputEvent::Touch(TouchEvent::new( + TouchEventType::Down, + TouchId(pointer_id), + Point2D::new(x, y), + ))); self.perform_updates(); } /// Touch event: move touching finger pub fn touch_move(&self, x: f32, y: f32, pointer_id: i32) { self.active_webview() - .notify_input_event(InputEvent::Touch(TouchEvent { - event_type: TouchEventType::Move, - id: TouchId(pointer_id), - point: Point2D::new(x, y), - action: TouchAction::NoAction, - })); + .notify_input_event(InputEvent::Touch(TouchEvent::new( + TouchEventType::Move, + TouchId(pointer_id), + Point2D::new(x, y), + ))); self.perform_updates(); } /// Touch event: Lift touching finger pub fn touch_up(&self, x: f32, y: f32, pointer_id: i32) { self.active_webview() - .notify_input_event(InputEvent::Touch(TouchEvent { - event_type: TouchEventType::Up, - id: TouchId(pointer_id), - point: Point2D::new(x, y), - action: TouchAction::NoAction, - })); + .notify_input_event(InputEvent::Touch(TouchEvent::new( + TouchEventType::Up, + TouchId(pointer_id), + Point2D::new(x, y), + ))); self.perform_updates(); } /// Cancel touch event pub fn touch_cancel(&self, x: f32, y: f32, pointer_id: i32) { self.active_webview() - .notify_input_event(InputEvent::Touch(TouchEvent { - event_type: TouchEventType::Cancel, - id: TouchId(pointer_id), - point: Point2D::new(x, y), - action: TouchAction::NoAction, - })); + .notify_input_event(InputEvent::Touch(TouchEvent::new( + TouchEventType::Cancel, + TouchId(pointer_id), + Point2D::new(x, y), + ))); self.perform_updates(); }