mirror of
https://github.com/servo/servo.git
synced 2025-08-05 13:40:08 +01:00
webdriver: improve perform pointermove & wheel actions with more accurate coordinates (#38095)
1. Create `get_origin_relative_coordinates` according to [spec](https://w3c.github.io/webdriver/#dfn-get-coordinates-relative-to-an-origin) to be reused 2. Add previously missing offset for PointerOrigin::Element 3. Refactor code for perform pointermove/wheel to be closer to spec. 4. Handle some issues with spec: https://github.com/w3c/webdriver/issues/1758 Testing: Several new passing cases as we are more precise with coordinates now. Fixes: Part of #38042. --------- Signed-off-by: Euclid Ye <euclid.ye@huawei.com>
This commit is contained in:
parent
189e649222
commit
2e3c280f46
8 changed files with 128 additions and 79 deletions
|
@ -286,7 +286,7 @@ impl Handler {
|
|||
self.dispatch_general_action(input_id);
|
||||
},
|
||||
ActionItem::Wheel(WheelActionItem::Wheel(WheelAction::Scroll(scroll_action))) => {
|
||||
self.dispatch_scroll_action(scroll_action, tick_duration)?;
|
||||
self.dispatch_scroll_action(input_id, scroll_action, tick_duration)?;
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
|
@ -437,6 +437,46 @@ impl Handler {
|
|||
let _ = self.send_message_to_embedder(cmd_msg);
|
||||
}
|
||||
|
||||
/// <https://w3c.github.io/webdriver/#dfn-get-coordinates-relative-to-an-origin>
|
||||
fn get_origin_relative_coordinates(
|
||||
&self,
|
||||
origin: &PointerOrigin,
|
||||
x_offset: f64,
|
||||
y_offset: f64,
|
||||
source_id: &str,
|
||||
) -> Result<(f64, f64), ErrorStatus> {
|
||||
match origin {
|
||||
PointerOrigin::Viewport => Ok((x_offset, y_offset)),
|
||||
PointerOrigin::Pointer => {
|
||||
// Step 1. Let start x be equal to the x property of source.
|
||||
// Step 2. Let start y be equal to the y property of source.
|
||||
let (start_x, start_y) = match self
|
||||
.session()
|
||||
.unwrap()
|
||||
.input_state_table
|
||||
.borrow()
|
||||
.get(source_id)
|
||||
.unwrap()
|
||||
{
|
||||
InputSourceState::Pointer(pointer_input_state) => {
|
||||
(pointer_input_state.x, pointer_input_state.y)
|
||||
},
|
||||
_ => unreachable!(),
|
||||
};
|
||||
// Step 3. Let x equal start x + x offset and y equal start y + y offset.
|
||||
Ok((start_x + x_offset, start_y + y_offset))
|
||||
},
|
||||
PointerOrigin::Element(web_element) => {
|
||||
// Steps 1 - 2: Check "no such element", covered in script thread handler.
|
||||
|
||||
// Step 3. Let x element and y element be the result of calculating the in-view center point of element.
|
||||
let (x_element, y_element) = self.get_element_in_view_center_point(web_element)?;
|
||||
// Step 4. Let x equal x element + x offset, and y equal y element + y offset.
|
||||
Ok((x_element as f64 + x_offset, y_element as f64 + y_offset))
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// <https://w3c.github.io/webdriver/#dfn-dispatch-a-pointermove-action>
|
||||
pub(crate) fn dispatch_pointermove_action(
|
||||
&self,
|
||||
|
@ -446,16 +486,40 @@ impl Handler {
|
|||
) -> Result<(), ErrorStatus> {
|
||||
let tick_start = Instant::now();
|
||||
|
||||
// Steps 1 - 2
|
||||
// Step 1. Let x offset be equal to the x property of action object.
|
||||
let x_offset = action.x;
|
||||
// Step 2. Let y offset be equal to the y property of action object.
|
||||
let y_offset = action.y;
|
||||
|
||||
// Steps 3 - 4
|
||||
// Step 3. Let origin be equal to the origin property of action object.
|
||||
let origin = &action.origin;
|
||||
|
||||
// Step 4. Let (x, y) be the result of trying to get coordinates relative to an origin
|
||||
// with source, x offset, y offset, origin, browsing context, and actions options.
|
||||
|
||||
let (x, y) = self.get_origin_relative_coordinates(origin, x_offset, y_offset, source_id)?;
|
||||
|
||||
// Step 5 - 6
|
||||
self.check_viewport_bound(x, y)?;
|
||||
|
||||
// Step 7. Let duration be equal to action object's duration property
|
||||
// if it is not undefined, or tick duration otherwise.
|
||||
let duration = match action.duration {
|
||||
Some(duration) => duration,
|
||||
None => tick_duration,
|
||||
};
|
||||
|
||||
// Step 8. If duration is greater than 0 and inside any implementation-defined bounds,
|
||||
// asynchronously wait for an implementation defined amount of time to pass.
|
||||
if duration > 0 {
|
||||
thread::sleep(Duration::from_millis(POINTERMOVE_INTERVAL));
|
||||
}
|
||||
|
||||
let (start_x, start_y) = match self
|
||||
.session()
|
||||
.unwrap()
|
||||
.input_state_table
|
||||
.borrow_mut()
|
||||
.borrow()
|
||||
.get(source_id)
|
||||
.unwrap()
|
||||
{
|
||||
|
@ -465,29 +529,6 @@ impl Handler {
|
|||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
let (x, y) = match action.origin {
|
||||
PointerOrigin::Viewport => (x_offset, y_offset),
|
||||
PointerOrigin::Pointer => (start_x + x_offset, start_y + y_offset),
|
||||
PointerOrigin::Element(ref web_element) => {
|
||||
let point = self.get_element_origin_relative_coordinates(web_element)?;
|
||||
(point.0 as f64, point.1 as f64)
|
||||
},
|
||||
};
|
||||
|
||||
// Step 5 - 6
|
||||
self.check_viewport_bound(x, y)?;
|
||||
|
||||
// Step 7
|
||||
let duration = match action.duration {
|
||||
Some(duration) => duration,
|
||||
None => tick_duration,
|
||||
};
|
||||
|
||||
// Step 8
|
||||
if duration > 0 {
|
||||
thread::sleep(Duration::from_millis(POINTERMOVE_INTERVAL));
|
||||
}
|
||||
|
||||
// Step 9 - 18
|
||||
self.perform_pointer_move(source_id, duration, start_x, start_y, x, y, tick_start);
|
||||
|
||||
|
@ -578,6 +619,7 @@ impl Handler {
|
|||
/// <https://w3c.github.io/webdriver/#dfn-dispatch-a-scroll-action>
|
||||
fn dispatch_scroll_action(
|
||||
&self,
|
||||
source_id: &str,
|
||||
action: &WheelScrollAction,
|
||||
tick_duration: u64,
|
||||
) -> Result<(), ErrorStatus> {
|
||||
|
@ -589,51 +631,68 @@ impl Handler {
|
|||
|
||||
let tick_start = Instant::now();
|
||||
|
||||
// Step 1
|
||||
// Step 1. Let x offset be equal to the x property of action object.
|
||||
let Some(x_offset) = action.x else {
|
||||
return Err(ErrorStatus::InvalidArgument);
|
||||
};
|
||||
|
||||
// Step 2
|
||||
// Step 2. Let y offset be equal to the y property of action object.
|
||||
let Some(y_offset) = action.y else {
|
||||
return Err(ErrorStatus::InvalidArgument);
|
||||
};
|
||||
|
||||
// Step 3 - 4
|
||||
// Get coordinates relative to an origin.
|
||||
let (x, y) = match action.origin {
|
||||
PointerOrigin::Viewport => (x_offset, y_offset),
|
||||
PointerOrigin::Pointer => return Err(ErrorStatus::InvalidArgument),
|
||||
PointerOrigin::Element(ref web_element) => {
|
||||
self.get_element_origin_relative_coordinates(web_element)?
|
||||
},
|
||||
};
|
||||
// Step 3. Let origin be equal to the origin property of action object.
|
||||
let origin = &action.origin;
|
||||
|
||||
// Pointer origin isn't currently supported for wheel input source
|
||||
// See: https://github.com/w3c/webdriver/issues/1758
|
||||
|
||||
if origin == &PointerOrigin::Pointer {
|
||||
return Err(ErrorStatus::InvalidArgument);
|
||||
}
|
||||
|
||||
// Step 4. Let (x, y) be the result of trying to get coordinates relative to an origin
|
||||
// with source, x offset, y offset, origin, browsing context, and actions options.
|
||||
let (x, y) =
|
||||
self.get_origin_relative_coordinates(origin, x_offset as _, y_offset as _, source_id)?;
|
||||
|
||||
// Step 5 - 6
|
||||
self.check_viewport_bound(x as _, y as _)?;
|
||||
self.check_viewport_bound(x, y)?;
|
||||
|
||||
// Step 7 - 8
|
||||
// Step 7. Let delta x be equal to the deltaX property of action object.
|
||||
let Some(delta_x) = action.deltaX else {
|
||||
return Err(ErrorStatus::InvalidArgument);
|
||||
};
|
||||
|
||||
// Step 8. Let delta y be equal to the deltaY property of action object.
|
||||
let Some(delta_y) = action.deltaY else {
|
||||
return Err(ErrorStatus::InvalidArgument);
|
||||
};
|
||||
|
||||
// Step 9
|
||||
// Step 9. Let duration be equal to action object's duration property
|
||||
// if it is not undefined, or tick duration otherwise.
|
||||
let duration = match action.duration {
|
||||
Some(duration) => duration,
|
||||
None => tick_duration,
|
||||
};
|
||||
|
||||
// Step 10
|
||||
// Step 10. If duration is greater than 0 and inside any implementation-defined bounds,
|
||||
// asynchronously wait for an implementation defined amount of time to pass.
|
||||
if duration > 0 {
|
||||
thread::sleep(Duration::from_millis(WHEELSCROLL_INTERVAL));
|
||||
}
|
||||
|
||||
// Step 11
|
||||
self.perform_scroll(duration, x, y, delta_x, delta_y, 0, 0, tick_start);
|
||||
// Step 11. Perform a scroll with arguments global key state, duration, x, y, delta x, delta y, 0, 0.
|
||||
self.perform_scroll(
|
||||
duration,
|
||||
x,
|
||||
y,
|
||||
delta_x as _,
|
||||
delta_y as _,
|
||||
0.0,
|
||||
0.0,
|
||||
tick_start,
|
||||
);
|
||||
|
||||
// Step 12
|
||||
Ok(())
|
||||
|
@ -644,27 +703,31 @@ impl Handler {
|
|||
fn perform_scroll(
|
||||
&self,
|
||||
duration: u64,
|
||||
x: i64,
|
||||
y: i64,
|
||||
target_delta_x: i64,
|
||||
target_delta_y: i64,
|
||||
mut curr_delta_x: i64,
|
||||
mut curr_delta_y: i64,
|
||||
x: f64,
|
||||
y: f64,
|
||||
target_delta_x: f64,
|
||||
target_delta_y: f64,
|
||||
mut curr_delta_x: f64,
|
||||
mut curr_delta_y: f64,
|
||||
tick_start: Instant,
|
||||
) {
|
||||
let session = self.session().unwrap();
|
||||
|
||||
// Step 1
|
||||
// Step 1. Let time delta be the time since the beginning of the current tick,
|
||||
// measured in milliseconds on a monotonic clock.
|
||||
let time_delta = tick_start.elapsed().as_millis();
|
||||
|
||||
// Step 2
|
||||
// Step 2. Let duration ratio be the ratio of time delta and duration,
|
||||
// if duration is greater than 0, or 1 otherwise.
|
||||
let duration_ratio = if duration > 0 {
|
||||
time_delta as f64 / duration as f64
|
||||
} else {
|
||||
1.0
|
||||
};
|
||||
|
||||
// Step 3
|
||||
// Step 3. If duration ratio is 1, or close enough to 1 that
|
||||
// the implementation will not further subdivide the move action,
|
||||
// let last be true. Otherwise let last be false.
|
||||
let last = 1.0 - duration_ratio < 0.001;
|
||||
|
||||
// Step 4
|
||||
|
@ -672,15 +735,15 @@ impl Handler {
|
|||
(target_delta_x - curr_delta_x, target_delta_y - curr_delta_y)
|
||||
} else {
|
||||
(
|
||||
(duration_ratio * target_delta_x as f64) as i64 - curr_delta_x,
|
||||
(duration_ratio * target_delta_y as f64) as i64 - curr_delta_y,
|
||||
duration_ratio * target_delta_x - curr_delta_x,
|
||||
duration_ratio * target_delta_y - curr_delta_y,
|
||||
)
|
||||
};
|
||||
|
||||
// Step 5
|
||||
// Actually "last" should not be checked here based on spec.
|
||||
// However, we need to send the webdriver id at the final perform.
|
||||
if delta_x != 0 || delta_y != 0 || last {
|
||||
if delta_x != 0.0 || delta_y != 0.0 || last {
|
||||
// Perform implementation-specific action dispatch steps
|
||||
let msg_id = if last {
|
||||
self.increment_num_pending_actions();
|
||||
|
@ -690,10 +753,10 @@ impl Handler {
|
|||
};
|
||||
let cmd_msg = WebDriverCommandMsg::WheelScrollAction(
|
||||
session.webview_id,
|
||||
x as f32,
|
||||
y as f32,
|
||||
delta_x as f64,
|
||||
delta_y as f64,
|
||||
x,
|
||||
y,
|
||||
delta_x,
|
||||
delta_y,
|
||||
msg_id,
|
||||
);
|
||||
let _ = self.send_message_to_embedder(cmd_msg);
|
||||
|
@ -743,7 +806,8 @@ impl Handler {
|
|||
}
|
||||
}
|
||||
|
||||
fn get_element_origin_relative_coordinates(
|
||||
/// <https://w3c.github.io/webdriver/#dfn-center-point>
|
||||
fn get_element_in_view_center_point(
|
||||
&self,
|
||||
web_element: &WebElement,
|
||||
) -> Result<(i64, i64), ErrorStatus> {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue