aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--components/compositing/constellation.rs8
-rw-r--r--components/msg/constellation_msg.rs3
-rw-r--r--components/msg/webdriver_msg.rs1
-rw-r--r--components/script/script_task.rs2
-rw-r--r--components/script/webdriver_handlers.rs21
-rw-r--r--components/webdriver_server/keys.rs186
-rw-r--r--components/webdriver_server/lib.rs33
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) => {