Implement pointerMove webdriver action

This commit is contained in:
George Roman 2019-07-31 00:18:27 +03:00
parent 2a9b2fe027
commit f064883e07
9 changed files with 306 additions and 28 deletions

View file

@ -3,30 +3,40 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use crate::Handler;
use crossbeam_channel::Sender;
use ipc_channel::ipc;
use keyboard_types::webdriver::KeyInputState;
use script_traits::webdriver_msg::WebDriverScriptCommand;
use script_traits::{ConstellationMsg, MouseButton, MouseEventType, WebDriverCommandMsg};
use std::cmp;
use std::collections::HashSet;
use std::sync::{Arc, Mutex};
use std::thread;
use std::time::{Duration, Instant};
use webdriver::actions::{ActionSequence, ActionsType, GeneralAction, NullActionItem};
use webdriver::actions::{KeyAction, KeyActionItem, KeyDownAction, KeyUpAction};
use webdriver::actions::{
PointerAction, PointerActionItem, PointerActionParameters, PointerDownAction,
};
use webdriver::actions::{PointerType, PointerUpAction};
use webdriver::actions::{PointerMoveAction, PointerOrigin, PointerType, PointerUpAction};
use webdriver::error::ErrorStatus;
// Interval between pointerMove increments in ms, based on common vsync
static POINTERMOVE_INTERVAL: u64 = 17;
// https://w3c.github.io/webdriver/#dfn-input-source-state
pub(crate) enum InputSourceState {
Null,
Key(KeyInputState),
Pointer(PointerInputState),
Pointer(Arc<Mutex<PointerInputState>>),
}
// https://w3c.github.io/webdriver/#dfn-pointer-input-source
pub(crate) struct PointerInputState {
subtype: PointerType,
pressed: HashSet<u64>,
x: u64,
y: u64,
x: i64,
y: i64,
}
impl PointerInputState {
@ -72,6 +82,73 @@ fn compute_tick_duration(tick_actions: &ActionSequence) -> u64 {
duration
}
// https://w3c.github.io/webdriver/#dfn-perform-a-pointer-move
fn perform_pointer_move(
constellation_chan: Sender<ConstellationMsg>,
pointer_input_state: Arc<Mutex<PointerInputState>>,
duration: u64,
start_x: i64,
start_y: i64,
target_x: i64,
target_y: i64,
tick_start: Instant,
) {
let mut pointer_input_state = pointer_input_state.lock().unwrap();
loop {
// Step 1
let time_delta = tick_start.elapsed().as_millis();
// Step 2
let duration_ratio = if duration > 0 {
time_delta as f64 / duration as f64
} else {
1.0
};
// Step 3
let last = if 1.0 - duration_ratio < 0.001 {
true
} else {
false
};
// Step 4
let (x, y) = if last {
(target_x, target_y)
} else {
(
(duration_ratio * (target_x - start_x) as f64) as i64 + start_x,
(duration_ratio * (target_y - start_y) as f64) as i64 + start_y,
)
};
// Steps 5 - 6
let current_x = pointer_input_state.x;
let current_y = pointer_input_state.y;
// Step 7
if x != current_x || y != current_y {
// Step 7.2
let cmd_msg = WebDriverCommandMsg::MouseMoveAction(x as f32, y as f32);
constellation_chan
.send(ConstellationMsg::WebDriverCommand(cmd_msg))
.unwrap();
// Step 7.3
pointer_input_state.x = x;
pointer_input_state.y = y;
}
// Step 8
if last {
return;
}
// Step 9
thread::sleep(Duration::from_millis(POINTERMOVE_INTERVAL));
}
}
fn u64_to_mouse_button(button: u64) -> Option<MouseButton> {
if MouseButton::Left as u64 == button {
Some(MouseButton::Left)
@ -86,11 +163,15 @@ fn u64_to_mouse_button(button: u64) -> Option<MouseButton> {
impl Handler {
// https://w3c.github.io/webdriver/#dfn-dispatch-actions
pub(crate) fn dispatch_actions(&mut self, actions_by_tick: &[ActionSequence]) {
pub(crate) fn dispatch_actions(
&mut self,
actions_by_tick: &[ActionSequence],
) -> Result<(), ErrorStatus> {
for tick_actions in actions_by_tick.iter() {
let tick_duration = compute_tick_duration(&tick_actions);
self.dispatch_tick_actions(&tick_actions, tick_duration);
self.dispatch_tick_actions(&tick_actions, tick_duration)?;
}
Ok(())
}
fn dispatch_general_action(&mut self, source_id: &str) {
@ -104,7 +185,11 @@ impl Handler {
}
// https://w3c.github.io/webdriver/#dfn-dispatch-tick-actions
fn dispatch_tick_actions(&mut self, tick_actions: &ActionSequence, _tick_duration: u64) {
fn dispatch_tick_actions(
&mut self,
tick_actions: &ActionSequence,
tick_duration: u64,
) -> Result<(), ErrorStatus> {
let source_id = &tick_actions.id;
match &tick_actions.actions {
ActionsType::Null { actions } => {
@ -150,15 +235,19 @@ impl Handler {
.unwrap()
.input_state_table
.entry(source_id.to_string())
.or_insert(InputSourceState::Pointer(PointerInputState::new(
&parameters.pointer_type,
)));
.or_insert(InputSourceState::Pointer(Arc::new(Mutex::new(
PointerInputState::new(&parameters.pointer_type),
))));
match action {
PointerAction::Cancel => (),
PointerAction::Down(action) => {
self.dispatch_pointerdown_action(&source_id, &action)
},
PointerAction::Move(_action) => (),
PointerAction::Move(action) => self.dispatch_pointermove_action(
&source_id,
&action,
tick_duration,
)?,
PointerAction::Up(action) => {
self.dispatch_pointerup_action(&source_id, &action)
},
@ -168,6 +257,8 @@ impl Handler {
}
},
}
Ok(())
}
// https://w3c.github.io/webdriver/#dfn-dispatch-a-keydown-action
@ -231,10 +322,10 @@ impl Handler {
fn dispatch_pointerdown_action(&mut self, source_id: &str, action: &PointerDownAction) {
let session = self.session.as_mut().unwrap();
let pointer_input_state = match session.input_state_table.get_mut(source_id).unwrap() {
let mut pointer_input_state = match session.input_state_table.get(source_id).unwrap() {
InputSourceState::Null => unreachable!(),
InputSourceState::Key(_) => unreachable!(),
InputSourceState::Pointer(pointer_input_state) => pointer_input_state,
InputSourceState::Pointer(pointer_input_state) => pointer_input_state.lock().unwrap(),
};
if pointer_input_state.pressed.contains(&action.button) {
@ -277,10 +368,10 @@ impl Handler {
fn dispatch_pointerup_action(&mut self, source_id: &str, action: &PointerUpAction) {
let session = self.session.as_mut().unwrap();
let pointer_input_state = match session.input_state_table.get_mut(source_id).unwrap() {
let mut pointer_input_state = match session.input_state_table.get(source_id).unwrap() {
InputSourceState::Null => unreachable!(),
InputSourceState::Key(_) => unreachable!(),
InputSourceState::Pointer(pointer_input_state) => pointer_input_state,
InputSourceState::Pointer(pointer_input_state) => pointer_input_state.lock().unwrap(),
};
if !pointer_input_state.pressed.contains(&action.button) {
@ -318,4 +409,116 @@ impl Handler {
.unwrap();
}
}
// https://w3c.github.io/webdriver/#dfn-dispatch-a-pointermove-action
fn dispatch_pointermove_action(
&mut self,
source_id: &str,
action: &PointerMoveAction,
tick_duration: u64,
) -> Result<(), ErrorStatus> {
let tick_start = Instant::now();
// Steps 1 - 2
let x_offset = action.x.unwrap_or(0);
let y_offset = action.y.unwrap_or(0);
// Steps 3 - 4
let (start_x, start_y) = match self
.session
.as_ref()
.unwrap()
.input_state_table
.get(source_id)
.unwrap()
{
InputSourceState::Null => unreachable!(),
InputSourceState::Key(_) => unreachable!(),
InputSourceState::Pointer(pointer_input_state) => {
let pointer_input_state = pointer_input_state.lock().unwrap();
(pointer_input_state.x, pointer_input_state.y)
},
};
// Step 5 - 6
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 x) => {
let (sender, receiver) = ipc::channel().unwrap();
self.top_level_script_command(WebDriverScriptCommand::GetElementInViewCenterPoint(
x.to_string(),
sender,
))
.unwrap();
match receiver.recv().unwrap() {
Ok(point) => match point {
Some(point) => point,
None => return Err(ErrorStatus::UnknownError),
},
Err(_) => return Err(ErrorStatus::UnknownError),
}
},
};
let (sender, receiver) = ipc::channel().unwrap();
let cmd_msg = WebDriverCommandMsg::GetWindowSize(
self.session.as_ref().unwrap().top_level_browsing_context_id,
sender,
);
self.constellation_chan
.send(ConstellationMsg::WebDriverCommand(cmd_msg))
.unwrap();
// Steps 7 - 8
let viewport = receiver.recv().unwrap().initial_viewport;
if x < 0 || x as f32 > viewport.width || y < 0 || y as f32 > viewport.height {
return Err(ErrorStatus::MoveTargetOutOfBounds);
}
// Step 9
let duration = match action.duration {
Some(duration) => duration,
None => tick_duration,
};
// Step 10
if duration > 0 {
thread::sleep(Duration::from_millis(POINTERMOVE_INTERVAL));
}
let pointer_input_state = match self
.session
.as_ref()
.unwrap()
.input_state_table
.get(source_id)
.unwrap()
{
InputSourceState::Null => unreachable!(),
InputSourceState::Key(_) => unreachable!(),
InputSourceState::Pointer(pointer_input_state) => pointer_input_state,
};
let constellation_chan = self.constellation_chan.clone();
let pointer_input_state = pointer_input_state.clone();
// Step 11
thread::spawn(move || {
perform_pointer_move(
constellation_chan,
pointer_input_state,
duration,
start_x,
start_y,
x,
y,
tick_start,
);
});
// Step 12
Ok(())
}
}

View file

@ -1345,9 +1345,10 @@ impl Handler {
&mut self,
parameters: &ActionsParameters,
) -> WebDriverResult<WebDriverResponse> {
self.dispatch_actions(&parameters.actions);
Ok(WebDriverResponse::Void)
match self.dispatch_actions(&parameters.actions) {
Ok(_) => Ok(WebDriverResponse::Void),
Err(error) => Err(WebDriverError::new(error, "")),
}
}
fn handle_release_actions(&mut self) -> WebDriverResult<WebDriverResponse> {
@ -1356,7 +1357,10 @@ impl Handler {
session.input_cancel_list.reverse();
mem::replace(&mut session.input_cancel_list, Vec::new())
};
self.dispatch_actions(&input_cancel_list);
if let Err(error) = self.dispatch_actions(&input_cancel_list) {
return Err(WebDriverError::new(error, ""));
}
let session = self.session_mut()?;
session.input_state_table = HashMap::new();