diff --git a/Cargo.lock b/Cargo.lock index 2be64eb8..f93de86a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,9 +4,11 @@ version = "0.3.0" dependencies = [ "argparse 0.2.0 (git+https://github.com/tailhook/rust-argparse.git)", "env_logger 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "hyper 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", "mozprofile 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "mozrunner 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "regex 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)", "rustc-serialize 0.3.16 (registry+https://github.com/rust-lang/crates.io-index)", "uuid 0.1.17 (registry+https://github.com/rust-lang/crates.io-index)", "webdriver 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", @@ -34,11 +36,18 @@ name = "argparse" version = "0.2.0" source = "git+https://github.com/tailhook/rust-argparse.git#e156caad3d8b557aae78d0613757438aed02955a" +[[package]] +name = "bitflags" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "cookie" version = "0.1.21" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ + "openssl 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)", + "rustc-serialize 0.3.16 (registry+https://github.com/rust-lang/crates.io-index)", "time 0.1.32 (registry+https://github.com/rust-lang/crates.io-index)", "url 0.2.37 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -52,6 +61,15 @@ dependencies = [ "regex 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "gcc" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "advapi32-sys 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "hpack" version = "0.2.0" @@ -76,6 +94,7 @@ dependencies = [ "log 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", "mime 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "num_cpus 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", + "openssl 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)", "rustc-serialize 0.3.16 (registry+https://github.com/rust-lang/crates.io-index)", "solicit 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)", "time 0.1.32 (registry+https://github.com/rust-lang/crates.io-index)", @@ -99,11 +118,24 @@ name = "language-tags" version = "0.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "lazy_static" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "libc" version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "libressl-pnacl-sys" +version = "2.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "pnacl-build-helper 1.4.10 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "log" version = "0.3.2" @@ -159,6 +191,41 @@ dependencies = [ "libc 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "openssl" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bitflags 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", + "openssl-sys 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "openssl-sys" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "gcc 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", + "libressl-pnacl-sys 2.1.6 (registry+https://github.com/rust-lang/crates.io-index)", + "pkg-config 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "pkg-config" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "pnacl-build-helper" +version = "1.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "tempdir 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "rand" version = "0.3.11" diff --git a/Cargo.toml b/Cargo.toml index d552d7e2..df84bfa6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,12 +8,14 @@ license = "MPL-2.0" [dependencies] env_logger = "0.3.1" +hyper = "0.6.13" log = "0.3.1" +mozprofile = "0.1.0" +mozrunner = "0.1.0" rustc-serialize = "0.3.14" uuid = "0.1.17" -mozrunner = "0.1.0" -mozprofile = "0.1.0" webdriver = "0.3.1" +regex="0.1.41" [dependencies.argparse] git = "https://github.com/tailhook/rust-argparse.git" diff --git a/src/main.rs b/src/main.rs index 825e16a5..b2b9b4c9 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,10 +1,12 @@ #[macro_use] extern crate log; -extern crate rustc_serialize; extern crate argparse; extern crate env_logger; +extern crate hyper; extern crate mozprofile; extern crate mozrunner; +extern crate regex; +extern crate rustc_serialize; #[macro_use] extern crate webdriver; @@ -17,7 +19,7 @@ use std::path::Path; use argparse::{ArgumentParser, StoreTrue, Store}; use webdriver::server::start; -use marionette::{MarionetteHandler, BrowserLauncher, MarionetteSettings}; +use marionette::{MarionetteHandler, BrowserLauncher, MarionetteSettings, extension_routes}; macro_rules! try_opt { ($expr:expr, $err_type:expr, $err_msg:expr) => ({ @@ -100,5 +102,5 @@ fn main() { launcher); //TODO: what if binary isn't a valid path? - start(addr, MarionetteHandler::new(settings), vec![]); + start(addr, MarionetteHandler::new(settings), extension_routes()); } diff --git a/src/marionette.rs b/src/marionette.rs index 36d49032..332859c4 100644 --- a/src/marionette.rs +++ b/src/marionette.rs @@ -10,11 +10,13 @@ use std::net::TcpStream; use std::path::PathBuf; use std::sync::Mutex; use std::thread::sleep_ms; - +use hyper::method::Method; +use regex::Captures; use mozrunner::runner::{Runner, FirefoxRunner}; use mozprofile::preferences::{PrefValue}; -use webdriver::command::{WebDriverMessage}; +use webdriver::command::{WebDriverCommand, WebDriverMessage, Parameters, + WebDriverExtensionCommand}; use webdriver::command::WebDriverCommand::{ NewSession, DeleteSession, Get, GetCurrentUrl, GoBack, GoForward, Refresh, GetTitle, GetWindowHandle, @@ -41,6 +43,113 @@ use webdriver::common::{ use webdriver::error::{ WebDriverResult, WebDriverError, ErrorStatus}; use webdriver::server::{WebDriverHandler, Session}; +use webdriver::httpapi::{WebDriverExtensionRoute}; + +pub fn extension_routes() -> Vec<(Method, &'static str, GeckoExtensionRoute)> { + return vec![(Method::Get, "/session/{sessionId}/moz/context", GeckoExtensionRoute::GetContext), + (Method::Post, "/session/{sessionId}/moz/context", GeckoExtensionRoute::SetContext)] +} + +#[derive(Clone, Copy, PartialEq)] +pub enum GeckoExtensionRoute { + GetContext, + SetContext +} + +impl WebDriverExtensionRoute for GeckoExtensionRoute { + type Command = GeckoExtensionCommand; + + fn command(&self, + _captures: &Captures, + body_data: &Json) -> WebDriverResult> { + let command = match self { + &GeckoExtensionRoute::GetContext => { + GeckoExtensionCommand::GetContext + } + &GeckoExtensionRoute::SetContext => { + let parameters: GeckoContextParameters = try!(Parameters::from_json(&body_data)); + GeckoExtensionCommand::SetContext(parameters) + } + }; + Ok(WebDriverCommand::Extension(command)) + } +} + +#[derive(Clone, PartialEq)] +pub enum GeckoExtensionCommand { + GetContext, + SetContext(GeckoContextParameters), +} + +impl WebDriverExtensionCommand for GeckoExtensionCommand { + fn parameters_json(&self) -> Option { + match self { + &GeckoExtensionCommand::GetContext => None, + &GeckoExtensionCommand::SetContext(ref x) => Some(x.to_json()), + } + } +} + +#[derive(Clone, Debug, PartialEq)] +enum GeckoContext { + Content, + Chrome +} + +impl ToJson for GeckoContext { + fn to_json(&self) -> Json { + match self { + &GeckoContext::Content => Json::String("content".to_owned()), + &GeckoContext::Chrome => Json::String("chrome".to_owned()), + } + } +} + +#[derive(Clone, Debug, PartialEq)] +struct GeckoContextParameters { + context: GeckoContext +} + +impl Parameters for GeckoContextParameters { + fn from_json(body: &Json) -> WebDriverResult { + let data = try!(body.as_object().ok_or( + WebDriverError::new(ErrorStatus::InvalidArgument, + "Message body was not an object"))); + let context_value = try!(data.get("context").ok_or( + WebDriverError::new(ErrorStatus::InvalidArgument, + "Missing context key"))); + let value = try!(context_value.as_string().ok_or( + WebDriverError::new( + ErrorStatus::InvalidArgument, + "context was not a string"))); + let context = try!(match value { + "chrome" => Ok(GeckoContext::Chrome), + "content" => Ok(GeckoContext::Content), + _ => Err(WebDriverError::new(ErrorStatus::InvalidArgument, + &format!("{} is not a valid context", + value))) + }); + Ok(GeckoContextParameters { + context: context + }) + } +} + +impl ToJson for GeckoContextParameters { + fn to_json(&self) -> Json { + let mut data = BTreeMap::new(); + data.insert("context".to_owned(), self.context.to_json()); + Json::Object(data) + } +} + +impl ToMarionette for GeckoContextParameters { + fn to_marionette(&self) -> WebDriverResult { + let mut data = BTreeMap::new(); + data.insert("value".to_owned(), self.context.to_json()); + Ok(Json::Object(data)) + } +} pub static FIREFOX_PREFERENCES: [(&'static str, PrefValue); 49] = [ ("app.update.enabled", PrefValue::PrefBool(false)), @@ -162,8 +271,8 @@ impl MarionetteHandler { } } -impl WebDriverHandler for MarionetteHandler { - fn handle_command(&mut self, _: &Option, msg: &WebDriverMessage) -> WebDriverResult { +impl WebDriverHandler for MarionetteHandler { + fn handle_command(&mut self, _: &Option, msg: &WebDriverMessage) -> WebDriverResult { let mut create_connection = false; match self.connection.lock() { Ok(ref mut connection) => { @@ -240,7 +349,7 @@ impl MarionetteSession { } } - pub fn msg_to_marionette(&self, msg: &WebDriverMessage) -> WebDriverResult { + pub fn msg_to_marionette(&self, msg: &WebDriverMessage) -> WebDriverResult { let x = try!(msg.to_marionette()); let data = try_opt!(x.as_object(), ErrorStatus::UnknownError, @@ -248,7 +357,7 @@ impl MarionetteSession { Ok(Json::Object(data)) } - pub fn update(&mut self, msg: &WebDriverMessage, + pub fn update(&mut self, msg: &WebDriverMessage, resp: &Json) -> WebDriverResult<()> { match msg.command { NewSession => { @@ -288,7 +397,7 @@ impl MarionetteSession { Ok(WebElement::new(id)) } - pub fn response_from_json(&mut self, message: &WebDriverMessage, + pub fn response_from_json(&mut self, message: &WebDriverMessage, data: &str) -> WebDriverResult { let json_data = try!(Json::from_str(data)); @@ -440,8 +549,16 @@ impl MarionetteSession { DeleteSession => { WebDriverResponse::DeleteSession }, - Extension(_) => { - panic!("No extensions implemented") + Extension(ref extension) => { + match extension { + &GeckoExtensionCommand::GetContext => { + let value = try_opt!(json_data.find("value"), + ErrorStatus::UnknownError, + "Failed to find value field"); + WebDriverResponse::Generic(ValueResponse::new(value.clone())) + } + &GeckoExtensionCommand::SetContext(_) => WebDriverResponse::Void + } } }) } @@ -607,7 +724,7 @@ impl MarionetteConnection { format!("{}:{}", data.len(), data) } - pub fn send_message(&mut self, msg: &WebDriverMessage) -> WebDriverResult { + pub fn send_message(&mut self, msg: &WebDriverMessage) -> WebDriverResult { let resp = try!(self.session.msg_to_marionette(msg)); let resp_data = try!(self.send(&resp)); self.session.response_from_json(msg, &resp_data[..]) @@ -698,7 +815,7 @@ trait ToMarionette { fn to_marionette(&self) -> WebDriverResult; } -impl ToMarionette for WebDriverMessage { +impl ToMarionette for WebDriverMessage { fn to_marionette(&self) -> WebDriverResult { let (opt_name, opt_parameters) = match self.command { NewSession => { @@ -735,7 +852,7 @@ impl ToMarionette for WebDriverMessage { let mut data = try_opt!(body.unwrap().as_object(), ErrorStatus::UnknownError, - "Marionette response was not an object").clone(); + "Marionette request was not an object").clone(); data.insert("element".to_string(), e.id.to_json()); (Some("findElement"), Some(Ok(Json::Object(data.clone())))) }, @@ -747,7 +864,7 @@ impl ToMarionette for WebDriverMessage { let mut data = try_opt!(body.unwrap().as_object(), ErrorStatus::UnknownError, - "Marionette response was not an object").clone(); + "Marionette request was not an object").clone(); data.insert("element".to_string(), e.id.to_json()); (Some("findElements"), Some(Ok(Json::Object(data.clone())))) }, @@ -807,8 +924,13 @@ impl ToMarionette for WebDriverMessage { data.insert("full".to_string(), Json::Boolean(false)); (Some("takeScreenshot"), Some(Ok(Json::Object(data)))) }, - Extension(_) => { - panic!("No extensions implemented"); + Extension(ref extension) => { + match extension { + &GeckoExtensionCommand::GetContext => (Some("getContext"), None), + &GeckoExtensionCommand::SetContext(ref x) => { + (Some("setContext"), Some(x.to_marionette())) + } + } } };