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 <schwenderjonathan@gmail.com>

* Fix test-tidy

Signed-off-by: Jonathan Schwender <schwenderjonathan@gmail.com>

* Fix clippy missing-default lint

Signed-off-by: Jonathan Schwender <schwenderjonathan@gmail.com>

* Fix remaining clippy lints

Signed-off-by: Jonathan Schwender <schwenderjonathan@gmail.com>

* Remove accidental committed test file

Signed-off-by: Jonathan Schwender <schwenderjonathan@gmail.com>

* Remove wrong todo comment

(move events that are sent to script are just raw  touchpoints,
 no merging needed)

Signed-off-by: Jonathan Schwender <schwenderjonathan@gmail.com>

* Fix preventdefault after long touch_down handler

Signed-off-by: Jonathan Schwender <schwenderjonathan@gmail.com>

---------

Signed-off-by: kongbai1996 <1782765876@qq.com>
Signed-off-by: Jonathan Schwender <schwenderjonathan@gmail.com>
Co-authored-by: Jonathan Schwender <schwenderjonathan@gmail.com>
This commit is contained in:
Bi Fuguo 2025-02-25 15:13:16 +08:00 committed by GitHub
parent 374bfc6983
commit f3a8bf8ca2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 746 additions and 278 deletions

View file

@ -22,7 +22,7 @@ use compositing_traits::{
use crossbeam_channel::Sender; use crossbeam_channel::Sender;
use embedder_traits::{ use embedder_traits::{
Cursor, InputEvent, MouseButton, MouseButtonAction, MouseButtonEvent, MouseMoveEvent, Cursor, InputEvent, MouseButton, MouseButtonAction, MouseButtonEvent, MouseMoveEvent,
ShutdownState, TouchAction, TouchEvent, TouchEventType, TouchId, ShutdownState, TouchEvent, TouchEventType, TouchId,
}; };
use euclid::{Point2D, Rect, Scale, Size2D, Transform3D, Vector2D}; use euclid::{Point2D, Rect, Scale, Size2D, Transform3D, Vector2D};
use fnv::{FnvHashMap, FnvHashSet}; 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::{self as profile_time, ProfilerCategory};
use profile_traits::time_profile; use profile_traits::time_profile;
use script_traits::{ use script_traits::{
AnimationState, AnimationTickType, EventResult, ScriptThreadMessage, ScrollState, AnimationState, AnimationTickType, ScriptThreadMessage, ScrollState, TouchEventResult,
WindowSizeData, WindowSizeType, WindowSizeData, WindowSizeType,
}; };
use servo_config::opts; use servo_config::opts;
@ -57,7 +57,7 @@ use webrender_traits::{
CompositorHitTestResult, CrossProcessCompositorMessage, ImageUpdate, UntrustedNodeAddress, CompositorHitTestResult, CrossProcessCompositorMessage, ImageUpdate, UntrustedNodeAddress,
}; };
use crate::touch::TouchHandler; use crate::touch::{TouchHandler, TouchMoveAction, TouchMoveAllowed, TouchSequenceState};
use crate::webview::{UnknownWebView, WebView, WebViewAlreadyExists, WebViewManager}; use crate::webview::{UnknownWebView, WebView, WebViewAlreadyExists, WebViewManager};
use crate::windowing::{self, EmbedderCoordinates, WebRenderDebugOption, WindowMethods}; use crate::windowing::{self, EmbedderCoordinates, WebRenderDebugOption, WindowMethods};
use crate::InitialCompositorState; use crate::InitialCompositorState;
@ -1303,28 +1303,25 @@ impl IOCompositor {
InputEvent::MouseButton(event) => { InputEvent::MouseButton(event) => {
match event.action { match event.action {
MouseButtonAction::Click => {}, MouseButtonAction::Click => {},
MouseButtonAction::Down => self.on_touch_down(TouchEvent { MouseButtonAction::Down => self.on_touch_down(TouchEvent::new(
event_type: TouchEventType::Down, TouchEventType::Down,
id: TouchId(0), TouchId(0),
point: event.point, event.point,
action: TouchAction::NoAction, )),
}), MouseButtonAction::Up => self.on_touch_up(TouchEvent::new(
MouseButtonAction::Up => self.on_touch_up(TouchEvent { TouchEventType::Up,
event_type: TouchEventType::Up, TouchId(0),
id: TouchId(0), event.point,
point: event.point, )),
action: TouchAction::NoAction,
}),
} }
return; return;
}, },
InputEvent::MouseMove(event) => { InputEvent::MouseMove(event) => {
self.on_touch_move(TouchEvent { self.on_touch_move(TouchEvent::new(
event_type: TouchEventType::Move, TouchEventType::Move,
id: TouchId(0), TouchId(0),
point: event.point, event.point,
action: TouchAction::NoAction, ));
});
return; return;
}, },
_ => {}, _ => {},
@ -1386,11 +1383,12 @@ impl IOCompositor {
.collect() .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 { 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); let event = InputEvent::Touch(event);
if let Err(e) = self if let Err(e) = self
.global .global
@ -1398,6 +1396,9 @@ impl IOCompositor {
.send(ConstellationMsg::ForwardInputEvent(event, Some(result))) .send(ConstellationMsg::ForwardInputEvent(event, Some(result)))
{ {
warn!("Sending event to constellation failed ({:?}).", e); warn!("Sending event to constellation failed ({:?}).", e);
false
} else {
true
} }
} }
@ -1419,16 +1420,21 @@ impl IOCompositor {
self.send_touch_event(event); self.send_touch_event(event);
} }
fn on_touch_move(&mut self, mut event: TouchEvent) { fn on_touch_move(&mut self, event: TouchEvent) {
let action: TouchAction = self.touch_handler.on_touch_move(event.id, event.point); let action: TouchMoveAction = self.touch_handler.on_touch_move(event.id, event.point);
if TouchAction::NoAction != action { if TouchMoveAction::NoAction != action {
if !self.touch_handler.prevent_move { // 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 { 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())), ScrollLocation::Delta(LayoutVector2D::from_untyped(delta.to_untyped())),
point.cast(), 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. 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 // 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) { fn on_touch_up(&mut self, event: TouchEvent) {
let action = self.touch_handler.on_touch_up(event.id, event.point); self.touch_handler.on_touch_up(event.id, event.point);
event.action = action;
self.send_touch_event(event); self.send_touch_event(event);
} }
fn on_touch_cancel(&mut self, event: TouchEvent) { fn on_touch_cancel(&mut self, event: TouchEvent) {
// Send the event to script. // Send the event to script.
self.touch_handler.on_touch_cancel(event.id, event.point); 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 { 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 { match event_type {
TouchEventType::Down | TouchEventType::Move => { TouchEventType::Down => {
self.touch_handler.prevent_move = true; // 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);
}, },
_ => {}, TouchEventType::Move => {
} // script thread processed the touch move event, mark this false.
self.touch_handler.prevent_click = true; let info = self.touch_handler.get_touch_sequence_mut(sequence_id);
}, info.prevent_move = TouchMoveAllowed::Prevented;
EventResult::DefaultAllowed(action) => { if let TouchSequenceState::PendingFling { .. } = info.state {
self.touch_handler.prevent_move = false; info.state = TouchSequenceState::Finished;
match action { }
TouchAction::Click(point) => { self.touch_handler.prevent_move(sequence_id);
if !self.touch_handler.prevent_click { self.touch_handler
self.simulate_mouse_click(point); .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) => { TouchEventType::Cancel => {
self.touch_handler.on_fling(velocity, point); // 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(), TouchEventResult::DefaultAllowed(sequence_id, event_type) => {
), debug!(
TouchAction::Zoom(magnification, scroll_delta) => { "Touch event {:?} in sequence {:?} allowed",
let cursor = Point2D::new(-1, -1); // Make sure this hits the base layer. event_type, sequence_id
);
// The order of these events doesn't matter, because zoom is handled by match event_type {
// a root display list and the scroll event here is handled by the scroll TouchEventType::Down => {},
// applied to the content display list. TouchEventType::Move => {
self.pending_scroll_zoom_events if let Some(action) =
.push(ScrollZoomEvent::PinchZoom(magnification)); self.touch_handler.pending_touch_move_action(sequence_id)
self.pending_scroll_zoom_events {
.push(ScrollZoomEvent::Scroll(ScrollEvent { match action {
scroll_location: ScrollLocation::Delta( TouchMoveAction::Scroll(delta, point) => self
LayoutVector2D::from_untyped(scroll_delta.to_untyped()), .on_scroll_window_event(
), ScrollLocation::Delta(LayoutVector2D::from_untyped(
cursor, delta.to_untyped(),
event_count: 1, )),
})); 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);
}, },
_ => {},
} }
}, },
} }

View file

@ -2,12 +2,14 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this * 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/. */ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use embedder_traits::{TouchAction, TouchId}; use std::collections::HashMap;
use euclid::{Point2D, Scale, Vector2D};
use log::{debug, warn};
use webrender_api::units::{DeviceIntPoint, DevicePixel, LayoutVector2D};
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` // TODO: All `_SCREEN_PX` units below are currently actually used as `DevicePixel`
// without multiplying with the `hidpi_factor`. This should be fixed and the // 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; const FLING_MAX_SCREEN_PX: f32 = 4000.0;
pub struct TouchHandler { pub struct TouchHandler {
pub state: TouchState, pub current_sequence_id: TouchSequenceId,
pub active_touch_points: Vec<TouchPoint>, // todo: VecDeque + modulo arithmetic would be more efficient.
pub prevent_move: bool, pub touch_sequence_map: HashMap<TouchSequenceId, TouchSequenceInfo>,
}
/// 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<TouchPoint>,
/// 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, 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<TouchMoveAction>,
}
impl TouchSequenceInfo {
fn touch_count(&self) -> usize {
self.active_touch_points.len()
}
fn pinch_distance_and_center(&self) -> (f32, Point2D<f32, DevicePixel>) {
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<f32, DevicePixel>, DevicePoint),
/// Zoom by a magnification factor and scroll by the provided offset.
Zoom(f32, Vector2D<f32, DevicePixel>),
/// Don't do anything.
NoAction,
} }
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy, Debug)]
@ -42,25 +158,34 @@ impl TouchPoint {
/// The states of the touch input state machine. /// The states of the touch input state machine.
#[derive(Clone, Copy, Debug, PartialEq)] #[derive(Clone, Copy, Debug, PartialEq)]
pub enum TouchState { pub(crate) enum TouchSequenceState {
/// Not tracking any touch point /// touch point is active but does not start moving
Nothing,
/// A single touch point is active and may perform click or pan default actions.
/// Contains the initial touch location.
Touching, Touching,
/// A single touch point is active and has started panning. /// A single touch point is active and has started panning.
Panning { Panning {
velocity: Vector2D<f32, DevicePixel>, velocity: Vector2D<f32, DevicePixel>,
}, },
/// 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<f32, DevicePixel>,
cursor: DeviceIntPoint,
},
/// No active touch points, but there is still scrolling velocity /// No active touch points, but there is still scrolling velocity
Flinging { Flinging {
velocity: Vector2D<f32, DevicePixel>, velocity: Vector2D<f32, DevicePixel>,
cursor: DeviceIntPoint, cursor: DeviceIntPoint,
}, },
/// A two-finger pinch zoom gesture is active. /// The touch sequence is finished, but a click is still pending, waiting on script.
Pinching, PendingClick(DevicePoint),
/// A multi-touch gesture is in progress. Contains the number of active touch points. /// touch sequence finished.
MultiTouch, Finished,
} }
pub(crate) struct FlingAction { pub(crate) struct FlingAction {
@ -70,180 +195,356 @@ pub(crate) struct FlingAction {
impl TouchHandler { impl TouchHandler {
pub fn new() -> Self { pub fn new() -> Self {
TouchHandler { let finished_info = TouchSequenceInfo {
state: Nothing, state: TouchSequenceState::Finished,
active_touch_points: Vec::new(), active_touch_points: vec![],
prevent_move: true, handling_touch_move: false,
prevent_click: 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<TouchMoveAction> {
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<f32, DevicePixel>) { pub fn on_touch_down(&mut self, id: TouchId, point: Point2D<f32, DevicePixel>) {
let point = TouchPoint::new(id, point); // if the current sequence ID does not exist in the map, then it was already handled
self.active_touch_points.push(point); if !self
self.state = Touching; .touch_sequence_map
self.prevent_click = match self.touch_count() { .contains_key(&self.current_sequence_id) ||
1 => { self.get_touch_sequence(self.current_sequence_id)
self.prevent_move = true; .is_finished()
false {
}, self.current_sequence_id.next();
_ => true, 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<FlingAction> { pub fn on_vsync(&mut self) -> Option<FlingAction> {
let touch_sequence = self.touch_sequence_map.get_mut(&self.current_sequence_id)?;
let Flinging { let Flinging {
velocity, velocity,
ref cursor, ref cursor,
} = &mut self.state } = &mut touch_sequence.state
else { else {
return None; return None;
}; };
if velocity.length().abs() < FLING_MIN_SCREEN_PX { if velocity.length().abs() < FLING_MIN_SCREEN_PX {
self.state = Nothing; touch_sequence.state = Finished;
return None; // 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<f32, DevicePixel>) -> TouchAction { pub fn on_touch_move(
let idx = match self.active_touch_points.iter_mut().position(|t| t.id == id) { &mut self,
id: TouchId,
point: Point2D<f32, DevicePixel>,
) -> 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, Some(i) => i,
None => { None => {
warn!("Got a touchmove event for a non-active touch point"); unreachable!("Got a touchmove event for a non-active touch point");
return TouchAction::NoAction;
}, },
}; };
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 delta = point - old_point;
let action = match self.touch_count() { let action = match touch_sequence.touch_count() {
1 => { 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 // TODO: Probably we should track 1-3 more points and use a smarter algorithm
*velocity += delta; *velocity += delta;
*velocity /= 2.0; *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 || } else if delta.x.abs() > TOUCH_PAN_MIN_SCREEN_PX ||
delta.y.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), velocity: Vector2D::new(delta.x, delta.y),
}; };
self.prevent_click = true; // No clicks should be issued after we transitioned to move.
TouchAction::Scroll(delta, point) touch_sequence.prevent_click = true;
// update the touch point
touch_sequence.active_touch_points[idx].point = point;
TouchMoveAction::Scroll(delta, point)
} else { } else {
TouchAction::NoAction // We don't update the touchpoint, so multiple small moves can
// accumulate and merge into a larger move.
TouchMoveAction::NoAction
} }
}, },
2 => { 2 => {
if self.state == Pinching || if touch_sequence.state == Pinching ||
delta.x.abs() > TOUCH_PAN_MIN_SCREEN_PX || delta.x.abs() > TOUCH_PAN_MIN_SCREEN_PX ||
delta.y.abs() > TOUCH_PAN_MIN_SCREEN_PX delta.y.abs() > TOUCH_PAN_MIN_SCREEN_PX
{ {
self.state = Pinching; touch_sequence.state = Pinching;
let (d0, c0) = self.pinch_distance_and_center(); let (d0, c0) = touch_sequence.pinch_distance_and_center();
self.active_touch_points[idx].point = point; // update the touch point with the enough distance or pinching.
let (d1, c1) = self.pinch_distance_and_center(); touch_sequence.active_touch_points[idx].point = point;
let (d1, c1) = touch_sequence.pinch_distance_and_center();
let magnification = d1 / d0; let magnification = d1 / d0;
let scroll_delta = c1 - c0 * Scale::new(magnification); let scroll_delta = c1 - c0 * Scale::new(magnification);
TouchAction::Zoom(magnification, scroll_delta) TouchMoveAction::Zoom(magnification, scroll_delta)
} else { } 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; touch_sequence.active_touch_points[idx].point = point;
TouchAction::NoAction touch_sequence.state = MultiTouch;
TouchMoveAction::NoAction
}, },
}; };
// If the touch action is not `NoAction` and the first move has not been processed,
// update the touch point with the latest location. // set pending_touch_move_action.
if self.state != Touching { if TouchMoveAction::NoAction != action &&
self.active_touch_points[idx].point = point; touch_sequence.prevent_move == TouchMoveAllowed::Pending
{
touch_sequence.update_pending_touch_move_action(action);
} }
action action
} }
pub fn on_touch_up(&mut self, id: TouchId, point: Point2D<f32, DevicePixel>) -> TouchAction { pub fn on_touch_up(&mut self, id: TouchId, point: Point2D<f32, DevicePixel>) {
let old = match self.active_touch_points.iter().position(|t| t.id == id) { let touch_sequence = self.get_touch_sequence_mut(self.current_sequence_id);
Some(i) => Some(self.active_touch_points.swap_remove(i).point), 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 => { None => {
warn!("Got a touch up event for a non-active touch point"); warn!("Got a touch up event for a non-active touch point");
None None
}, },
}; };
match self.touch_count() { match touch_sequence.state {
0 => { Touching => {
if let Panning { velocity } = self.state { if touch_sequence.prevent_click {
if velocity.length().abs() >= FLING_MIN_SCREEN_PX { touch_sequence.state = Finished;
// 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
}
} else { } else {
self.state = Nothing; touch_sequence.state = PendingClick(point);
if !self.prevent_click {
TouchAction::Click(point)
} else {
TouchAction::NoAction
}
} }
}, },
_ => { Panning { velocity } => {
self.state = Touching; if velocity.length().abs() >= FLING_MIN_SCREEN_PX {
TouchAction::NoAction // 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<f32, DevicePixel>) { pub fn on_touch_cancel(&mut self, id: TouchId, _point: Point2D<f32, DevicePixel>) {
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) => { Some(i) => {
self.active_touch_points.swap_remove(i); touch_sequence.active_touch_points.swap_remove(i);
}, },
None => { None => {
warn!("Got a touchcancel event for a non-active touch point"); warn!("Got a touchcancel event for a non-active touch point");
return; return;
}, },
} }
self.state = Nothing; touch_sequence.state = Finished;
}
pub fn on_fling(&mut self, velocity: Vector2D<f32, DevicePixel>, 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<f32, DevicePixel>) {
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)
} }
} }

View file

@ -80,11 +80,10 @@ use script_layout_interface::{
node_id_from_scroll_id, LayoutConfig, LayoutFactory, ReflowGoal, ScriptThreadFactory, node_id_from_scroll_id, LayoutConfig, LayoutFactory, ReflowGoal, ScriptThreadFactory,
}; };
use script_traits::{ use script_traits::{
ConstellationInputEvent, DiscardBrowsingContext, DocumentActivity, EventResult, ConstellationInputEvent, DiscardBrowsingContext, DocumentActivity, InitialScriptState,
InitialScriptState, JsEvalResult, LoadData, LoadOrigin, NavigationHistoryBehavior, JsEvalResult, LoadData, LoadOrigin, NavigationHistoryBehavior, NewLayoutInfo, Painter,
NewLayoutInfo, Painter, ProgressiveWebMetricType, ScriptMsg, ScriptThreadMessage, ProgressiveWebMetricType, ScriptMsg, ScriptThreadMessage, ScriptToConstellationChan,
ScriptToConstellationChan, ScrollState, StructuredSerializedData, UpdatePipelineIdReason, ScrollState, StructuredSerializedData, UpdatePipelineIdReason, WindowSizeData, WindowSizeType,
WindowSizeData, WindowSizeType,
}; };
use servo_atoms::Atom; use servo_atoms::Atom;
use servo_config::opts; use servo_config::opts;
@ -1096,22 +1095,24 @@ impl ScriptThread {
InputEvent::Touch(touch_event) => { InputEvent::Touch(touch_event) => {
let touch_result = let touch_result =
document.handle_touch_event(touch_event, event.hit_test_result, can_gc); document.handle_touch_event(touch_event, event.hit_test_result, can_gc);
match touch_result { if let TouchEventResult::Processed(handled) = touch_result {
TouchEventResult::Processed(handled) => { let sequence_id = touch_event.expect_sequence_id();
let result = if handled { let result = if handled {
EventResult::DefaultAllowed(touch_event.action) script_traits::TouchEventResult::DefaultAllowed(
} else { sequence_id,
EventResult::DefaultPrevented(touch_event.event_type) touch_event.event_type,
}; )
let message = ScriptMsg::TouchEventProcessed(result); } else {
self.senders script_traits::TouchEventResult::DefaultPrevented(
.pipeline_to_constellation_sender sequence_id,
.send((pipeline_id, message)) touch_event.event_type,
.unwrap(); )
}, };
_ => { let message = ScriptMsg::TouchEventProcessed(result);
// TODO: Calling preventDefault on a touchup event should prevent clicks. self.senders
}, .pipeline_to_constellation_sender
.send((pipeline_id, message))
.unwrap();
} }
}, },
InputEvent::Wheel(wheel_event) => { InputEvent::Wheel(wheel_event) => {

View file

@ -17,7 +17,7 @@ use euclid::Rect;
use ipc_channel::ipc::IpcSender; use ipc_channel::ipc::IpcSender;
use log::warn; use log::warn;
use pixels::Image; use pixels::Image;
use script_traits::{AnimationState, EventResult, ScriptThreadMessage}; use script_traits::{AnimationState, ScriptThreadMessage, TouchEventResult};
use style_traits::CSSPixel; use style_traits::CSSPixel;
use webrender_api::DocumentId; use webrender_api::DocumentId;
use webrender_traits::{CrossProcessCompositorApi, CrossProcessCompositorMessage}; use webrender_traits::{CrossProcessCompositorApi, CrossProcessCompositorMessage};
@ -65,7 +65,7 @@ pub enum CompositorMsg {
/// Remove a webview. /// Remove a webview.
RemoveWebView(TopLevelBrowsingContextId), RemoveWebView(TopLevelBrowsingContextId),
/// Script has handled a touch event, and either prevented or allowed default actions. /// 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. /// Composite to a PNG file and return the Image over a passed channel.
CreatePng(Option<Rect<f32, CSSPixel>>, IpcSender<Option<Image>>), CreatePng(Option<Rect<f32, CSSPixel>>, IpcSender<Option<Image>>),
/// A reply to the compositor asking if the output image is stable. /// A reply to the compositor asking if the output image is stable.

View file

@ -2,11 +2,11 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this * 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/. */ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use euclid::Vector2D;
use keyboard_types::{CompositionEvent, KeyboardEvent}; use keyboard_types::{CompositionEvent, KeyboardEvent};
use log::error;
use malloc_size_of_derive::MallocSizeOf; use malloc_size_of_derive::MallocSizeOf;
use serde::{Deserialize, Serialize}; 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. /// An input event that is sent from the embedder to Servo.
#[derive(Clone, Debug, Deserialize, Serialize)] #[derive(Clone, Debug, Deserialize, Serialize)]
@ -116,33 +116,65 @@ pub enum TouchEventType {
Cancel, 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<f32, DevicePixel>, DeviceIntPoint),
/// Scroll by the provided offset.
Scroll(Vector2D<f32, DevicePixel>, DevicePoint),
/// Zoom by a magnification factor and scroll by the provided offset.
Zoom(f32, Vector2D<f32, DevicePixel>),
/// Don't do anything.
NoAction,
}
/// An opaque identifier for a touch point. /// An opaque identifier for a touch point.
/// ///
/// <http://w3c.github.io/touch-events/#widl-Touch-identifier> /// <http://w3c.github.io/touch-events/#widl-Touch-identifier>
#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)] #[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct TouchId(pub i32); 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)] #[derive(Clone, Copy, Debug, Deserialize, Serialize)]
pub struct TouchEvent { pub struct TouchEvent {
pub event_type: TouchEventType, pub event_type: TouchEventType,
pub id: TouchId, pub id: TouchId,
pub point: DevicePoint, pub point: DevicePoint,
pub action: TouchAction, /// The sequence_id will be set by servo's touch handler.
sequence_id: Option<TouchSequenceId>,
}
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 /// Mode to measure WheelDelta floats in

View file

@ -62,8 +62,9 @@ use webrender_traits::{
}; };
pub use crate::script_msg::{ pub use crate::script_msg::{
DOMMessage, EventResult, IFrameSizeMsg, Job, JobError, JobResult, JobResultValue, JobType, DOMMessage, IFrameSizeMsg, Job, JobError, JobResult, JobResultValue, JobType, LayoutMsg,
LayoutMsg, LogEntry, SWManagerMsg, SWManagerSenders, ScopeThings, ScriptMsg, ServiceWorkerMsg, LogEntry, SWManagerMsg, SWManagerSenders, ScopeThings, ScriptMsg, ServiceWorkerMsg,
TouchEventResult,
}; };
use crate::serializable::{BlobData, BlobImpl}; use crate::serializable::{BlobData, BlobImpl};
use crate::transferable::MessagePortImpl; use crate::transferable::MessagePortImpl;

View file

@ -14,7 +14,7 @@ use base::Epoch;
use canvas_traits::canvas::{CanvasId, CanvasMsg}; use canvas_traits::canvas::{CanvasId, CanvasMsg};
use devtools_traits::{ScriptToDevtoolsControlMsg, WorkerId}; use devtools_traits::{ScriptToDevtoolsControlMsg, WorkerId};
use embedder_traits::{ use embedder_traits::{
EmbedderMsg, MediaSessionEvent, TouchAction, TouchEventType, TraversalDirection, EmbedderMsg, MediaSessionEvent, TouchEventType, TouchSequenceId, TraversalDirection,
}; };
use euclid::default::Size2D as UntypedSize2D; use euclid::default::Size2D as UntypedSize2D;
use euclid::Size2D; 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)] #[derive(Debug, Deserialize, Serialize)]
pub enum EventResult { pub enum TouchEventResult {
/// Allowed by web content /// Allowed by web content
DefaultAllowed(TouchAction), DefaultAllowed(TouchSequenceId, TouchEventType),
/// Prevented by web content /// Prevented by web content
DefaultPrevented(TouchEventType), DefaultPrevented(TouchSequenceId, TouchEventType),
} }
/// A log entry reported to the constellation /// A log entry reported to the constellation
@ -217,7 +217,7 @@ pub enum ScriptMsg {
/// Update the pipeline Url, which can change after redirections. /// Update the pipeline Url, which can change after redirections.
SetFinalUrl(ServoUrl), SetFinalUrl(ServoUrl),
/// Script has handled a touch event, and either prevented or allowed default actions. /// 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 /// A log entry, with the top-level browsing context id and thread name
LogEntry(Option<String>, LogEntry), LogEntry(Option<String>, LogEntry),
/// Discard the document. /// Discard the document.

View file

@ -24,8 +24,8 @@ use servo::webrender_api::ScrollLocation;
use servo::{ use servo::{
Cursor, ImeEvent, InputEvent, Key, KeyState, KeyboardEvent, MouseButton as ServoMouseButton, Cursor, ImeEvent, InputEvent, Key, KeyState, KeyboardEvent, MouseButton as ServoMouseButton,
MouseButtonAction, MouseButtonEvent, MouseMoveEvent, OffscreenRenderingContext, MouseButtonAction, MouseButtonEvent, MouseMoveEvent, OffscreenRenderingContext,
RenderingContext, Theme, TouchAction, TouchEvent, TouchEventType, TouchId, WebView, WheelDelta, RenderingContext, Theme, TouchEvent, TouchEventType, TouchId, WebView, WheelDelta, WheelEvent,
WheelEvent, WheelMode, WindowRenderingContext, WheelMode, WindowRenderingContext,
}; };
use surfman::{Context, Device}; use surfman::{Context, Device};
use url::Url; use url::Url;
@ -600,12 +600,11 @@ impl WindowPortsMethods for Window {
); );
}, },
WindowEvent::Touch(touch) => { WindowEvent::Touch(touch) => {
webview.notify_input_event(InputEvent::Touch(TouchEvent { webview.notify_input_event(InputEvent::Touch(TouchEvent::new(
event_type: winit_phase_to_touch_event_type(touch.phase), winit_phase_to_touch_event_type(touch.phase),
id: TouchId(touch.id as i32), TouchId(touch.id as i32),
point: Point2D::new(touch.location.x as f32, touch.location.y as f32), Point2D::new(touch.location.x as f32, touch.location.y as f32),
action: TouchAction::NoAction, )));
}));
}, },
WindowEvent::PinchGesture { delta, .. } => { WindowEvent::PinchGesture { delta, .. } => {
webview.set_pinch_zoom(delta as f32 + 1.0); webview.set_pinch_zoom(delta as f32 + 1.0);

View file

@ -23,8 +23,8 @@ use servo::{
InputMethodType, Key, KeyState, KeyboardEvent, LoadStatus, MediaSessionActionType, InputMethodType, Key, KeyState, KeyboardEvent, LoadStatus, MediaSessionActionType,
MediaSessionEvent, MouseButton, MouseButtonAction, MouseButtonEvent, MouseMoveEvent, MediaSessionEvent, MouseButton, MouseButtonAction, MouseButtonEvent, MouseMoveEvent,
NavigationRequest, PermissionRequest, PromptDefinition, PromptOrigin, PromptResult, NavigationRequest, PermissionRequest, PromptDefinition, PromptOrigin, PromptResult,
RenderingContext, Servo, ServoDelegate, ServoError, TouchAction, TouchEvent, TouchEventType, RenderingContext, Servo, ServoDelegate, ServoError, TouchEvent, TouchEventType, TouchId,
TouchId, WebView, WebViewDelegate, WindowRenderingContext, WebView, WebViewDelegate, WindowRenderingContext,
}; };
use url::Url; use url::Url;
@ -378,12 +378,10 @@ impl RunningAppState {
/// everytime wakeup is called or when embedder wants Servo /// everytime wakeup is called or when embedder wants Servo
/// to act on its pending events. /// to act on its pending events.
pub fn perform_updates(&self) { pub fn perform_updates(&self) {
debug!("perform_updates");
let should_continue = self.servo.spin_event_loop(); let should_continue = self.servo.spin_event_loop();
if !should_continue { if !should_continue {
self.callbacks.host_callbacks.on_shutdown_complete(); self.callbacks.host_callbacks.on_shutdown_complete();
} }
debug!("done perform_updates");
} }
/// Load an URL. /// Load an URL.
@ -486,48 +484,44 @@ impl RunningAppState {
/// Touch event: press down /// Touch event: press down
pub fn touch_down(&self, x: f32, y: f32, pointer_id: i32) { pub fn touch_down(&self, x: f32, y: f32, pointer_id: i32) {
self.active_webview() self.active_webview()
.notify_input_event(InputEvent::Touch(TouchEvent { .notify_input_event(InputEvent::Touch(TouchEvent::new(
event_type: TouchEventType::Down, TouchEventType::Down,
id: TouchId(pointer_id), TouchId(pointer_id),
point: Point2D::new(x, y), Point2D::new(x, y),
action: TouchAction::NoAction, )));
}));
self.perform_updates(); self.perform_updates();
} }
/// Touch event: move touching finger /// Touch event: move touching finger
pub fn touch_move(&self, x: f32, y: f32, pointer_id: i32) { pub fn touch_move(&self, x: f32, y: f32, pointer_id: i32) {
self.active_webview() self.active_webview()
.notify_input_event(InputEvent::Touch(TouchEvent { .notify_input_event(InputEvent::Touch(TouchEvent::new(
event_type: TouchEventType::Move, TouchEventType::Move,
id: TouchId(pointer_id), TouchId(pointer_id),
point: Point2D::new(x, y), Point2D::new(x, y),
action: TouchAction::NoAction, )));
}));
self.perform_updates(); self.perform_updates();
} }
/// Touch event: Lift touching finger /// Touch event: Lift touching finger
pub fn touch_up(&self, x: f32, y: f32, pointer_id: i32) { pub fn touch_up(&self, x: f32, y: f32, pointer_id: i32) {
self.active_webview() self.active_webview()
.notify_input_event(InputEvent::Touch(TouchEvent { .notify_input_event(InputEvent::Touch(TouchEvent::new(
event_type: TouchEventType::Up, TouchEventType::Up,
id: TouchId(pointer_id), TouchId(pointer_id),
point: Point2D::new(x, y), Point2D::new(x, y),
action: TouchAction::NoAction, )));
}));
self.perform_updates(); self.perform_updates();
} }
/// Cancel touch event /// Cancel touch event
pub fn touch_cancel(&self, x: f32, y: f32, pointer_id: i32) { pub fn touch_cancel(&self, x: f32, y: f32, pointer_id: i32) {
self.active_webview() self.active_webview()
.notify_input_event(InputEvent::Touch(TouchEvent { .notify_input_event(InputEvent::Touch(TouchEvent::new(
event_type: TouchEventType::Cancel, TouchEventType::Cancel,
id: TouchId(pointer_id), TouchId(pointer_id),
point: Point2D::new(x, y), Point2D::new(x, y),
action: TouchAction::NoAction, )));
}));
self.perform_updates(); self.perform_updates();
} }