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(DevicePoint),
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(&mut self, touch_id: TouchId, point: Point2D<f32, DevicePixel>) {
655 let Some(touch_sequence) = self.try_get_current_touch_sequence_mut() else {
656 warn!("Current touch sequence not found");
657 return;
658 };
659 let old = match touch_sequence
660 .active_touch_points
661 .iter()
662 .position(|t| t.touch_id == touch_id)
663 {
664 Some(i) => Some(touch_sequence.active_touch_points.swap_remove(i).point),
665 None => {
666 warn!("Got a touchup event for a non-active touch point");
667 None
668 },
669 };
670 match touch_sequence.state {
671 Touching => {
672 if touch_sequence.prevent_click {
673 touch_sequence.state = Finished;
674 } else {
675 touch_sequence.state = PendingClick(point);
676 }
677 },
678 Panning { policy, velocity } => {
679 if policy == PanPolicy::NoScroll {
681 touch_sequence.state = Finished;
682 } else if velocity.length().abs() >= FLING_MIN_SCREEN_PX {
683 let _span = profile_traits::info_span!(
684 "TouchHandler::FlingStart",
685 velocity = ?velocity,
686 )
687 .entered();
688 debug!(
690 "Transitioning to Fling. Cursor is {point:?}. Old cursor was {old:?}. \
691 Raw velocity is {velocity:?}."
692 );
693
694 let velocity = (velocity * 2.0).with_max_length(FLING_MAX_SCREEN_PX);
697 match touch_sequence.prevent_move {
698 TouchMoveAllowed::Allowed => {
699 touch_sequence.state = Flinging { velocity, point }
700 },
703 TouchMoveAllowed::Pending => {
704 touch_sequence.state = PendingFling { velocity, point }
705 },
706 TouchMoveAllowed::Prevented => touch_sequence.state = Finished,
707 }
708 } else {
709 let _span = profile_traits::info_span!("TouchHandler::ScrollEnd").entered();
710 touch_sequence.state = Finished;
711 }
712 },
713 Pinching => {
714 touch_sequence.state = Touching;
715 },
716 MultiTouch => {
717 if touch_sequence.active_touch_points.is_empty() {
719 touch_sequence.state = Finished;
720 }
721 },
722 PendingFling { .. } | Flinging { .. } | PendingClick(_) | Finished => {
723 error!("Touch-up received, but touch handler already in post-touchup state.")
724 },
725 }
726 #[cfg(debug_assertions)]
727 if touch_sequence.active_touch_points.is_empty() {
728 debug_assert!(
729 touch_sequence.is_finished(),
730 "Did not transition to a finished state: {:?}",
731 touch_sequence.state
732 );
733 }
734 debug!(
735 "Touch up with remaining active touchpoints: {:?}, in sequence {:?}",
736 touch_sequence.active_touch_points.len(),
737 self.current_sequence_id
738 );
739 }
740
741 pub(crate) fn on_touch_cancel(&mut self, touch_id: TouchId, _point: Point2D<f32, DevicePixel>) {
742 let Some(touch_sequence) = self.try_get_current_touch_sequence_mut() else {
744 return;
745 };
746 match touch_sequence
747 .active_touch_points
748 .iter()
749 .position(|t| t.touch_id == touch_id)
750 {
751 Some(i) => {
752 touch_sequence.active_touch_points.swap_remove(i);
753 },
754 None => {
755 warn!("Got a touchcancel event for a non-active touch point");
756 return;
757 },
758 }
759 if touch_sequence.active_touch_points.is_empty() {
760 touch_sequence.state = Finished;
761 }
762 }
763
764 pub(crate) fn get_hit_test_result_cache_value(&self) -> Option<PaintHitTestResult> {
765 let sequence = self.touch_sequence_map.get(&self.current_sequence_id)?;
766 if sequence.state == Finished {
767 return None;
768 }
769 sequence
770 .hit_test_result_cache
771 .as_ref()
772 .map(|cache| Some(cache.value.clone()))?
773 }
774
775 pub(crate) fn set_hit_test_result_cache_value(
776 &mut self,
777 value: PaintHitTestResult,
778 device_pixels_per_page: Scale<f32, CSSPixel, DevicePixel>,
779 ) {
780 if let Some(sequence) = self.touch_sequence_map.get_mut(&self.current_sequence_id) &&
781 sequence.hit_test_result_cache.is_none()
782 {
783 sequence.hit_test_result_cache = Some(HitTestResultCache {
784 value,
785 device_pixels_per_page,
786 });
787 }
788 }
789
790 pub(crate) fn set_pan_policy_input(&mut self, input: PanPolicyInput) {
795 if let Some(sequence) = self.touch_sequence_map.get_mut(&self.current_sequence_id) {
796 sequence.pan_policy_input = Some(input);
797 }
798 }
799
800 pub(crate) fn add_pending_touch_input_event(
801 &self,
802 id: InputEventId,
803 touch_id: TouchId,
804 event_type: TouchEventType,
805 ) {
806 self.pending_touch_input_events.borrow_mut().insert(
807 id,
808 PendingTouchInputEvent {
809 event_type,
810 sequence_id: self.current_sequence_id,
811 touch_id,
812 },
813 );
814 }
815
816 pub(crate) fn take_pending_touch_input_event(
817 &self,
818 id: InputEventId,
819 ) -> Option<PendingTouchInputEvent> {
820 self.pending_touch_input_events.borrow_mut().remove(&id)
821 }
822
823 pub(crate) fn add_touch_move_refresh_observer_if_necessary(
824 &self,
825 refresh_driver: Rc<BaseRefreshDriver>,
826 repaint_reason: &Cell<RepaintReason>,
827 ) {
828 if self.observing_frames_for_fling.get() {
829 return;
830 }
831
832 let Some(current_touch_sequence) = self.try_get_current_touch_sequence() else {
833 return;
834 };
835
836 if !matches!(
837 current_touch_sequence.state,
838 TouchSequenceState::Flinging { .. },
839 ) {
840 return;
841 }
842
843 refresh_driver.add_observer(Rc::new(FlingRefreshDriverObserver {
844 webview_id: self.webview_id,
845 }));
846 self.observing_frames_for_fling.set(true);
847 repaint_reason.set(repaint_reason.get().union(RepaintReason::StartedFlinging));
848 }
849}
850
851pub(crate) struct PendingTouchInputEvent {
855 pub event_type: TouchEventType,
856 pub sequence_id: TouchSequenceId,
857 pub touch_id: TouchId,
858}
859
860pub(crate) struct FlingRefreshDriverObserver {
861 pub webview_id: WebViewId,
862}
863
864impl RefreshDriverObserver for FlingRefreshDriverObserver {
865 fn frame_started(&self, painter: &mut Painter) -> bool {
866 painter
867 .webview_renderer_mut(self.webview_id)
868 .is_some_and(WebViewRenderer::update_touch_handling_at_new_frame_start)
869 }
870}