use crate::{
result::{PebblesError, PebblesErrorDetails},
service::Service,
rect::Rect,
error,
WebElement,
raise,
};
use rand::Rng;
use serde_json::{json, Value};
use std::rc::Rc;
pub struct Mouse {
x: i64,
y: i64,
service: Rc<Service>,
}
impl Mouse {
pub fn new(service: Rc<Service>) -> Self {
Mouse {
x: 0,
y: 0,
service,
}
}
pub fn down(&self, button: i64) -> Result<(), PebblesError> {
let actions = json!({"actions": [
{
"type": "pointer",
"id": "mouse",
"parameters": {
"pointerType": "mouse"
},
"actions": [
{"type": "pointerDown", "duration": 0, "button": button}
]
}
]});
self.service.post("/actions", &actions)?;
Ok(())
}
pub fn up(&self, button: i64) -> Result<(), PebblesError> {
let actions = json!({"actions": [
{
"type": "pointer",
"id": "mouse",
"parameters": {
"pointerType": "mouse"
},
"actions": [
{"type": "pointerUp", "duration": 0, "button": button}
]
}
]});
self.service.post("/actions", &actions)?;
Ok(())
}
pub fn click(&self, button: i64) -> Result<(), PebblesError> {
let mut rng = rand::thread_rng();
let actions = json!({"actions": [
{
"type": "pointer",
"id": "mouse",
"parameters": {
"pointerType": "mouse"
},
"actions": [
{"type": "pointerDown", "duration": 0, "button": button},
{"type": "pause", "duration": rng.gen_range(5..200)},
{"type": "pointerUp", "duration": 0, "button": button},
]
}
]});
self.service.post("/actions", &actions)?;
Ok(())
}
pub fn hover(&mut self, webelement:&WebElement, mouse_acceleration_modifier: f64) -> Result<(), PebblesError> {
let webelement_rect = webelement.rect()?;
let view_rect = self.viewport_rect()?;
if !webelement_rect.overlaps(&view_rect) {
raise!(PebblesError::NotInViewportError, "Can not Move to Element as its not in the viewport.")
}
let (point_x, point_y) = webelement_rect.world_point()?;
self.move_to(point_x - view_rect.x, point_y - view_rect.y, mouse_acceleration_modifier)?;
Ok(())
}
pub fn move_to(&mut self, x: i64, y: i64, acceleration_modifier: f64) -> Result<(), PebblesError> {
let viewport_rect = self.viewport_rect()?;
let wind_fluctuation: f64 = 1.0;
let gravitational_force: f64 = 2.0*(acceleration_modifier+1.0);
let mut maximum_step = 50.0;
let mut currentx = self.x;
let mut currenty = self.y;
let mut velocityx: f64 = 0.0;
let mut velocityy: f64 = 0.0;
let mut wind_forcex: f64 = 0.0;
let mut wind_forcey: f64 = 0.0;
let mut rng = rand::thread_rng();
let mut actions: Value = json!({"actions": [
{"type": "pointer", "id": "mouse", "parameters": {"pointerType": "mouse"}, "actions": []}
]});
let mut distance = (((x - currentx).pow(2) + (y - currenty).pow(2)) as f64).sqrt();
let damped_distance = (distance*0.05).clamp(50.0, 100.0);
let wind_damp_modifier = 1.73205080757;
let step_damp_modifier = 3.0;
while distance >= 1.0 {
let wind_magnitude = distance.min(wind_fluctuation);
if distance >= damped_distance {
wind_forcex =
wind_forcex / wind_damp_modifier + (rng.gen_range(0.0..2.0) - 1.0) * wind_magnitude / step_damp_modifier;
wind_forcey =
wind_forcey / wind_damp_modifier + (rng.gen_range(0.0..2.0) - 1.0) * wind_magnitude / step_damp_modifier;
} else {
wind_forcex /= wind_damp_modifier;
wind_forcey /= wind_damp_modifier;
if maximum_step > 12.0 {
maximum_step /= step_damp_modifier;
} else if maximum_step < 3.0 {
maximum_step = rng.gen_range(3.0..12.0);
} else {
maximum_step /= 1.5
}
}
velocityx += wind_forcex + gravitational_force * (x - currentx) as f64 / distance;
velocityy += wind_forcey + gravitational_force * (y - currenty) as f64 / distance;
let velocity_magnitude = (velocityx.powi(2) + velocityy.powi(2)).sqrt();
if velocity_magnitude > maximum_step {
let velocity_clip =
maximum_step / 2.0 + rng.gen_range(0.0..1.0) * maximum_step / 2.0;
velocityx = (velocityx / velocity_magnitude) * velocity_clip;
velocityy = (velocityy / velocity_magnitude) * velocity_clip;
}
if distance > maximum_step {
currentx += velocityx as i64;
currenty += velocityy as i64;
} else {
currentx = x;
currenty = y;
}
let new_action = vec![json!({
"type": "pointerMove",
"duration": 10,
"x": currentx.clamp(0, viewport_rect.width),
"y": currenty.clamp(0, viewport_rect.height)
})];
if let Some(actions_array) = actions["actions"][0]["actions"].as_array_mut() {
actions_array.extend(new_action);
}
distance = (((x - currentx).pow(2) + (y - currenty).pow(2)) as f64).sqrt();
}
self.service.post("/actions", &actions)?;
self.x = x;
self.y = y;
Ok(())
}
pub fn scroll(&self, distance: f64, acceleration_modifier: f64) -> Result<(), PebblesError> {
fn calculate_acceleration(distance: &f64, acceleration_modifier: f64) -> f64 {
let normalized_distance = (*distance - 3500.0) / 1000.0;
let acceleration = 1.2 + (normalized_distance.tanh() + 1.0) * 0.3;
let random_offset = rand::thread_rng().gen_range(-0.2..0.2);
(acceleration + random_offset).clamp(1.0, 4.0*(acceleration_modifier+0.25))
}
fn steps_to_target(target: i64, step_size: i64) -> i64 {
let remainder = target % step_size;
if remainder == 0 {
0
} else {
let lower: i64 = target - remainder;
let upper: i64 = target - remainder + step_size;
if (target - lower).abs() <= (target - upper).abs() {
lower / step_size
} else {
upper / step_size
}
}
}
let step_size: i64 = 114;
if distance.abs() < (step_size/2) as f64 {
return Ok(());
}
let duration_modifier = ((acceleration_modifier*7.0)+1.0) as i64;
let multiplier: i64 = if distance > 0.0 { 1 } else { -1 };
let steps: i64 = steps_to_target(distance.abs() as i64, step_size);
let delta: i64 = step_size * multiplier;
let acceleration: f64 = calculate_acceleration(&distance, acceleration_modifier);
let mut duration: i64 = 7000/duration_modifier;
let mut actions: Value = json!({"actions": [
{"type": "wheel", "id": "wheel", "actions": []}
]});
for _ in 0..steps {
duration = ((duration as f64 / acceleration) as i64).clamp(10, 200);
let new_actions = [
json!({
"type": "scroll",
"x": 0,
"y": 0,
"deltaX": 0,
"deltaY": delta,
"duration": duration,
"origin": "viewport",
}),
json!({
"type": "pause",
"duration": duration / duration_modifier,
}),
];
if let Some(actions_array) = actions["actions"][0]["actions"].as_array_mut() {
actions_array.extend(new_actions);
}
}
self.service.post("/actions", &actions)?;
Ok(())
}
fn viewport_rect(&self) -> Result<Rect, PebblesError> {
fn get_value_as_i64(json_value: &Value, key: &str) -> Result<i64, PebblesError> {
Ok(json_value.get(key)
.ok_or_else(|| error!(PebblesError::PhrasingError, "Invalid Javascript Response could not get Window Size from WebDriver.", json_value.clone()))?
.as_f64()
.ok_or_else(|| error!(PebblesError::PhrasingError, "Invalid Javascript Response could not get Window Size from WebDriver.", json_value.clone()))? as i64
)
}
let response = self
.service
.post(
"/execute/sync",
&json!({"script": "return {\"x\": window.pageXOffset, \"y\": window.pageYOffset, \"width\": window.innerWidth, \"height\": window.innerHeight};", "args": []})
)?.json::<Value>()?
.get("value")
.ok_or_else(|| error!(PebblesError::PhrasingError, "Could not get window size from javascript response."))?.clone();
let window_x: i64 = get_value_as_i64(&response, "x")?;
let window_y: i64 = get_value_as_i64(&response, "y")?;
let window_width: i64 = get_value_as_i64(&response, "width")?;
let window_height: i64 = get_value_as_i64(&response, "height")?;
Ok(
Rect{
x:window_x,
y:window_y,
width:window_width,
height:window_height,
}
)
}
}