diff options
author | George Roman <george.roman.99@gmail.com> | 2019-08-28 00:00:23 +0300 |
---|---|---|
committer | George Roman <george.roman.99@gmail.com> | 2019-08-31 10:14:40 +0300 |
commit | 32f7254883d573f0704fc885243f998e5f62c0b4 (patch) | |
tree | 200d6ebd35bbc3a93e06d9f3b2c5d57680af1bd6 /components | |
parent | b2a7fc9046b8488525adea93d8869b264280bfc8 (diff) | |
download | servo-32f7254883d573f0704fc885243f998e5f62c0b4.tar.gz servo-32f7254883d573f0704fc885243f998e5f62c0b4.zip |
Implement ElementClick wd command
Diffstat (limited to 'components')
-rw-r--r-- | components/atoms/static_atoms.txt | 3 | ||||
-rw-r--r-- | components/script/script_thread.rs | 8 | ||||
-rw-r--r-- | components/script/webdriver_handlers.rs | 166 | ||||
-rw-r--r-- | components/script_traits/webdriver_msg.rs | 1 | ||||
-rw-r--r-- | components/webdriver_server/actions.rs | 185 | ||||
-rw-r--r-- | components/webdriver_server/lib.rs | 67 |
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(¶meters.pointer_type), - )))); + .or_insert(InputSourceState::Pointer(PointerInputState::new( + ¶meters.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(), |