1use std::cell::{Cell, RefCell};
6use std::rc::Rc;
7
8use embedder_traits::{InputEventId, PaintHitTestResult, Scroll, TouchEventType, TouchId};
9use euclid::{Point2D, Scale, Vector2D};
10use log::{debug, error, warn};
11use paint_api::display_list::{ScrollType, TouchAction};
12use rustc_hash::{FxHashMap, FxHashSet};
13use servo_base::id::WebViewId;
14use style_traits::CSSPixel;
15use webrender_api::units::{DevicePixel, DevicePoint, DeviceVector2D};
16
17use self::TouchSequenceState::*;
18use crate::paint::RepaintReason;
19use crate::painter::Painter;
20use crate::refresh_driver::{BaseRefreshDriver, RefreshDriverObserver};
21use crate::webview_renderer::{ScrollEvent, ScrollZoomEvent, WebViewRenderer};
22
23#[repr(transparent)]
26#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)]
27pub(crate) struct TouchSequenceId(u32);
28
29impl TouchSequenceId {
30 const fn new() -> Self {
31 Self(0)
32 }
33
34 fn next(&mut self) {
40 self.0 = self.0.wrapping_add(1);
41 }
42}
43
44const TOUCH_PAN_MIN_SCREEN_PX: f32 = 10.0;
46const FLING_SCALING_FACTOR: f32 = 0.95;
48const FLING_MIN_SCREEN_PX: f32 = 3.0;
50const FLING_MAX_SCREEN_PX: f32 = 4000.0;
52
53pub struct TouchHandler {
54 webview_id: WebViewId,
56 pub current_sequence_id: TouchSequenceId,
57 touch_sequence_map: FxHashMap<TouchSequenceId, TouchSequenceInfo>,
59 pub(crate) pending_touch_input_events: RefCell<FxHashMap<InputEventId, PendingTouchInputEvent>>,
62 observing_frames_for_fling: Cell<bool>,
64}
65
66#[derive(Debug, Eq, PartialEq)]
68pub enum TouchMoveAllowed {
69 Prevented,
71 Allowed,
73 Pending,
75}
76
77pub(crate) enum TouchIdMoveTracking {
78 Track,
79 Remove,
80}
81
82#[derive(Clone, Copy, Debug, Eq, PartialEq)]
87pub(crate) enum PanAxis {
88 Horizontal,
89 Vertical,
90}
91
92#[derive(Clone, Copy, Debug, PartialEq)]
95pub(crate) enum PanPolicy {
96 Undetermined,
100 NoScroll,
103 Lock(PanAxis),
107 Free,
111}
112
113impl PanPolicy {
114 fn pan_delta(self, delta: Vector2D<f32, DevicePixel>) -> Vector2D<f32, DevicePixel> {
117 match self {
118 PanPolicy::Lock(axis) => match axis {
119 PanAxis::Horizontal => Vector2D::new(delta.x, 0.0),
120 PanAxis::Vertical => Vector2D::new(0.0, delta.y),
121 },
122 PanPolicy::Free | PanPolicy::Undetermined | PanPolicy::NoScroll => delta,
123 }
124 }
125}
126
127#[derive(Clone, Copy, Debug)]
130pub(crate) struct PanPolicyInput {
131 pub touch_action: TouchAction,
132 pub scrollable_x: bool,
133 pub scrollable_y: bool,
134}
135
136impl PanPolicyInput {
137 fn to_pan_policy(self, dominant: PanAxis) -> PanPolicy {
140 match self.touch_action {
141 TouchAction::None => PanPolicy::NoScroll,
142 TouchAction::PanX | TouchAction::PanY => PanPolicy::Lock(dominant),
143 TouchAction::Auto => {
144 if self.scrollable_x && self.scrollable_y {
145 PanPolicy::Free
146 } else {
147 PanPolicy::Lock(dominant)
148 }
149 },
150 }
151 }
152}
153
154struct HitTestResultCache {
158 value: PaintHitTestResult,
159 device_pixels_per_page: Scale<f32, CSSPixel, DevicePixel>,
160}
161
162pub struct TouchSequenceInfo {
163 pub(crate) state: TouchSequenceState,
165 active_touch_points: Vec<TouchPoint>,
167 touch_ids_in_move: FxHashSet<TouchId>,
172 pub prevent_click: bool,
179 pub prevent_move: TouchMoveAllowed,
183 pending_touch_move_actions: Vec<ScrollZoomEvent>,
191 hit_test_result_cache: Option<HitTestResultCache>,
193 pub(crate) pan_policy_input: Option<PanPolicyInput>,
197}
198
199impl TouchSequenceInfo {
200 fn touch_count(&self) -> usize {
201 self.active_touch_points.len()
202 }
203
204 fn pinch_distance_and_center(&self) -> (f32, Point2D<f32, DevicePixel>) {
205 debug_assert_eq!(self.touch_count(), 2);
206 let p0 = self.active_touch_points[0].point;
207 let p1 = self.active_touch_points[1].point;
208 let center = p0.lerp(p1, 0.5);
209 let distance = (p0 - p1).length();
210
211 (distance, center)
212 }
213
214 fn add_pending_touch_move_action(&mut self, action: ScrollZoomEvent) {
215 debug_assert!(self.prevent_move == TouchMoveAllowed::Pending);
216 self.pending_touch_move_actions.push(action);
217 }
218
219 fn is_finished(&self) -> bool {
222 matches!(
223 self.state,
224 Finished | Flinging { .. } | PendingFling { .. } | PendingClick
225 )
226 }
227
228 fn update_hit_test_result_cache_pointer(&mut self, delta: Vector2D<f32, DevicePixel>) {
229 if let Some(ref mut hit_test_result_cache) = self.hit_test_result_cache {
230 let scaled_delta = delta / hit_test_result_cache.device_pixels_per_page;
231 hit_test_result_cache.value.point_in_viewport += scaled_delta;
233 }
234 }
235}
236
237#[derive(Clone, Copy, Debug, PartialEq)]
240
241pub struct TouchPoint {
242 pub touch_id: TouchId,
243 pub point: Point2D<f32, DevicePixel>,
244}
245
246impl TouchPoint {
247 fn new(touch_id: TouchId, point: Point2D<f32, DevicePixel>) -> Self {
248 TouchPoint { touch_id, point }
249 }
250}
251
252#[derive(Clone, Copy, Debug, PartialEq)]
254pub(crate) enum TouchSequenceState {
255 Touching,
257 Panning {
259 policy: PanPolicy,
262 velocity: Vector2D<f32, DevicePixel>,
263 },
264 Pinching,
266 MultiTouch,
268 PendingFling {
273 velocity: Vector2D<f32, DevicePixel>,
274 point: DevicePoint,
275 },
276 Flinging {
278 velocity: Vector2D<f32, DevicePixel>,
279 point: DevicePoint,
280 },
281 PendingClick,
283 Finished,
285}
286
287pub(crate) struct FlingAction {
288 pub delta: DeviceVector2D,
289 pub cursor: DevicePoint,
290}
291
292impl TouchHandler {
293 pub(crate) fn new(webview_id: WebViewId) -> Self {
294 let finished_info = TouchSequenceInfo {
295 state: TouchSequenceState::Finished,
296 active_touch_points: vec![],
297 touch_ids_in_move: FxHashSet::default(),
298 prevent_click: false,
299 prevent_move: TouchMoveAllowed::Pending,
300 pending_touch_move_actions: vec![],
301 hit_test_result_cache: None,
302 pan_policy_input: None,
303 };
304 let mut touch_sequence_map = FxHashMap::default();
308 touch_sequence_map.insert(TouchSequenceId::new(), finished_info);
309 TouchHandler {
310 webview_id,
311 current_sequence_id: TouchSequenceId::new(),
312 touch_sequence_map,
313 pending_touch_input_events: Default::default(),
314 observing_frames_for_fling: Default::default(),
315 }
316 }
317
318 pub(crate) fn set_handling_touch_move_for_touch_id(
319 &mut self,
320 sequence_id: TouchSequenceId,
321 touch_id: TouchId,
322 flag: TouchIdMoveTracking,
323 ) {
324 if let Some(sequence) = self.touch_sequence_map.get_mut(&sequence_id) {
325 match flag {
326 TouchIdMoveTracking::Track => {
327 sequence.touch_ids_in_move.insert(touch_id);
328 },
329 TouchIdMoveTracking::Remove => {
330 sequence.touch_ids_in_move.remove(&touch_id);
331 },
332 }
333 }
334 }
335
336 pub(crate) fn is_handling_touch_move_for_touch_id(
337 &self,
338 sequence_id: TouchSequenceId,
339 touch_id: TouchId,
340 ) -> bool {
341 self.touch_sequence_map
342 .get(&sequence_id)
343 .is_some_and(|seq| seq.touch_ids_in_move.contains(&touch_id))
344 }
345
346 pub(crate) fn prevent_click(&mut self, sequence_id: TouchSequenceId) {
347 if let Some(sequence) = self.touch_sequence_map.get_mut(&sequence_id) {
348 sequence.prevent_click = true;
349 } else {
350 warn!("TouchSequenceInfo corresponding to the sequence number has been deleted.");
351 }
352 }
353
354 pub(crate) fn prevent_move(&mut self, sequence_id: TouchSequenceId) {
355 if let Some(sequence) = self.touch_sequence_map.get_mut(&sequence_id) {
356 sequence.prevent_move = TouchMoveAllowed::Prevented;
357 } else {
358 warn!("TouchSequenceInfo corresponding to the sequence number has been deleted.");
359 }
360 }
361
362 pub(crate) fn move_allowed(&self, sequence_id: TouchSequenceId) -> bool {
365 self.touch_sequence_map
366 .get(&sequence_id)
367 .is_none_or(|sequence| sequence.prevent_move == TouchMoveAllowed::Allowed)
368 }
369
370 pub(crate) fn take_pending_touch_move_actions(
371 &mut self,
372 sequence_id: TouchSequenceId,
373 ) -> Vec<ScrollZoomEvent> {
374 self.touch_sequence_map
375 .get_mut(&sequence_id)
376 .map(|sequence| std::mem::take(&mut sequence.pending_touch_move_actions))
377 .unwrap_or_default()
378 }
379
380 pub(crate) fn remove_pending_touch_move_actions(&mut self, sequence_id: TouchSequenceId) {
381 if let Some(sequence) = self.touch_sequence_map.get_mut(&sequence_id) {
382 sequence.pending_touch_move_actions.clear();
383 }
384 }
385
386 pub(crate) fn try_remove_touch_sequence(&mut self, sequence_id: TouchSequenceId) {
388 if let Some(sequence) = self.touch_sequence_map.get(&sequence_id) &&
389 sequence.pending_touch_move_actions.is_empty() &&
390 sequence.state == Finished
391 {
392 self.touch_sequence_map.remove(&sequence_id);
393 }
394 }
395
396 pub(crate) fn remove_touch_sequence(&mut self, sequence_id: TouchSequenceId) {
397 let old = self.touch_sequence_map.remove(&sequence_id);
398 debug_assert!(old.is_some(), "Sequence already removed?");
399 }
400
401 fn get_current_touch_sequence_mut(&mut self) -> &mut TouchSequenceInfo {
402 self.touch_sequence_map
403 .get_mut(&self.current_sequence_id)
404 .expect("Current Touch sequence does not exist")
405 }
406
407 fn try_get_current_touch_sequence(&self) -> Option<&TouchSequenceInfo> {
408 self.touch_sequence_map.get(&self.current_sequence_id)
409 }
410
411 fn try_get_current_touch_sequence_mut(&mut self) -> Option<&mut TouchSequenceInfo> {
412 self.touch_sequence_map.get_mut(&self.current_sequence_id)
413 }
414
415 fn get_touch_sequence(&self, sequence_id: TouchSequenceId) -> &TouchSequenceInfo {
416 self.touch_sequence_map
417 .get(&sequence_id)
418 .expect("Touch sequence not found.")
419 }
420
421 pub(crate) fn get_touch_sequence_mut(
422 &mut self,
423 sequence_id: TouchSequenceId,
424 ) -> Option<&mut TouchSequenceInfo> {
425 self.touch_sequence_map.get_mut(&sequence_id)
426 }
427
428 pub(crate) fn on_touch_down(&mut self, touch_id: TouchId, point: Point2D<f32, DevicePixel>) {
429 if !self
431 .touch_sequence_map
432 .contains_key(&self.current_sequence_id) ||
433 self.get_touch_sequence(self.current_sequence_id)
434 .is_finished()
435 {
436 self.current_sequence_id.next();
437 debug!("Entered new touch sequence: {:?}", self.current_sequence_id);
438 let active_touch_points = vec![TouchPoint::new(touch_id, point)];
439 self.touch_sequence_map.insert(
440 self.current_sequence_id,
441 TouchSequenceInfo {
442 state: Touching,
443 active_touch_points,
444 touch_ids_in_move: FxHashSet::default(),
445 prevent_click: false,
446 prevent_move: TouchMoveAllowed::Pending,
447 pending_touch_move_actions: vec![],
448 hit_test_result_cache: None,
449 pan_policy_input: None,
450 },
451 );
452 } else {
453 debug!("Touch down in sequence {:?}.", self.current_sequence_id);
454 let touch_sequence = self.get_current_touch_sequence_mut();
455 touch_sequence
456 .active_touch_points
457 .push(TouchPoint::new(touch_id, point));
458 match touch_sequence.active_touch_points.len() {
459 2.. => {
460 touch_sequence.state = MultiTouch;
461 },
462 0..2 => {
463 unreachable!("Secondary touch_down event with less than 2 fingers active?");
464 },
465 }
466 touch_sequence.prevent_click = true;
468 }
469 }
470
471 pub(crate) fn notify_new_frame_start(&mut self) -> Option<FlingAction> {
472 let touch_sequence = self.touch_sequence_map.get_mut(&self.current_sequence_id)?;
473
474 let Flinging {
475 velocity,
476 point: cursor,
477 } = &mut touch_sequence.state
478 else {
479 self.observing_frames_for_fling.set(false);
480 return None;
481 };
482
483 if velocity.length().abs() < FLING_MIN_SCREEN_PX {
484 self.stop_fling_if_needed();
485 None
486 } else {
487 *velocity *= FLING_SCALING_FACTOR;
490 let _span = profile_traits::info_span!(
491 "TouchHandler::Flinging",
492 velocity = ?velocity,
493 )
494 .entered();
495 debug_assert!(velocity.length() <= FLING_MAX_SCREEN_PX);
496 Some(FlingAction {
497 delta: DeviceVector2D::new(velocity.x, velocity.y),
498 cursor: *cursor,
499 })
500 }
501 }
502
503 pub(crate) fn stop_fling_if_needed(&mut self) {
504 let current_sequence_id = self.current_sequence_id;
505 let Some(touch_sequence) = self.try_get_current_touch_sequence_mut() else {
506 debug!(
507 "Touch sequence already removed before stoping potential flinging during Paint update"
508 );
509 return;
510 };
511 let Flinging { .. } = touch_sequence.state else {
512 return;
513 };
514 let _span = profile_traits::info_span!("TouchHandler::FlingEnd").entered();
515 debug!("Stopping flinging in touch sequence {current_sequence_id:?}");
516 touch_sequence.state = Finished;
517 self.try_remove_touch_sequence(current_sequence_id);
520 self.observing_frames_for_fling.set(false);
521 }
522
523 pub(crate) fn on_touch_move(
524 &mut self,
525 touch_id: TouchId,
526 point: Point2D<f32, DevicePixel>,
527 scale: f32,
528 ) -> Option<ScrollZoomEvent> {
529 let touch_sequence = self.try_get_current_touch_sequence_mut()?;
533 let idx = match touch_sequence
534 .active_touch_points
535 .iter_mut()
536 .position(|t| t.touch_id == touch_id)
537 {
538 Some(i) => i,
539 None => {
540 error!("Got a touchmove event for a non-active touch point");
541 return None;
542 },
543 };
544 let old_point = touch_sequence.active_touch_points[idx].point;
545 let delta = point - old_point;
546 touch_sequence.update_hit_test_result_cache_pointer(delta);
547
548 let action = match touch_sequence.touch_count() {
549 1 => {
550 if let Panning {
551 policy,
552 ref mut velocity,
553 } = touch_sequence.state
554 {
555 match policy {
556 PanPolicy::NoScroll => None,
557 PanPolicy::Free | PanPolicy::Undetermined | PanPolicy::Lock(_) => {
558 let pan_delta = policy.pan_delta(delta);
559 *velocity += pan_delta;
561 *velocity /= 2.0;
562 touch_sequence.active_touch_points[idx].point = point;
563 Some(ScrollZoomEvent::Scroll(ScrollEvent {
564 scroll: Scroll::Delta((-pan_delta).into()),
565 point,
566 scroll_type: ScrollType::Touch,
567 }))
568 },
569 }
570 } else if delta.x.abs() > TOUCH_PAN_MIN_SCREEN_PX * scale ||
571 delta.y.abs() > TOUCH_PAN_MIN_SCREEN_PX * scale
572 {
573 let _span = profile_traits::info_span!(
574 "TouchHandler::ScrollBegin",
575 delta = ?delta,
576 )
577 .entered();
578 let dominant = if delta.y.abs() > delta.x.abs() {
584 PanAxis::Vertical
585 } else {
586 PanAxis::Horizontal
587 };
588 let policy = touch_sequence
589 .pan_policy_input
590 .map(|input| input.to_pan_policy(dominant))
591 .unwrap_or(PanPolicy::Undetermined);
592 let pan_delta = policy.pan_delta(delta);
594 touch_sequence.state = Panning {
595 policy,
596 velocity: pan_delta,
597 };
598 touch_sequence.prevent_click = true;
600 touch_sequence.active_touch_points[idx].point = point;
602
603 if policy == PanPolicy::NoScroll {
604 None
605 } else {
606 Some(ScrollZoomEvent::Scroll(ScrollEvent {
608 scroll: Scroll::Delta((-pan_delta).into()),
609 point,
610 scroll_type: ScrollType::Touch,
611 }))
612 }
613 } else {
614 None
617 }
618 },
619 2 => {
620 if touch_sequence.state == Pinching ||
621 delta.x.abs() > TOUCH_PAN_MIN_SCREEN_PX * scale ||
622 delta.y.abs() > TOUCH_PAN_MIN_SCREEN_PX * scale
623 {
624 touch_sequence.state = Pinching;
625 let (d0, _) = touch_sequence.pinch_distance_and_center();
626
627 touch_sequence.active_touch_points[idx].point = point;
629 let (d1, c1) = touch_sequence.pinch_distance_and_center();
630
631 Some(ScrollZoomEvent::PinchZoom(d1 / d0, c1))
632 } else {
633 None
636 }
637 },
638 _ => {
639 touch_sequence.active_touch_points[idx].point = point;
640 touch_sequence.state = MultiTouch;
641 None
642 },
643 };
644 if let Some(action) = action &&
646 touch_sequence.prevent_move == TouchMoveAllowed::Pending
647 {
648 touch_sequence.add_pending_touch_move_action(action);
649 }
650
651 action
652 }
653
654 pub(crate) fn on_touch_up(
655 &mut self,
656 touch_id: TouchId,
657 point: Point2D<f32, DevicePixel>,
658 ) -> bool {
659 let Some(touch_sequence) = self.try_get_current_touch_sequence_mut() else {
660 warn!("Current touch sequence not found");
661 return false;
662 };
663 let old = match touch_sequence
664 .active_touch_points
665 .iter()
666 .position(|t| t.touch_id == touch_id)
667 {
668 Some(i) => Some(touch_sequence.active_touch_points.swap_remove(i).point),
669 None => {
670 warn!("Got a touchup event for a non-active touch point");
671 None
672 },
673 };
674 match touch_sequence.state {
675 Touching => {
676 if touch_sequence.prevent_click {
677 touch_sequence.state = Finished;
678 } else {
679 touch_sequence.state = PendingClick;
680 }
681 },
682 Panning { policy, velocity } => {
683 if policy == PanPolicy::NoScroll {
685 touch_sequence.state = Finished;
686 } else if velocity.length().abs() >= FLING_MIN_SCREEN_PX {
687 let _span = profile_traits::info_span!(
688 "TouchHandler::FlingStart",
689 velocity = ?velocity,
690 )
691 .entered();
692 debug!(
694 "Transitioning to Fling. Cursor is {point:?}. Old cursor was {old:?}. \
695 Raw velocity is {velocity:?}."
696 );
697
698 let velocity = (velocity * 2.0).with_max_length(FLING_MAX_SCREEN_PX);
701 match touch_sequence.prevent_move {
702 TouchMoveAllowed::Allowed => {
703 touch_sequence.state = Flinging { velocity, point }
704 },
707 TouchMoveAllowed::Pending => {
708 touch_sequence.state = PendingFling { velocity, point }
709 },
710 TouchMoveAllowed::Prevented => touch_sequence.state = Finished,
711 }
712 } else {
713 let _span = profile_traits::info_span!("TouchHandler::ScrollEnd").entered();
714 touch_sequence.state = Finished;
715 }
716 },
717 Pinching => {
718 touch_sequence.state = Touching;
719 },
720 MultiTouch => {
721 if touch_sequence.active_touch_points.is_empty() {
723 touch_sequence.state = Finished;
724 }
725 },
726 PendingFling { .. } | Flinging { .. } | PendingClick | Finished => {
727 error!("Touch-up received, but touch handler already in post-touchup state.")
728 },
729 }
730
731 let pending_click = matches!(touch_sequence.state, PendingClick);
732 #[cfg(debug_assertions)]
733 if touch_sequence.active_touch_points.is_empty() {
734 debug_assert!(
735 touch_sequence.is_finished(),
736 "Did not transition to a finished state: {:?}",
737 touch_sequence.state
738 );
739 }
740 debug!(
741 "Touch up with remaining active touchpoints: {:?}, in sequence {:?}",
742 touch_sequence.active_touch_points.len(),
743 self.current_sequence_id
744 );
745 pending_click
746 }
747
748 pub(crate) fn on_touch_cancel(&mut self, touch_id: TouchId, _point: Point2D<f32, DevicePixel>) {
749 let Some(touch_sequence) = self.try_get_current_touch_sequence_mut() else {
751 return;
752 };
753 match touch_sequence
754 .active_touch_points
755 .iter()
756 .position(|t| t.touch_id == touch_id)
757 {
758 Some(i) => {
759 touch_sequence.active_touch_points.swap_remove(i);
760 },
761 None => {
762 warn!("Got a touchcancel event for a non-active touch point");
763 return;
764 },
765 }
766 if touch_sequence.active_touch_points.is_empty() {
767 touch_sequence.state = Finished;
768 }
769 }
770
771 pub(crate) fn get_hit_test_result_cache_value(&self) -> Option<PaintHitTestResult> {
772 let sequence = self.touch_sequence_map.get(&self.current_sequence_id)?;
773 if sequence.state == Finished {
774 return None;
775 }
776 sequence
777 .hit_test_result_cache
778 .as_ref()
779 .map(|cache| Some(cache.value.clone()))?
780 }
781
782 pub(crate) fn set_hit_test_result_cache_value(
783 &mut self,
784 value: PaintHitTestResult,
785 device_pixels_per_page: Scale<f32, CSSPixel, DevicePixel>,
786 ) {
787 if let Some(sequence) = self.touch_sequence_map.get_mut(&self.current_sequence_id) &&
788 sequence.hit_test_result_cache.is_none()
789 {
790 sequence.hit_test_result_cache = Some(HitTestResultCache {
791 value,
792 device_pixels_per_page,
793 });
794 }
795 }
796
797 pub(crate) fn set_pan_policy_input(&mut self, input: PanPolicyInput) {
802 if let Some(sequence) = self.touch_sequence_map.get_mut(&self.current_sequence_id) {
803 sequence.pan_policy_input = Some(input);
804 }
805 }
806
807 pub(crate) fn add_pending_touch_input_event(
808 &self,
809 id: InputEventId,
810 touch_id: TouchId,
811 event_type: TouchEventType,
812 ) {
813 self.pending_touch_input_events.borrow_mut().insert(
814 id,
815 PendingTouchInputEvent {
816 event_type,
817 sequence_id: self.current_sequence_id,
818 touch_id,
819 },
820 );
821 }
822
823 pub(crate) fn take_pending_touch_input_event(
824 &self,
825 id: InputEventId,
826 ) -> Option<PendingTouchInputEvent> {
827 self.pending_touch_input_events.borrow_mut().remove(&id)
828 }
829
830 pub(crate) fn add_touch_move_refresh_observer_if_necessary(
831 &self,
832 refresh_driver: Rc<BaseRefreshDriver>,
833 repaint_reason: &Cell<RepaintReason>,
834 ) {
835 if self.observing_frames_for_fling.get() {
836 return;
837 }
838
839 let Some(current_touch_sequence) = self.try_get_current_touch_sequence() else {
840 return;
841 };
842
843 if !matches!(
844 current_touch_sequence.state,
845 TouchSequenceState::Flinging { .. },
846 ) {
847 return;
848 }
849
850 refresh_driver.add_observer(Rc::new(FlingRefreshDriverObserver {
851 webview_id: self.webview_id,
852 }));
853 self.observing_frames_for_fling.set(true);
854 repaint_reason.set(repaint_reason.get().union(RepaintReason::StartedFlinging));
855 }
856}
857
858pub(crate) struct PendingTouchInputEvent {
862 pub event_type: TouchEventType,
863 pub sequence_id: TouchSequenceId,
864 pub touch_id: TouchId,
865}
866
867pub(crate) struct FlingRefreshDriverObserver {
868 pub webview_id: WebViewId,
869}
870
871impl RefreshDriverObserver for FlingRefreshDriverObserver {
872 fn frame_started(&self, painter: &mut Painter) -> bool {
873 painter
874 .webview_renderer_mut(self.webview_id)
875 .is_some_and(WebViewRenderer::update_touch_handling_at_new_frame_start)
876 }
877}