diff options
-rw-r--r-- | components/compositing/constellation.rs | 8 | ||||
-rw-r--r-- | components/msg/constellation_msg.rs | 3 | ||||
-rw-r--r-- | components/msg/webdriver_msg.rs | 1 | ||||
-rw-r--r-- | components/script/script_task.rs | 2 | ||||
-rw-r--r-- | components/script/webdriver_handlers.rs | 21 | ||||
-rw-r--r-- | components/webdriver_server/keys.rs | 186 | ||||
-rw-r--r-- | components/webdriver_server/lib.rs | 33 |
7 files changed, 252 insertions, 2 deletions
diff --git a/components/compositing/constellation.rs b/components/compositing/constellation.rs index b47ae01a0a6..92e6d768bdd 100644 --- a/components/compositing/constellation.rs +++ b/components/compositing/constellation.rs @@ -1048,6 +1048,14 @@ impl<LTF: LayoutTaskFactory, STF: ScriptTaskFactory> Constellation<LTF, STF> { let control_msg = ConstellationControlMsg::WebDriverScriptCommand(pipeline_id, cmd); pipeline.script_chan.send(control_msg).unwrap(); }, + WebDriverCommandMsg::SendKeys(pipeline_id, cmd) => { + let pipeline = self.pipeline(pipeline_id); + for (key, mods, state) in cmd { + let event = CompositorEvent::KeyEvent(key, state, mods); + pipeline.script_chan.send( + ConstellationControlMsg::SendEvent(pipeline.id, event)).unwrap(); + } + }, WebDriverCommandMsg::TakeScreenshot(pipeline_id, reply) => { let current_pipeline_id = self.root_frame_id.map(|frame_id| { let frame = self.frames.get(&frame_id).unwrap(); diff --git a/components/msg/constellation_msg.rs b/components/msg/constellation_msg.rs index 009016e1433..7487fede8c2 100644 --- a/components/msg/constellation_msg.rs +++ b/components/msg/constellation_msg.rs @@ -61,7 +61,7 @@ pub struct WindowSizeData { pub device_pixel_ratio: ScaleFactor<ViewportPx, DevicePixel, f32>, } -#[derive(PartialEq, Eq, Copy, Clone, Deserialize, Serialize)] +#[derive(PartialEq, Eq, Copy, Clone, Debug, Deserialize, Serialize)] pub enum KeyState { Pressed, Released, @@ -360,6 +360,7 @@ pub enum WebDriverCommandMsg { LoadUrl(PipelineId, LoadData, IpcSender<LoadStatus>), Refresh(PipelineId, IpcSender<LoadStatus>), ScriptCommand(PipelineId, WebDriverScriptCommand), + SendKeys(PipelineId, Vec<(Key, KeyModifiers, KeyState)>), TakeScreenshot(PipelineId, IpcSender<Option<Image>>), } diff --git a/components/msg/webdriver_msg.rs b/components/msg/webdriver_msg.rs index 63ac4aa7ffd..a0e6f83a57f 100644 --- a/components/msg/webdriver_msg.rs +++ b/components/msg/webdriver_msg.rs @@ -13,6 +13,7 @@ pub enum WebDriverScriptCommand { ExecuteAsyncScript(String, IpcSender<WebDriverJSResult>), FindElementCSS(String, IpcSender<Result<Option<String>, ()>>), FindElementsCSS(String, IpcSender<Result<Vec<String>, ()>>), + FocusElement(String, IpcSender<Result<(), ()>>), GetActiveElement(IpcSender<Option<String>>), GetElementTagName(String, IpcSender<Result<String, ()>>), GetElementText(String, IpcSender<Result<String, ()>>), diff --git a/components/script/script_task.rs b/components/script/script_task.rs index c3ddfdad6b3..d2fc0a93917 100644 --- a/components/script/script_task.rs +++ b/components/script/script_task.rs @@ -1086,6 +1086,8 @@ impl ScriptTask { webdriver_handlers::handle_find_element_css(&page, pipeline_id, selector, reply), WebDriverScriptCommand::FindElementsCSS(selector, reply) => webdriver_handlers::handle_find_elements_css(&page, pipeline_id, selector, reply), + WebDriverScriptCommand::FocusElement(element_id, reply) => + webdriver_handlers::handle_focus_element(&page, pipeline_id, element_id, reply), WebDriverScriptCommand::GetActiveElement(reply) => webdriver_handlers::handle_get_active_element(&page, pipeline_id, reply), WebDriverScriptCommand::GetElementTagName(node_id, reply) => diff --git a/components/script/webdriver_handlers.rs b/components/script/webdriver_handlers.rs index 8a2385ae0f4..cd49c8126f7 100644 --- a/components/script/webdriver_handlers.rs +++ b/components/script/webdriver_handlers.rs @@ -4,6 +4,7 @@ use dom::bindings::codegen::Bindings::DocumentBinding::DocumentMethods; use dom::bindings::codegen::Bindings::ElementBinding::ElementMethods; +use dom::bindings::codegen::Bindings::HTMLElementBinding::HTMLElementMethods; use dom::bindings::codegen::Bindings::HTMLIFrameElementBinding::HTMLIFrameElementMethods; use dom::bindings::codegen::Bindings::NodeBinding::NodeMethods; use dom::bindings::codegen::Bindings::NodeListBinding::NodeListMethods; @@ -11,6 +12,7 @@ use dom::bindings::conversions::{FromJSValConvertible, StringificationBehavior}; use dom::bindings::inheritance::Castable; use dom::bindings::js::Root; use dom::element::Element; +use dom::htmlelement::HTMLElement; use dom::htmliframeelement::HTMLIFrameElement; use dom::node::Node; use dom::window::ScriptHelpers; @@ -147,6 +149,25 @@ pub fn handle_find_elements_css(page: &Rc<Page>, }).unwrap(); } +pub fn handle_focus_element(page: &Rc<Page>, + pipeline: PipelineId, + element_id: String, + reply: IpcSender<Result<(), ()>>) { + reply.send(match find_node_by_unique_id(page, pipeline, element_id) { + Some(ref node) => { + match node.downcast::<HTMLElement>() { + Some(ref elem) => { + // Need a way to find if this actually succeeded + elem.Focus(); + Ok(()) + } + None => Err(()) + } + }, + None => Err(()) + }).unwrap(); +} + pub fn handle_get_active_element(page: &Rc<Page>, _pipeline: PipelineId, reply: IpcSender<Option<String>>) { diff --git a/components/webdriver_server/keys.rs b/components/webdriver_server/keys.rs new file mode 100644 index 00000000000..8d78270acea --- /dev/null +++ b/components/webdriver_server/keys.rs @@ -0,0 +1,186 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use msg::constellation_msg::{Key, KeyState, KeyModifiers, SHIFT}; + + +/// Takes a character and returns an Option containing a tuple of the +/// corresponding keycode and whether shift is required. This is +/// currently pretty much ascii-only and the webdriver spec isn't +/// entirely clear on how to deal with characters outside this +/// range. Returns None if no key corresponding to the character is +/// matched. +fn key_from_char(key_string: &char) -> Option<(Key, bool)> { + match *key_string { + ' ' => Some((Key::Space, false)), + '\'' => Some((Key::Apostrophe, true)), + '\"' => Some((Key::Apostrophe, false)), + '<' => Some((Key::Comma, true)), + ',' => Some((Key::Comma, false)), + '_' => Some((Key::Minus, true)), + '-' => Some((Key::Minus, false)), + '>' => Some((Key::Period, true)), + '.' => Some((Key::Period, false)), + '?' => Some((Key::Slash, true)), + '/' => Some((Key::Slash, false)), + '~' => Some((Key::GraveAccent, true)), + '`' => Some((Key::GraveAccent, false)), + ')' => Some((Key::Num0, true)), + '0' => Some((Key::Num0, false)), + '!' => Some((Key::Num1, true)), + '1' => Some((Key::Num1, false)), + '@' => Some((Key::Num2, true)), + '2' => Some((Key::Num2, false)), + '#' => Some((Key::Num3, true)), + '3' => Some((Key::Num3, false)), + '$' => Some((Key::Num4, true)), + '4' => Some((Key::Num4, false)), + '%' => Some((Key::Num5, true)), + '5' => Some((Key::Num5, false)), + '^' => Some((Key::Num6, true)), + '6' => Some((Key::Num6, false)), + '&' => Some((Key::Num7, true)), + '7' => Some((Key::Num7, false)), + '*' => Some((Key::Num8, true)), + '8' => Some((Key::Num8, false)), + '(' => Some((Key::Num9, true)), + '9' => Some((Key::Num9, false)), + ':' => Some((Key::Semicolon, true)), + ';' => Some((Key::Semicolon, false)), + '+' => Some((Key::Equal, true)), + '=' => Some((Key::Equal, false)), + 'A' => Some((Key::A, true)), + 'a' => Some((Key::A, false)), + 'B' => Some((Key::B, true)), + 'b' => Some((Key::B, false)), + 'C' => Some((Key::C, true)), + 'c' => Some((Key::C, false)), + 'D' => Some((Key::D, true)), + 'd' => Some((Key::D, false)), + 'E' => Some((Key::E, true)), + 'e' => Some((Key::E, false)), + 'F' => Some((Key::F, true)), + 'f' => Some((Key::F, false)), + 'G' => Some((Key::G, true)), + 'g' => Some((Key::G, false)), + 'H' => Some((Key::H, true)), + 'h' => Some((Key::H, false)), + 'I' => Some((Key::I, true)), + 'i' => Some((Key::I, false)), + 'J' => Some((Key::J, true)), + 'j' => Some((Key::J, false)), + 'K' => Some((Key::K, true)), + 'k' => Some((Key::K, false)), + 'L' => Some((Key::L, true)), + 'l' => Some((Key::L, false)), + 'M' => Some((Key::M, true)), + 'm' => Some((Key::M, false)), + 'N' => Some((Key::N, true)), + 'n' => Some((Key::N, false)), + 'O' => Some((Key::O, true)), + 'o' => Some((Key::O, false)), + 'P' => Some((Key::P, true)), + 'p' => Some((Key::P, false)), + 'Q' => Some((Key::Q, true)), + 'q' => Some((Key::Q, false)), + 'R' => Some((Key::R, true)), + 'r' => Some((Key::R, false)), + 'S' => Some((Key::S, true)), + 's' => Some((Key::S, false)), + 'T' => Some((Key::T, true)), + 't' => Some((Key::T, false)), + 'U' => Some((Key::U, true)), + 'u' => Some((Key::U, false)), + 'V' => Some((Key::V, true)), + 'v' => Some((Key::V, false)), + 'W' => Some((Key::W, true)), + 'w' => Some((Key::W, false)), + 'X' => Some((Key::X, true)), + 'x' => Some((Key::X, false)), + 'Y' => Some((Key::Y, true)), + 'y' => Some((Key::Y, false)), + 'Z' => Some((Key::Z, true)), + 'z' => Some((Key::Z, false)), + '{' => Some((Key::LeftBracket, true)), + '[' => Some((Key::LeftBracket, false)), + '|' => Some((Key::Backslash, true)), + '\\' => Some((Key::Backslash, false)), + '}' => Some((Key::RightBracket, true)), + ']' => Some((Key::RightBracket, false)), + '\u{E000}' => None, + '\u{E001}' => None, + '\u{E002}' => None, + '\u{E003}' => Some((Key::Backspace, false)), + '\u{E004}' => Some((Key::Tab, false)), + '\u{E005}' => None, + '\u{E006}' => Some((Key::Enter, false)), // This is supposed to be the Return key + '\u{E007}' => Some((Key::Enter, false)), + '\u{E008}' => Some((Key::LeftShift, false)), + '\u{E009}' => Some((Key::LeftShift, false)), + '\u{E00A}' => Some((Key::LeftAlt, false)), + '\u{E00B}' => Some((Key::Pause, false)), + '\u{E00C}' => Some((Key::Escape, false)), + '\u{E00D}' => Some((Key::Space, false)), + '\u{E00E}' => Some((Key::PageUp, false)), + '\u{E00F}' => Some((Key::PageDown, false)), + '\u{E010}' => Some((Key::End, false)), + '\u{E011}' => Some((Key::Home, false)), + '\u{E012}' => Some((Key::Right, false)), + '\u{E013}' => Some((Key::Left, false)), + '\u{E014}' => Some((Key::Down, false)), + '\u{E015}' => Some((Key::Up, false)), + '\u{E016}' => Some((Key::Insert, false)), + '\u{E017}' => Some((Key::Delete, false)), + '\u{E018}' => Some((Key::Semicolon, false)), + '\u{E019}' => Some((Key::Equal, false)), + '\u{E01A}' => Some((Key::Kp0, false)), + '\u{E01B}' => Some((Key::Kp1, false)), + '\u{E01C}' => Some((Key::Kp2, false)), + '\u{E01D}' => Some((Key::Kp3, false)), + '\u{E01E}' => Some((Key::Kp4, false)), + '\u{E01F}' => Some((Key::Kp5, false)), + '\u{E020}' => Some((Key::Kp6, false)), + '\u{E021}' => Some((Key::Kp7, false)), + '\u{E022}' => Some((Key::Kp8, false)), + '\u{E023}' => Some((Key::Kp9, false)), + '\u{E024}' => Some((Key::KpMultiply, false)), + '\u{E025}' => Some((Key::KpAdd, false)), + '\u{E026}' => Some((Key::KpEnter, false)), + '\u{E027}' => Some((Key::KpSubtract, false)), + '\u{E028}' => Some((Key::KpDecimal, false)), + '\u{E029}' => Some((Key::KpDivide, false)), + '\u{E031}' => Some((Key::F1, false)), + '\u{E032}' => Some((Key::F2, false)), + '\u{E033}' => Some((Key::F3, false)), + '\u{E034}' => Some((Key::F4, false)), + '\u{E035}' => Some((Key::F5, false)), + '\u{E036}' => Some((Key::F6, false)), + '\u{E037}' => Some((Key::F7, false)), + '\u{E038}' => Some((Key::F8, false)), + '\u{E039}' => Some((Key::F9, false)), + '\u{E03A}' => Some((Key::F10, false)), + '\u{E03B}' => Some((Key::F11, false)), + '\u{E03C}' => Some((Key::F12, false)), + '\u{E03D}' => None, + '\u{E040}' => None, + _ => None + } +} + +pub fn keycodes_to_keys(key_codes: &[char]) -> Result<Vec<(Key, KeyModifiers, KeyState)>, String> { + let mut rv = vec![]; + + for char_code in key_codes.iter() { + let (key, with_shift) = try!( + key_from_char(char_code).ok_or(format!("Unsupported character code {}", char_code))); + let modifiers = if with_shift { + SHIFT + } else { + KeyModifiers::empty() + }; + rv.push((key, modifiers, KeyState::Pressed)); + rv.push((key, modifiers, KeyState::Released)); + }; + Ok(rv) +} diff --git a/components/webdriver_server/lib.rs b/components/webdriver_server/lib.rs index bb3019582d0..497e543b28d 100644 --- a/components/webdriver_server/lib.rs +++ b/components/webdriver_server/lib.rs @@ -21,9 +21,12 @@ extern crate util; extern crate uuid; extern crate webdriver; +mod keys; + use hyper::method::Method::{self, Post}; use image::{DynamicImage, ImageFormat, RgbImage}; use ipc_channel::ipc::{self, IpcReceiver, IpcSender}; +use keys::keycodes_to_keys; use msg::constellation_msg::Msg as ConstellationMsg; use msg::constellation_msg::{ConstellationChan, FrameId, LoadData, PipelineId}; use msg::constellation_msg::{NavigationDirection, PixelFormat, WebDriverCommandMsg}; @@ -40,7 +43,7 @@ use util::prefs::{get_pref, reset_all_prefs, reset_pref, set_pref, PrefValue}; use util::task::spawn_named; use uuid::Uuid; use webdriver::command::{GetParameters, JavascriptCommandParameters, LocatorParameters}; -use webdriver::command::{Parameters, SwitchToFrameParameters, TimeoutsParameters}; +use webdriver::command::{Parameters, SendKeysParameters, SwitchToFrameParameters, TimeoutsParameters}; use webdriver::command::{WebDriverCommand, WebDriverExtensionCommand, WebDriverMessage}; use webdriver::common::{LocatorStrategy, WebElement}; use webdriver::error::{ErrorStatus, WebDriverError, WebDriverResult}; @@ -204,6 +207,7 @@ impl WebDriverSession { } } + impl Handler { pub fn new(constellation_chan: ConstellationChan) -> Handler { Handler { @@ -595,6 +599,31 @@ impl Handler { } } + fn handle_element_send_keys(&self, + element: &WebElement, + keys: &SendKeysParameters) -> WebDriverResult<WebDriverResponse> { + let pipeline_id = try!(self.frame_pipeline()); + + let ConstellationChan(ref const_chan) = self.constellation_chan; + let (sender, receiver) = ipc::channel().unwrap(); + + let cmd = WebDriverScriptCommand::FocusElement(element.id.clone(), sender); + let cmd_msg = WebDriverCommandMsg::ScriptCommand(pipeline_id, cmd); + const_chan.send(ConstellationMsg::WebDriverCommand(cmd_msg)).unwrap(); + + // TODO: distinguish the not found and not focusable cases + try!(receiver.recv().unwrap().or_else(|_| Err(WebDriverError::new( + ErrorStatus::StaleElementReference, "Element not found or not focusable")))); + + let keys = try!(keycodes_to_keys(&keys.value).or_else(|_| + Err(WebDriverError::new(ErrorStatus::UnsupportedOperation, "Failed to convert keycodes")))); + + let cmd_msg = WebDriverCommandMsg::SendKeys(pipeline_id, keys); + const_chan.send(ConstellationMsg::WebDriverCommand(cmd_msg)).unwrap(); + + Ok(WebDriverResponse::Void) + } + fn handle_take_screenshot(&self) -> WebDriverResult<WebDriverResponse> { let mut img = None; let pipeline_id = try!(self.root_pipeline()); @@ -705,6 +734,8 @@ impl WebDriverHandler<ServoExtensionRoute> for Handler { WebDriverCommand::GetElementTagName(ref element) => self.handle_element_tag_name(element), WebDriverCommand::ExecuteScript(ref x) => self.handle_execute_script(x), WebDriverCommand::ExecuteAsyncScript(ref x) => self.handle_execute_async_script(x), + WebDriverCommand::ElementSendKeys(ref element, ref keys) => + self.handle_element_send_keys(element, keys), WebDriverCommand::SetTimeouts(ref x) => self.handle_set_timeouts(x), WebDriverCommand::TakeScreenshot => self.handle_take_screenshot(), WebDriverCommand::Extension(ref extension) => { |