-
-
Notifications
You must be signed in to change notification settings - Fork 3.4k
Add retry for hit tests with expired epoch in result #37085
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
b5a3c97
ddad26f
54d3763
afa3f4b
3d7ced5
0d02b61
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2,9 +2,9 @@ | |
| * License, v. 2.0. If a copy of the MPL was not distributed with this | ||
| * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ | ||
|
|
||
| use std::cell::RefCell; | ||
| use std::collections::HashMap; | ||
| use std::cell::{Cell, RefCell}; | ||
| use std::collections::hash_map::Keys; | ||
| use std::collections::{HashMap, VecDeque}; | ||
| use std::rc::Rc; | ||
|
|
||
| use base::id::{PipelineId, WebViewId}; | ||
|
|
@@ -25,7 +25,7 @@ use webrender_api::units::{ | |
| }; | ||
| use webrender_api::{ExternalScrollId, HitTestFlags, ScrollLocation}; | ||
|
|
||
| use crate::compositor::{PipelineDetails, ServoRenderer}; | ||
| use crate::compositor::{HitTestError, PipelineDetails, ServoRenderer}; | ||
| use crate::touch::{TouchHandler, TouchMoveAction, TouchMoveAllowed, TouchSequenceState}; | ||
|
|
||
| // Default viewport constraints | ||
|
|
@@ -98,6 +98,10 @@ pub(crate) struct WebViewRenderer { | |
| /// Whether or not this [`WebViewRenderer`] isn't throttled and has a pipeline with | ||
| /// active animations or animation frame callbacks. | ||
| animating: bool, | ||
| /// Pending input events queue. Priavte and only this thread pushes events to it. | ||
| pending_point_input_events: RefCell<VecDeque<InputEvent>>, | ||
| /// WebRender is not ready between `SendDisplayList` and `WebRenderFrameReady` messages. | ||
| pub webrender_frame_ready: Cell<bool>, | ||
| } | ||
|
|
||
| impl Drop for WebViewRenderer { | ||
|
|
@@ -132,6 +136,8 @@ impl WebViewRenderer { | |
| max_viewport_zoom: None, | ||
| hidpi_scale_factor: Scale::new(hidpi_scale_factor.0), | ||
| animating: false, | ||
| pending_point_input_events: Default::default(), | ||
| webrender_frame_ready: Cell::default(), | ||
| } | ||
| } | ||
|
|
||
|
|
@@ -309,29 +315,89 @@ impl WebViewRenderer { | |
| } | ||
| } | ||
|
|
||
| pub(crate) fn dispatch_input_event(&mut self, event: InputEvent) { | ||
| pub(crate) fn dispatch_point_input_event(&mut self, mut event: InputEvent) -> bool { | ||
| // Events that do not need to do hit testing are sent directly to the | ||
| // constellation to filter down. | ||
| let Some(point) = event.point() else { | ||
| return; | ||
| return false; | ||
| }; | ||
|
|
||
| // Delay the event if the epoch is not synchronized yet (new frame is not ready), | ||
| // or hit test result would fail and the event is rejected anyway. | ||
| if !self.webrender_frame_ready.get() || !self.pending_point_input_events.borrow().is_empty() | ||
| { | ||
| self.pending_point_input_events | ||
| .borrow_mut() | ||
| .push_back(event); | ||
| return false; | ||
| } | ||
|
|
||
| // If we can't find a pipeline to send this event to, we cannot continue. | ||
| let get_pipeline_details = |pipeline_id| self.pipelines.get(&pipeline_id); | ||
| let Some(result) = self | ||
| let result = match self | ||
| .global | ||
| .borrow() | ||
| .hit_test_at_point(point, get_pipeline_details) | ||
| else { | ||
| return; | ||
| { | ||
| Ok(hit_test_results) => hit_test_results, | ||
| Err(HitTestError::EpochMismatch) => { | ||
| self.pending_point_input_events | ||
| .borrow_mut() | ||
| .push_back(event); | ||
| return false; | ||
| }, | ||
| _ => { | ||
| return false; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In this case, may also need to retry.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This just keeps the default logic, you can add a retry here with a proper reason. |
||
| }, | ||
| }; | ||
|
|
||
| self.global.borrow_mut().update_cursor(point, &result); | ||
| match event { | ||
longvatrong111 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| InputEvent::Touch(ref mut touch_event) => { | ||
xiaochengh marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| touch_event.init_sequence_id(self.touch_handler.current_sequence_id); | ||
| }, | ||
| InputEvent::MouseButton(_) | InputEvent::MouseMove(_) | InputEvent::Wheel(_) => { | ||
| self.global.borrow_mut().update_cursor(point, &result); | ||
| }, | ||
| _ => unreachable!("Unexpected input event type: {event:?}"), | ||
| } | ||
|
|
||
| if let Err(error) = self.global.borrow().constellation_sender.send( | ||
| EmbedderToConstellationMessage::ForwardInputEvent(self.id, event, Some(result)), | ||
| ) { | ||
| warn!("Sending event to constellation failed ({error:?})."); | ||
| false | ||
| } else { | ||
| true | ||
| } | ||
| } | ||
|
|
||
| pub(crate) fn dispatch_pending_point_input_events(&self) { | ||
| while let Some(event) = self.pending_point_input_events.borrow_mut().pop_front() { | ||
| // Events that do not need to do hit testing are sent directly to the | ||
| // constellation to filter down. | ||
| let Some(point) = event.point() else { | ||
| continue; | ||
| }; | ||
|
|
||
| // If we can't find a pipeline to send this event to, we cannot continue. | ||
| let get_pipeline_details = |pipeline_id| self.pipelines.get(&pipeline_id); | ||
| let Ok(result) = self | ||
| .global | ||
| .borrow() | ||
| .hit_test_at_point(point, get_pipeline_details) | ||
| else { | ||
| // Don't need to process pending input events in this frame any more. | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. On some complex pages, such as https://m.huaweimossel.com, a retry failure may occur once.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
If we don't retry touch events, there is no impact?
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The touchdown and touchup events exist in pairs. If touchdown is successful and touchup fails, the application's JavaScript will consider there to be a touchpoint present. When touchdown is triggered again, the number of touchpoints becomes 2.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks for your details. As my understanding, the problem occurs because of hit test failings. Since the retry keeps the order of events, it doesn't play a role here.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For the problem found now, should we first solve it by increasing the number of retries?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Increase amount of retry may lead to a scenario in which, 1 event takes several frames to be processed.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do you mean that if either down or up fails, everything will be retried?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes both must succeed or fail together.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. But there could be two touchdowns, and then two touchups. Or more. |
||
| // TODO: Add multiple retry later if needed. | ||
| return; | ||
| }; | ||
|
|
||
| self.global.borrow_mut().update_cursor(point, &result); | ||
|
|
||
| if let Err(error) = self.global.borrow().constellation_sender.send( | ||
| EmbedderToConstellationMessage::ForwardInputEvent(self.id, event, Some(result)), | ||
| ) { | ||
| warn!("Sending event to constellation failed ({error:?})."); | ||
| } | ||
| } | ||
| } | ||
|
|
||
|
|
@@ -401,29 +467,11 @@ impl WebViewRenderer { | |
| } | ||
| } | ||
|
|
||
| self.dispatch_input_event(event); | ||
| self.dispatch_point_input_event(event); | ||
| } | ||
|
|
||
| fn send_touch_event(&self, mut event: TouchEvent) -> bool { | ||
| let get_pipeline_details = |pipeline_id| self.pipelines.get(&pipeline_id); | ||
| let Some(result) = self | ||
| .global | ||
| .borrow() | ||
| .hit_test_at_point(event.point, get_pipeline_details) | ||
| else { | ||
| return false; | ||
| }; | ||
|
|
||
| event.init_sequence_id(self.touch_handler.current_sequence_id); | ||
| let event = InputEvent::Touch(event); | ||
| if let Err(e) = self.global.borrow().constellation_sender.send( | ||
| EmbedderToConstellationMessage::ForwardInputEvent(self.id, event, Some(result)), | ||
| ) { | ||
| warn!("Sending event to constellation failed ({:?}).", e); | ||
| false | ||
| } else { | ||
| true | ||
| } | ||
| fn send_touch_event(&mut self, event: TouchEvent) -> bool { | ||
| self.dispatch_point_input_event(InputEvent::Touch(event)) | ||
| } | ||
|
|
||
| pub(crate) fn on_touch_event(&mut self, event: TouchEvent) { | ||
|
|
@@ -687,13 +735,13 @@ impl WebViewRenderer { | |
| /// <http://w3c.github.io/touch-events/#mouse-events> | ||
| fn simulate_mouse_click(&mut self, point: DevicePoint) { | ||
| let button = MouseButton::Left; | ||
| self.dispatch_input_event(InputEvent::MouseMove(MouseMoveEvent::new(point))); | ||
| self.dispatch_input_event(InputEvent::MouseButton(MouseButtonEvent::new( | ||
| self.dispatch_point_input_event(InputEvent::MouseMove(MouseMoveEvent::new(point))); | ||
| self.dispatch_point_input_event(InputEvent::MouseButton(MouseButtonEvent::new( | ||
| MouseButtonAction::Down, | ||
| button, | ||
| point, | ||
| ))); | ||
| self.dispatch_input_event(InputEvent::MouseButton(MouseButtonEvent::new( | ||
| self.dispatch_point_input_event(InputEvent::MouseButton(MouseButtonEvent::new( | ||
| MouseButtonAction::Up, | ||
| button, | ||
| point, | ||
|
|
@@ -858,7 +906,8 @@ impl WebViewRenderer { | |
| HitTestFlags::FIND_ALL, | ||
| None, | ||
| get_pipeline_details, | ||
| ); | ||
| ) | ||
| .unwrap_or_default(); | ||
|
|
||
| // Iterate through all hit test results, processing only the first node of each pipeline. | ||
| // This is needed to propagate the scroll events from a pipeline representing an iframe to | ||
|
|
||

Uh oh!
There was an error while loading. Please reload this page.