aboutsummaryrefslogtreecommitdiffstats
path: root/components
diff options
context:
space:
mode:
authorGeorge Roman <george.roman.99@gmail.com>2019-08-28 00:00:23 +0300
committerGeorge Roman <george.roman.99@gmail.com>2019-08-31 10:14:40 +0300
commit32f7254883d573f0704fc885243f998e5f62c0b4 (patch)
tree200d6ebd35bbc3a93e06d9f3b2c5d57680af1bd6 /components
parentb2a7fc9046b8488525adea93d8869b264280bfc8 (diff)
downloadservo-32f7254883d573f0704fc885243f998e5f62c0b4.tar.gz
servo-32f7254883d573f0704fc885243f998e5f62c0b4.zip
Implement ElementClick wd command
Diffstat (limited to 'components')
-rw-r--r--components/atoms/static_atoms.txt3
-rw-r--r--components/script/script_thread.rs8
-rw-r--r--components/script/webdriver_handlers.rs166
-rw-r--r--components/script_traits/webdriver_msg.rs1
-rw-r--r--components/webdriver_server/actions.rs185
-rw-r--r--components/webdriver_server/lib.rs67
6 files changed, 297 insertions, 133 deletions
diff --git a/components/atoms/static_atoms.txt b/components/atoms/static_atoms.txt
index 7b3d2fc7f88..c8773667e74 100644
--- a/components/atoms/static_atoms.txt
+++ b/components/atoms/static_atoms.txt
@@ -59,7 +59,10 @@ message
message
monospace
month
+mousedown
+mousemove
mouseover
+mouseup
negotiationneeded
none
number
diff --git a/components/script/script_thread.rs b/components/script/script_thread.rs
index eaffa1d6ef6..4cda69cdced 100644
--- a/components/script/script_thread.rs
+++ b/components/script/script_thread.rs
@@ -2159,6 +2159,14 @@ impl ScriptThread {
reply,
)
},
+ WebDriverScriptCommand::ElementClick(element_id, reply) => {
+ webdriver_handlers::handle_element_click(
+ &*documents,
+ pipeline_id,
+ element_id,
+ reply,
+ )
+ },
WebDriverScriptCommand::GetActiveElement(reply) => {
webdriver_handlers::handle_get_active_element(&*documents, pipeline_id, reply)
},
diff --git a/components/script/webdriver_handlers.rs b/components/script/webdriver_handlers.rs
index fdb688b1f60..cea4cca5c71 100644
--- a/components/script/webdriver_handlers.rs
+++ b/components/script/webdriver_handlers.rs
@@ -9,7 +9,8 @@ use crate::dom::bindings::codegen::Bindings::ElementBinding::ElementMethods;
use crate::dom::bindings::codegen::Bindings::HTMLElementBinding::HTMLElementMethods;
use crate::dom::bindings::codegen::Bindings::HTMLInputElementBinding::HTMLInputElementMethods;
use crate::dom::bindings::codegen::Bindings::HTMLOptionElementBinding::HTMLOptionElementMethods;
-use crate::dom::bindings::codegen::Bindings::NodeBinding::NodeMethods;
+use crate::dom::bindings::codegen::Bindings::HTMLSelectElementBinding::HTMLSelectElementMethods;
+use crate::dom::bindings::codegen::Bindings::NodeBinding::{GetRootNodeOptions, NodeMethods};
use crate::dom::bindings::codegen::Bindings::WindowBinding::WindowMethods;
use crate::dom::bindings::codegen::Bindings::XMLSerializerBinding::XMLSerializerMethods;
use crate::dom::bindings::conversions::{
@@ -24,11 +25,14 @@ use crate::dom::bindings::reflector::DomObject;
use crate::dom::bindings::root::DomRoot;
use crate::dom::bindings::str::DOMString;
use crate::dom::element::Element;
+use crate::dom::eventtarget::EventTarget;
use crate::dom::globalscope::GlobalScope;
+use crate::dom::htmldatalistelement::HTMLDataListElement;
use crate::dom::htmlelement::HTMLElement;
use crate::dom::htmliframeelement::HTMLIFrameElement;
-use crate::dom::htmlinputelement::HTMLInputElement;
+use crate::dom::htmlinputelement::{HTMLInputElement, InputType};
use crate::dom::htmloptionelement::HTMLOptionElement;
+use crate::dom::htmlselectelement::HTMLSelectElement;
use crate::dom::node::{window_from_node, Node, ShadowIncluding};
use crate::dom::nodelist::NodeList;
use crate::dom::window::Window;
@@ -363,35 +367,38 @@ pub fn handle_get_browsing_context_id(
// https://w3c.github.io/webdriver/#dfn-center-point
fn get_element_in_view_center_point(element: &Element) -> Option<Point2D<i64>> {
- element
- .GetClientRects()
- .iter()
- // Step 1
- .next()
- .map(|rectangle| {
- let x = rectangle.X().round() as i64;
- let y = rectangle.Y().round() as i64;
- let width = rectangle.Width().round() as i64;
- let height = rectangle.Height().round() as i64;
-
- let window = window_from_node(element.upcast::<Node>());
- let document = window.Document();
- let document_element = document.upcast::<Node>().downcast::<Element>().unwrap();
- let clientWidth = document_element.ClientWidth() as i64;
- let clientHeight = document_element.ClientHeight() as i64;
-
- // Steps 2 - 5
- let left = cmp::max(0, cmp::min(x, x + width));
- let right = cmp::min(clientWidth, cmp::max(x, x + width));
- let top = cmp::max(0, cmp::min(y, y + height));
- let bottom = cmp::min(clientHeight, cmp::max(y, y + height));
-
- // Steps 6 - 7
- let x = (left + right) / 2;
- let y = (top + bottom) / 2;
-
- // Step 8
- Point2D::new(x, y)
+ window_from_node(element.upcast::<Node>())
+ .Document()
+ .GetBody()
+ .map(DomRoot::upcast::<Element>)
+ .and_then(|body| {
+ element
+ .GetClientRects()
+ .iter()
+ // Step 1
+ .next()
+ .map(|rectangle| {
+ let x = rectangle.X().round() as i64;
+ let y = rectangle.Y().round() as i64;
+ let width = rectangle.Width().round() as i64;
+ let height = rectangle.Height().round() as i64;
+
+ let clientWidth = body.ClientWidth() as i64;
+ let clientHeight = body.ClientHeight() as i64;
+
+ // Steps 2 - 5
+ let left = cmp::max(0, cmp::min(x, x + width));
+ let right = cmp::min(clientWidth, cmp::max(x, x + width));
+ let top = cmp::max(0, cmp::min(y, y + height));
+ let bottom = cmp::min(clientHeight, cmp::max(y, y + height));
+
+ // Steps 6 - 7
+ let x = (left + right) / 2;
+ let y = (top + bottom) / 2;
+
+ // Step 8
+ Point2D::new(x, y)
+ })
})
}
@@ -1050,6 +1057,103 @@ pub fn handle_get_url(documents: &Documents, pipeline: PipelineId, reply: IpcSen
.unwrap();
}
+// https://w3c.github.io/webdriver/#element-click
+pub fn handle_element_click(
+ documents: &Documents,
+ pipeline: PipelineId,
+ element_id: String,
+ reply: IpcSender<Result<Option<String>, ErrorStatus>>,
+) {
+ reply
+ .send(
+ // Step 3
+ find_node_by_unique_id(documents, pipeline, element_id).and_then(|node| {
+ // Step 4
+ if let Some(input_element) = node.downcast::<HTMLInputElement>() {
+ if input_element.input_type() == InputType::File {
+ return Err(ErrorStatus::InvalidArgument);
+ }
+ }
+
+ // Step 5
+ // TODO: scroll into view
+
+ // Step 6
+ // TODO: return error if still not in view
+
+ // Step 7
+ // TODO: return error if obscured
+
+ // Step 8
+ match node.downcast::<HTMLOptionElement>() {
+ Some(option_element) => {
+ // https://w3c.github.io/webdriver/#dfn-container
+ let root_node = node.GetRootNode(&GetRootNodeOptions::empty());
+ let datalist_parent = node
+ .preceding_nodes(&root_node)
+ .find(|preceding| preceding.is::<HTMLDataListElement>());
+ let select_parent = node
+ .preceding_nodes(&root_node)
+ .find(|preceding| preceding.is::<HTMLSelectElement>());
+
+ // Step 8.1
+ let parent_node = match datalist_parent {
+ Some(datalist_parent) => datalist_parent,
+ None => match select_parent {
+ Some(select_parent) => select_parent,
+ None => return Err(ErrorStatus::UnknownError),
+ },
+ };
+
+ // Steps 8.2 - 8.4
+ let event_target = parent_node.upcast::<EventTarget>();
+ event_target.fire_event(atom!("mouseover"));
+ event_target.fire_event(atom!("mousemove"));
+ event_target.fire_event(atom!("mousedown"));
+
+ // Step 8.5
+ match parent_node.downcast::<HTMLElement>() {
+ Some(html_element) => html_element.Focus(),
+ None => return Err(ErrorStatus::UnknownError),
+ }
+
+ // Step 8.6
+ if !option_element.Disabled() {
+ // Step 8.6.1
+ event_target.fire_event(atom!("input"));
+
+ // Steps 8.6.2
+ let previous_selectedness = option_element.Selected();
+
+ // Step 8.6.3
+ match parent_node.downcast::<HTMLSelectElement>() {
+ Some(select_element) => {
+ if select_element.Multiple() {
+ option_element.SetSelected(!option_element.Selected());
+ }
+ },
+ None => option_element.SetSelected(true),
+ }
+
+ // Step 8.6.4
+ if !previous_selectedness {
+ event_target.fire_event(atom!("change"));
+ }
+ }
+
+ // Steps 8.7 - 8.8
+ event_target.fire_event(atom!("mouseup"));
+ event_target.fire_event(atom!("click"));
+
+ Ok(None)
+ },
+ None => Ok(Some(node.unique_id())),
+ }
+ }),
+ )
+ .unwrap();
+}
+
pub fn handle_is_enabled(
documents: &Documents,
pipeline: PipelineId,
diff --git a/components/script_traits/webdriver_msg.rs b/components/script_traits/webdriver_msg.rs
index 8a691d75eb5..2291cf64356 100644
--- a/components/script_traits/webdriver_msg.rs
+++ b/components/script_traits/webdriver_msg.rs
@@ -58,6 +58,7 @@ pub enum WebDriverScriptCommand {
),
FindElementElementsTagName(String, String, IpcSender<Result<Vec<String>, ErrorStatus>>),
FocusElement(String, IpcSender<Result<(), ErrorStatus>>),
+ ElementClick(String, IpcSender<Result<Option<String>, ErrorStatus>>),
GetActiveElement(IpcSender<Option<String>>),
GetCookie(String, IpcSender<Vec<Serde<Cookie<'static>>>>),
GetCookies(IpcSender<Vec<Serde<Cookie<'static>>>>),
diff --git a/components/webdriver_server/actions.rs b/components/webdriver_server/actions.rs
index 28255e44cd9..e04c40d54c8 100644
--- a/components/webdriver_server/actions.rs
+++ b/components/webdriver_server/actions.rs
@@ -3,14 +3,12 @@
* 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};
@@ -28,7 +26,7 @@ static POINTERMOVE_INTERVAL: u64 = 17;
pub(crate) enum InputSourceState {
Null,
Key(KeyInputState),
- Pointer(Arc<Mutex<PointerInputState>>),
+ Pointer(PointerInputState),
}
// https://w3c.github.io/webdriver/#dfn-pointer-input-source
@@ -82,73 +80,6 @@ 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)
@@ -235,9 +166,9 @@ impl Handler {
.unwrap()
.input_state_table
.entry(source_id.to_string())
- .or_insert(InputSourceState::Pointer(Arc::new(Mutex::new(
- PointerInputState::new(&parameters.pointer_type),
- ))));
+ .or_insert(InputSourceState::Pointer(PointerInputState::new(
+ &parameters.pointer_type,
+ )));
match action {
PointerAction::Cancel => (),
PointerAction::Down(action) => {
@@ -319,13 +250,17 @@ impl Handler {
}
// https://w3c.github.io/webdriver/#dfn-dispatch-a-pointerdown-action
- fn dispatch_pointerdown_action(&mut self, source_id: &str, action: &PointerDownAction) {
+ pub(crate) fn dispatch_pointerdown_action(
+ &mut self,
+ source_id: &str,
+ action: &PointerDownAction,
+ ) {
let session = self.session.as_mut().unwrap();
- let mut pointer_input_state = match session.input_state_table.get(source_id).unwrap() {
+ let pointer_input_state = match session.input_state_table.get_mut(source_id).unwrap() {
InputSourceState::Null => unreachable!(),
InputSourceState::Key(_) => unreachable!(),
- InputSourceState::Pointer(pointer_input_state) => pointer_input_state.lock().unwrap(),
+ InputSourceState::Pointer(pointer_input_state) => pointer_input_state,
};
if pointer_input_state.pressed.contains(&action.button) {
@@ -365,13 +300,13 @@ impl Handler {
}
// https://w3c.github.io/webdriver/#dfn-dispatch-a-pointerup-action
- fn dispatch_pointerup_action(&mut self, source_id: &str, action: &PointerUpAction) {
+ pub(crate) fn dispatch_pointerup_action(&mut self, source_id: &str, action: &PointerUpAction) {
let session = self.session.as_mut().unwrap();
- let mut pointer_input_state = match session.input_state_table.get(source_id).unwrap() {
+ let pointer_input_state = match session.input_state_table.get_mut(source_id).unwrap() {
InputSourceState::Null => unreachable!(),
InputSourceState::Key(_) => unreachable!(),
- InputSourceState::Pointer(pointer_input_state) => pointer_input_state.lock().unwrap(),
+ InputSourceState::Pointer(pointer_input_state) => pointer_input_state,
};
if !pointer_input_state.pressed.contains(&action.button) {
@@ -411,7 +346,7 @@ impl Handler {
}
// https://w3c.github.io/webdriver/#dfn-dispatch-a-pointermove-action
- fn dispatch_pointermove_action(
+ pub(crate) fn dispatch_pointermove_action(
&mut self,
source_id: &str,
action: &PointerMoveAction,
@@ -435,7 +370,6 @@ impl Handler {
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)
},
};
@@ -488,12 +422,30 @@ impl Handler {
thread::sleep(Duration::from_millis(POINTERMOVE_INTERVAL));
}
+ // Step 11
+ self.perform_pointer_move(source_id, duration, start_x, start_y, x, y, tick_start);
+
+ // Step 12
+ Ok(())
+ }
+
+ // https://w3c.github.io/webdriver/#dfn-perform-a-pointer-move
+ fn perform_pointer_move(
+ &mut self,
+ source_id: &str,
+ duration: u64,
+ start_x: i64,
+ start_y: i64,
+ target_x: i64,
+ target_y: i64,
+ tick_start: Instant,
+ ) {
let pointer_input_state = match self
.session
- .as_ref()
+ .as_mut()
.unwrap()
.input_state_table
- .get(source_id)
+ .get_mut(source_id)
.unwrap()
{
InputSourceState::Null => unreachable!(),
@@ -501,24 +453,57 @@ impl Handler {
InputSourceState::Pointer(pointer_input_state) => pointer_input_state,
};
- let constellation_chan = self.constellation_chan.clone();
- let pointer_input_state = pointer_input_state.clone();
+ 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);
+ self.constellation_chan
+ .send(ConstellationMsg::WebDriverCommand(cmd_msg))
+ .unwrap();
+ // Step 7.3
+ pointer_input_state.x = x;
+ pointer_input_state.y = y;
+ }
- // Step 11
- thread::spawn(move || {
- perform_pointer_move(
- constellation_chan,
- pointer_input_state,
- duration,
- start_x,
- start_y,
- x,
- y,
- tick_start,
- );
- });
+ // Step 8
+ if last {
+ return;
+ }
- // Step 12
- Ok(())
+ // Step 9
+ thread::sleep(Duration::from_millis(POINTERMOVE_INTERVAL));
+ }
}
}
diff --git a/components/webdriver_server/lib.rs b/components/webdriver_server/lib.rs
index 431ac5eee9e..c6813488df7 100644
--- a/components/webdriver_server/lib.rs
+++ b/components/webdriver_server/lib.rs
@@ -18,7 +18,7 @@ extern crate serde_json;
mod actions;
mod capabilities;
-use crate::actions::InputSourceState;
+use crate::actions::{InputSourceState, PointerInputState};
use base64;
use capabilities::ServoCapabilities;
use crossbeam_channel::{after, unbounded, Receiver, Sender};
@@ -49,7 +49,10 @@ use std::thread;
use std::time::Duration;
use style_traits::CSSPixel;
use uuid::Uuid;
-use webdriver::actions::ActionSequence;
+use webdriver::actions::{
+ ActionSequence, PointerDownAction, PointerMoveAction, PointerOrigin, PointerType,
+ PointerUpAction,
+};
use webdriver::capabilities::{Capabilities, CapabilitiesMatching};
use webdriver::command::{ActionsParameters, SwitchToWindowParameters};
use webdriver::command::{
@@ -1474,6 +1477,65 @@ impl Handler {
Ok(WebDriverResponse::Void)
}
+ // https://w3c.github.io/webdriver/#element-click
+ fn handle_element_click(&mut self, element: &WebElement) -> WebDriverResult<WebDriverResponse> {
+ let (sender, receiver) = ipc::channel().unwrap();
+
+ // Steps 1 - 7
+ let command = WebDriverScriptCommand::ElementClick(element.to_string(), sender);
+ self.browsing_context_script_command(command)?;
+
+ match receiver.recv().unwrap() {
+ Ok(element_id) => match element_id {
+ Some(element_id) => {
+ let id = Uuid::new_v4().to_string();
+
+ // Step 8.1
+ self.session_mut()?.input_state_table.insert(
+ id.clone(),
+ InputSourceState::Pointer(PointerInputState::new(&PointerType::Mouse)),
+ );
+
+ // Steps 8.3 - 8.6
+ let pointer_move_action = PointerMoveAction {
+ duration: None,
+ origin: PointerOrigin::Element(WebElement(element_id)),
+ x: Some(0),
+ y: Some(0),
+ };
+
+ // Steps 8.7 - 8.8
+ let pointer_down_action = PointerDownAction { button: 1 };
+
+ // Steps 8.9 - 8.10
+ let pointer_up_action = PointerUpAction { button: 1 };
+
+ // Step 8.11
+ if let Err(error) =
+ self.dispatch_pointermove_action(&id, &pointer_move_action, 0)
+ {
+ return Err(WebDriverError::new(error, ""));
+ }
+
+ // Steps 8.12
+ self.dispatch_pointerdown_action(&id, &pointer_down_action);
+
+ // Steps 8.13
+ self.dispatch_pointerup_action(&id, &pointer_up_action);
+
+ // Step 8.14
+ self.session_mut()?.input_state_table.remove(&id);
+
+ // Step 13
+ Ok(WebDriverResponse::Void)
+ },
+ // Step 13
+ None => Ok(WebDriverResponse::Void),
+ },
+ Err(error) => Err(WebDriverError::new(error, "")),
+ }
+ }
+
fn take_screenshot(&self, rect: Option<Rect<f32, CSSPixel>>) -> WebDriverResult<String> {
let mut img = None;
@@ -1694,6 +1756,7 @@ impl WebDriverHandler<ServoExtensionRoute> for Handler {
WebDriverCommand::ElementSendKeys(ref element, ref keys) => {
self.handle_element_send_keys(element, keys)
},
+ WebDriverCommand::ElementClick(ref element) => self.handle_element_click(element),
WebDriverCommand::DismissAlert => self.handle_dismiss_alert(),
WebDriverCommand::DeleteCookies => self.handle_delete_cookies(),
WebDriverCommand::GetTimeouts => self.handle_get_timeouts(),