diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/capabilities.rs | 94 | ||||
-rw-r--r-- | src/command.rs | 51 | ||||
-rw-r--r-- | src/main.rs | 18 | ||||
-rw-r--r-- | src/marionette.rs | 239 | ||||
-rw-r--r-- | src/prefs.rs | 29 |
5 files changed, 246 insertions, 185 deletions
diff --git a/src/capabilities.rs b/src/capabilities.rs index b25c202..ed85705 100644 --- a/src/capabilities.rs +++ b/src/capabilities.rs @@ -1,6 +1,6 @@ use base64; -use command::LogOptions; -use logging::Level; +use crate::command::LogOptions; +use crate::logging::Level; use mozprofile::preferences::Pref; use mozprofile::profile::Profile; use mozrunner::runner::platform::firefox_default_path; @@ -136,7 +136,10 @@ impl<'a> BrowserCapabilities for FirefoxCapabilities<'a> { fn accept_insecure_certs(&mut self, _: &Capabilities) -> WebDriverResult<bool> { let version_str = self.version(); if let Some(x) = version_str { - Ok(try!(Version::from_str(&*x).or_else(|x| Err(convert_version_error(x)))).major >= 52) + Ok(Version::from_str(&*x) + .or_else(|x| Err(convert_version_error(x)))? + .major + >= 52) } else { Ok(false) } @@ -151,11 +154,16 @@ impl<'a> BrowserCapabilities for FirefoxCapabilities<'a> { version: &str, comparison: &str, ) -> WebDriverResult<bool> { - try!(Version::from_str(version).or_else(|x| Err(convert_version_error(x)))) + Version::from_str(version) + .or_else(|x| Err(convert_version_error(x)))? .matches(comparison) .or_else(|x| Err(convert_version_error(x))) } + fn strict_file_interactability(&mut self, _: &Capabilities) -> WebDriverResult<bool> { + Ok(true) + } + fn accept_proxy(&mut self, _: &Capabilities, _: &Capabilities) -> WebDriverResult<bool> { Ok(true) } @@ -316,16 +324,16 @@ impl FirefoxOptions { rv.binary = binary_path; if let Some(json) = matched.remove("moz:firefoxOptions") { - let options = try!(json.as_object().ok_or(WebDriverError::new( + let options = json.as_object().ok_or(WebDriverError::new( ErrorStatus::InvalidArgument, "'moz:firefoxOptions' \ capability is not an object" - ))); + ))?; - rv.profile = try!(FirefoxOptions::load_profile(&options)); - rv.args = try!(FirefoxOptions::load_args(&options)); - rv.log = try!(FirefoxOptions::load_log(&options)); - rv.prefs = try!(FirefoxOptions::load_prefs(&options)); + rv.profile = FirefoxOptions::load_profile(&options)?; + rv.args = FirefoxOptions::load_args(&options)?; + rv.log = FirefoxOptions::load_log(&options)?; + rv.prefs = FirefoxOptions::load_prefs(&options)?; } Ok(rv) @@ -333,22 +341,22 @@ impl FirefoxOptions { fn load_profile(options: &Capabilities) -> WebDriverResult<Option<Profile>> { if let Some(profile_json) = options.get("profile") { - let profile_base64 = try!(profile_json.as_str().ok_or(WebDriverError::new( + let profile_base64 = profile_json.as_str().ok_or(WebDriverError::new( ErrorStatus::UnknownError, "Profile is not a string" - ))); - let profile_zip = &*try!(base64::decode(profile_base64)); + ))?; + let profile_zip = &*base64::decode(profile_base64)?; // Create an emtpy profile directory - let profile = try!(Profile::new(None)); - try!(unzip_buffer( + let profile = Profile::new(None)?; + unzip_buffer( profile_zip, profile .temp_dir .as_ref() .expect("Profile doesn't have a path") .path() - )); + )?; Ok(Some(profile)) } else { @@ -358,22 +366,20 @@ impl FirefoxOptions { fn load_args(options: &Capabilities) -> WebDriverResult<Option<Vec<String>>> { if let Some(args_json) = options.get("args") { - let args_array = try!(args_json.as_array().ok_or(WebDriverError::new( + let args_array = args_json.as_array().ok_or(WebDriverError::new( ErrorStatus::UnknownError, "Arguments were not an \ array" - ))); - let args = try!( - args_array - .iter() - .map(|x| x.as_str().map(|x| x.to_owned())) - .collect::<Option<Vec<String>>>() - .ok_or(WebDriverError::new( - ErrorStatus::UnknownError, - "Arguments entries were not all \ - strings" - )) - ); + ))?; + let args = args_array + .iter() + .map(|x| x.as_str().map(|x| x.to_owned())) + .collect::<Option<Vec<String>>>() + .ok_or(WebDriverError::new( + ErrorStatus::UnknownError, + "Arguments entries were not all \ + strings" + ))?; Ok(Some(args)) } else { Ok(None) @@ -409,13 +415,13 @@ impl FirefoxOptions { pub fn load_prefs(options: &Capabilities) -> WebDriverResult<Vec<(String, Pref)>> { if let Some(prefs_data) = options.get("prefs") { - let prefs = try!(prefs_data.as_object().ok_or(WebDriverError::new( + let prefs = prefs_data.as_object().ok_or(WebDriverError::new( ErrorStatus::UnknownError, "Prefs were not an object" - ))); + ))?; let mut rv = Vec::with_capacity(prefs.len()); for (key, value) in prefs.iter() { - rv.push((key.clone(), try!(pref_from_json(value)))); + rv.push((key.clone(), pref_from_json(value)?)); } Ok(rv) } else { @@ -438,16 +444,16 @@ fn pref_from_json(value: &Value) -> WebDriverResult<Pref> { fn unzip_buffer(buf: &[u8], dest_dir: &Path) -> WebDriverResult<()> { let reader = Cursor::new(buf); - let mut zip = try!( - zip::ZipArchive::new(reader) - .map_err(|_| WebDriverError::new(ErrorStatus::UnknownError, "Failed to unzip profile")) - ); + let mut zip = zip::ZipArchive::new(reader) + .map_err(|_| WebDriverError::new(ErrorStatus::UnknownError, "Failed to unzip profile"))?; for i in 0..zip.len() { - let mut file = try!(zip.by_index(i).map_err(|_| WebDriverError::new( - ErrorStatus::UnknownError, - "Processing profile zip file failed" - ))); + let mut file = zip.by_index(i).map_err(|_| { + WebDriverError::new( + ErrorStatus::UnknownError, + "Processing profile zip file failed", + ) + })?; let unzip_path = { let name = file.name(); let is_dir = name.ends_with("/"); @@ -463,7 +469,7 @@ fn unzip_buffer(buf: &[u8], dest_dir: &Path) -> WebDriverResult<()> { if let Some(dir) = create_dir { if !dir.exists() { debug!("Creating profile directory tree {}", dir.to_string_lossy()); - try!(fs::create_dir_all(dir)); + fs::create_dir_all(dir)?; } } } @@ -477,10 +483,10 @@ fn unzip_buffer(buf: &[u8], dest_dir: &Path) -> WebDriverResult<()> { if let Some(unzip_path) = unzip_path { debug!("Extracting profile to {}", unzip_path.to_string_lossy()); - let dest = try!(fs::File::create(unzip_path)); + let dest = fs::File::create(unzip_path)?; if file.size() > 0 { let mut writer = BufWriter::new(dest); - try!(io::copy(&mut file, &mut writer)); + io::copy(&mut file, &mut writer)?; } } } @@ -494,7 +500,7 @@ mod tests { use self::mozprofile::preferences::Pref; use super::*; - use marionette::MarionetteHandler; + use crate::marionette::MarionetteHandler; use std::default::Default; use std::fs::File; use std::io::Read; diff --git a/src/command.rs b/src/command.rs index 754b91e..9847d02 100644 --- a/src/command.rs +++ b/src/command.rs @@ -1,6 +1,6 @@ use base64; +use crate::logging; use hyper::Method; -use logging; use regex::Captures; use serde::de::{self, Deserialize, Deserializer}; use serde_json::{self, Value}; @@ -48,6 +48,11 @@ pub fn extension_routes() -> Vec<(Method, &'static str, GeckoExtensionRoute)> { "/session/{sessionId}/moz/addon/uninstall", GeckoExtensionRoute::UninstallAddon, ), + ( + Method::GET, + "/session/{sessionId}/moz/screenshot/full", + GeckoExtensionRoute::TakeFullScreenshot, + ), ]; } @@ -59,6 +64,7 @@ pub enum GeckoExtensionRoute { XblAnonymousByAttribute, InstallAddon, UninstallAddon, + TakeFullScreenshot, } impl WebDriverExtensionRoute for GeckoExtensionRoute { @@ -69,12 +75,14 @@ impl WebDriverExtensionRoute for GeckoExtensionRoute { params: &Captures, body_data: &Value, ) -> WebDriverResult<WebDriverCommand<GeckoExtensionCommand>> { - let command = match self { - &GeckoExtensionRoute::GetContext => GeckoExtensionCommand::GetContext, - &GeckoExtensionRoute::SetContext => { + use self::GeckoExtensionRoute::*; + + let command = match *self { + GetContext => GeckoExtensionCommand::GetContext, + SetContext => { GeckoExtensionCommand::SetContext(serde_json::from_value(body_data.clone())?) } - &GeckoExtensionRoute::XblAnonymousChildren => { + XblAnonymousChildren => { let element_id = try_opt!( params.name("elementId"), ErrorStatus::InvalidArgument, @@ -83,7 +91,7 @@ impl WebDriverExtensionRoute for GeckoExtensionRoute { let element = WebElement::new(element_id.as_str().to_string()); GeckoExtensionCommand::XblAnonymousChildren(element) } - &GeckoExtensionRoute::XblAnonymousByAttribute => { + XblAnonymousByAttribute => { let element_id = try_opt!( params.name("elementId"), ErrorStatus::InvalidArgument, @@ -94,13 +102,15 @@ impl WebDriverExtensionRoute for GeckoExtensionRoute { serde_json::from_value(body_data.clone())?, ) } - &GeckoExtensionRoute::InstallAddon => { + InstallAddon => { GeckoExtensionCommand::InstallAddon(serde_json::from_value(body_data.clone())?) } - &GeckoExtensionRoute::UninstallAddon => { + UninstallAddon => { GeckoExtensionCommand::UninstallAddon(serde_json::from_value(body_data.clone())?) } + TakeFullScreenshot => GeckoExtensionCommand::TakeFullScreenshot, }; + Ok(WebDriverCommand::Extension(command)) } } @@ -113,25 +123,20 @@ pub enum GeckoExtensionCommand { XblAnonymousByAttribute(WebElement, XblLocatorParameters), InstallAddon(AddonInstallParameters), UninstallAddon(AddonUninstallParameters), + TakeFullScreenshot, } impl WebDriverExtensionCommand for GeckoExtensionCommand { fn parameters_json(&self) -> Option<Value> { + use self::GeckoExtensionCommand::*; match self { - &GeckoExtensionCommand::GetContext => None, - &GeckoExtensionCommand::InstallAddon(ref x) => { - Some(serde_json::to_value(x.clone()).unwrap()) - } - &GeckoExtensionCommand::SetContext(ref x) => { - Some(serde_json::to_value(x.clone()).unwrap()) - } - &GeckoExtensionCommand::UninstallAddon(ref x) => { - Some(serde_json::to_value(x.clone()).unwrap()) - } - &GeckoExtensionCommand::XblAnonymousByAttribute(_, ref x) => { - Some(serde_json::to_value(x.clone()).unwrap()) - } - &GeckoExtensionCommand::XblAnonymousChildren(_) => None, + GetContext => None, + InstallAddon(x) => Some(serde_json::to_value(x).unwrap()), + SetContext(x) => Some(serde_json::to_value(x).unwrap()), + UninstallAddon(x) => Some(serde_json::to_value(x).unwrap()), + XblAnonymousByAttribute(_, x) => Some(serde_json::to_value(x).unwrap()), + XblAnonymousChildren(_) => None, + TakeFullScreenshot => None, } } } @@ -231,9 +236,9 @@ pub struct LogOptions { #[cfg(test)] mod tests { use super::*; + use crate::test::check_deserialize; use std::fs::File; use std::io::Read; - use test::check_deserialize; #[test] fn test_json_addon_install_parameters_null() { diff --git a/src/main.rs b/src/main.rs index 685919f..efa774f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -46,9 +46,9 @@ mod prefs; #[cfg(test)] pub mod test; -use build::BuildInfo; -use command::extension_routes; -use marionette::{MarionetteHandler, MarionetteSettings}; +use crate::build::BuildInfo; +use crate::command::extension_routes; +use crate::marionette::{MarionetteHandler, MarionetteSettings}; type ProgramResult = std::result::Result<(), (ExitCode, String)>; @@ -96,10 +96,17 @@ fn app<'a, 'b>() -> App<'a, 'b> { .takes_value(true), ) .arg( + Arg::with_name("marionette_host") + .long("marionette-host") + .value_name("HOST") + .help("Host to use to connect to Gecko (default: 127.0.0.1)") + .takes_value(true) + ) + .arg( Arg::with_name("marionette_port") .long("marionette-port") .value_name("PORT") - .help("Port to use to connect to Gecko (default: random free port)") + .help("Port to use to connect to Gecko (default: system-allocated port)") .takes_value(true), ) .arg( @@ -162,6 +169,8 @@ fn run() -> ProgramResult { let binary = matches.value_of("binary").map(PathBuf::from); + let marionette_host = matches.value_of("marionette_host") + .unwrap_or("127.0.0.1").to_string(); let marionette_port = match matches.value_of("marionette_port") { Some(x) => match u16::from_str(x) { Ok(x) => Some(x), @@ -186,6 +195,7 @@ fn run() -> ProgramResult { } let settings = MarionetteSettings { + host: marionette_host, port: marionette_port, binary, connect_existing: matches.is_present("connect_existing"), diff --git a/src/marionette.rs b/src/marionette.rs index 7998bfa..4ed383e 100644 --- a/src/marionette.rs +++ b/src/marionette.rs @@ -1,6 +1,8 @@ -use command::{AddonInstallParameters, AddonUninstallParameters, GeckoContextParameters, - GeckoExtensionCommand, GeckoExtensionRoute, XblLocatorParameters, - CHROME_ELEMENT_KEY, LEGACY_ELEMENT_KEY}; +use crate::command::{ + AddonInstallParameters, AddonUninstallParameters, GeckoContextParameters, + GeckoExtensionCommand, GeckoExtensionRoute, XblLocatorParameters, CHROME_ELEMENT_KEY, + LEGACY_ELEMENT_KEY, +}; use mozprofile::preferences::Pref; use mozprofile::profile::Profile; use mozrunner::runner::{FirefoxProcess, FirefoxRunner, Runner, RunnerProcess}; @@ -18,10 +20,10 @@ use std::sync::Mutex; use std::thread; use std::time; use webdriver::capabilities::CapabilitiesMatching; -use webdriver::command::WebDriverCommand::{AcceptAlert, AddCookie, CloseWindow, DeleteCookie, - DeleteCookies, DeleteSession, DismissAlert, - ElementClear, ElementClick, ElementSendKeys, - ExecuteAsyncScript, ExecuteScript, +use webdriver::command::WebDriverCommand::{AcceptAlert, AddCookie, NewWindow, CloseWindow, + DeleteCookie, DeleteCookies, DeleteSession, + DismissAlert, ElementClear, ElementClick, + ElementSendKeys, ExecuteAsyncScript, ExecuteScript, Extension, FindElement, FindElementElement, FindElementElements, FindElements, FullscreenWindow, Get, GetActiveElement, GetAlertText, GetCSSValue, @@ -39,23 +41,19 @@ use webdriver::command::WebDriverCommand::{AcceptAlert, AddCookie, CloseWindow, use webdriver::command::{ActionsParameters, AddCookieParameters, GetNamedCookieParameters, GetParameters, JavascriptCommandParameters, LocatorParameters, NewSessionParameters, SwitchToFrameParameters, SwitchToWindowParameters, - TakeScreenshotParameters, TimeoutsParameters, WindowRectParameters}; + TimeoutsParameters, WindowRectParameters, NewWindowParameters}; use webdriver::command::{WebDriverCommand, WebDriverMessage}; use webdriver::common::{Cookie, FrameId, WebElement, ELEMENT_KEY, FRAME_KEY, WINDOW_KEY}; use webdriver::error::{ErrorStatus, WebDriverError, WebDriverResult}; -use webdriver::response::{CloseWindowResponse, CookieResponse, CookiesResponse, +use webdriver::response::{NewWindowResponse, CloseWindowResponse, CookieResponse, CookiesResponse, ElementRectResponse, NewSessionResponse, TimeoutsResponse, ValueResponse, WebDriverResponse, WindowRectResponse}; use webdriver::server::{Session, WebDriverHandler}; -use build::BuildInfo; -use capabilities::{FirefoxCapabilities, FirefoxOptions}; -use logging; -use prefs; - -// localhost may be routed to the IPv6 stack on certain systems, -// and nsIServerSocket in Marionette only supports IPv4 -const DEFAULT_HOST: &'static str = "127.0.0.1"; +use crate::build::BuildInfo; +use crate::capabilities::{FirefoxCapabilities, FirefoxOptions}; +use crate::logging; +use crate::prefs; #[derive(Debug, PartialEq, Deserialize)] pub struct MarionetteHandshake { @@ -67,6 +65,7 @@ pub struct MarionetteHandshake { #[derive(Default)] pub struct MarionetteSettings { + pub host: String, pub port: Option<u16>, pub binary: Option<PathBuf>, pub connect_existing: bool, @@ -117,12 +116,13 @@ impl MarionetteHandler { logging::set_max_level(l); } - let port = self.settings.port.unwrap_or(get_free_port()?); + let host = self.settings.host.to_owned(); + let port = self.settings.port.unwrap_or(get_free_port(&host)?); if !self.settings.connect_existing { self.start_browser(port, options)?; } - let mut connection = MarionetteConnection::new(port, session_id.clone()); + let mut connection = MarionetteConnection::new(host, port, session_id.clone()); connection.connect(&mut self.browser).or_else(|e| { if let Some(ref mut runner) = self.browser { runner.kill()?; @@ -423,6 +423,8 @@ impl MarionetteSession { msg: &WebDriverMessage<GeckoExtensionRoute>, resp: MarionetteResponse, ) -> WebDriverResult<WebDriverResponse> { + use self::GeckoExtensionCommand::*; + if resp.id != self.command_id { return Err(WebDriverError::new( ErrorStatus::UnknownError, @@ -437,7 +439,7 @@ impl MarionetteSession { return Err(error.into()); } - try!(self.update(msg, &resp)); + self.update(msg, &resp)?; Ok(match msg.command { // Everything that doesn't have a response value @@ -479,15 +481,18 @@ impl MarionetteSession { | TakeScreenshot | TakeElementScreenshot(_) => WebDriverResponse::Generic(resp.to_value_response(true)?), GetTimeouts => { - let script = try_opt!( - try_opt!( + let script = match try_opt!( resp.result.get("script"), ErrorStatus::UnknownError, "Missing field: script" - ).as_u64(), - ErrorStatus::UnknownError, - "Failed to interpret script timeout duration as u64" - ); + ) { + Value::Null => None, + n => try_opt!( + Some(n.as_u64()), + ErrorStatus::UnknownError, + "Failed to interpret script timeout duration as u64" + ), + }; // Check for the spec-compliant "pageLoad", but also for "page load", // which was sent by Firefox 52 and earlier. let page_load = try_opt!( @@ -517,23 +522,43 @@ impl MarionetteSession { } Status => panic!("Got status command that should already have been handled"), GetWindowHandles => WebDriverResponse::Generic(resp.to_value_response(false)?), + NewWindow(_) => { + let handle: String = try_opt!( + try_opt!( + resp.result.get("handle"), + ErrorStatus::UnknownError, + "Failed to find handle field" + ).as_str(), + ErrorStatus::UnknownError, + "Failed to interpret handle as string" + ).into(); + let typ: String = try_opt!( + try_opt!( + resp.result.get("type"), + ErrorStatus::UnknownError, + "Failed to find type field" + ).as_str(), + ErrorStatus::UnknownError, + "Failed to interpret type as string" + ).into(); + + WebDriverResponse::NewWindow(NewWindowResponse { handle, typ }) + } CloseWindow => { let data = try_opt!( resp.result.as_array(), ErrorStatus::UnknownError, "Failed to interpret value as array" ); - let handles = try!( - data.iter() - .map(|x| { - Ok(try_opt!( - x.as_str(), - ErrorStatus::UnknownError, - "Failed to interpret window handle as string" - ).to_owned()) - }) - .collect() - ); + let handles = data + .iter() + .map(|x| { + Ok(try_opt!( + x.as_str(), + ErrorStatus::UnknownError, + "Failed to interpret window handle as string" + ).to_owned()) + }).collect::<Result<Vec<_>, _>>()?; WebDriverResponse::CloseWindow(CloseWindowResponse(handles)) } GetElementRect(_) => { @@ -650,11 +675,11 @@ impl MarionetteSession { WebDriverResponse::Cookie(CookieResponse(cookie)) } FindElement(_) | FindElementElement(_, _) => { - let element = try!(self.to_web_element(try_opt!( + let element = self.to_web_element(try_opt!( resp.result.get("value"), ErrorStatus::UnknownError, "Failed to find value field" - ))); + ))?; WebDriverResponse::Generic(ValueResponse(serde_json::to_value(element)?)) } FindElements(_) | FindElementElements(_, _) => { @@ -663,12 +688,11 @@ impl MarionetteSession { ErrorStatus::UnknownError, "Failed to interpret value as array" ); - let elements = try!( - element_vec - .iter() - .map(|x| self.to_web_element(x)) - .collect::<Result<Vec<_>, _>>() - ); + let elements = element_vec + .iter() + .map(|x| self.to_web_element(x)) + .collect::<Result<Vec<_>, _>>()?; + // TODO(Henrik): How to remove unwrap? WebDriverResponse::Generic(ValueResponse(Value::Array( elements @@ -678,11 +702,11 @@ impl MarionetteSession { ))) } GetActiveElement => { - let element = try!(self.to_web_element(try_opt!( + let element = self.to_web_element(try_opt!( resp.result.get("value"), ErrorStatus::UnknownError, "Failed to find value field" - ))); + ))?; WebDriverResponse::Generic(ValueResponse(serde_json::to_value(element)?)) } NewSession(_) => { @@ -715,36 +739,32 @@ impl MarionetteSession { } DeleteSession => WebDriverResponse::DeleteSession, Extension(ref extension) => match extension { - &GeckoExtensionCommand::GetContext => { - WebDriverResponse::Generic(resp.to_value_response(true)?) - } - &GeckoExtensionCommand::SetContext(_) => WebDriverResponse::Void, - &GeckoExtensionCommand::XblAnonymousChildren(_) => { + GetContext => WebDriverResponse::Generic(resp.to_value_response(true)?), + SetContext(_) => WebDriverResponse::Void, + XblAnonymousChildren(_) => { let els_vec = try_opt!( resp.result.as_array(), ErrorStatus::UnknownError, "Failed to interpret body as array" ); - let els = try!( - els_vec - .iter() - .map(|x| self.to_web_element(x)) - .collect::<Result<Vec<_>, _>>() - ); + let els = els_vec + .iter() + .map(|x| self.to_web_element(x)) + .collect::<Result<Vec<_>, _>>()?; + WebDriverResponse::Generic(ValueResponse(serde_json::to_value(els)?)) } - &GeckoExtensionCommand::XblAnonymousByAttribute(_, _) => { - let el = try!(self.to_web_element(try_opt!( + XblAnonymousByAttribute(_, _) => { + let el = self.to_web_element(try_opt!( resp.result.get("value"), ErrorStatus::UnknownError, "Failed to find value field" - ))); + ))?; WebDriverResponse::Generic(ValueResponse(serde_json::to_value(el)?)) } - &GeckoExtensionCommand::InstallAddon(_) => { - WebDriverResponse::Generic(resp.to_value_response(true)?) - } - &GeckoExtensionCommand::UninstallAddon(_) => WebDriverResponse::Void, + InstallAddon(_) => WebDriverResponse::Generic(resp.to_value_response(true)?), + UninstallAddon(_) => WebDriverResponse::Void, + TakeFullScreenshot => WebDriverResponse::Generic(resp.to_value_response(true)?), }, }) } @@ -781,6 +801,8 @@ impl MarionetteCommand { capabilities: Option<Map<String, Value>>, msg: &WebDriverMessage<GeckoExtensionRoute>, ) -> WebDriverResult<MarionetteCommand> { + use self::GeckoExtensionCommand::*; + let (opt_name, opt_parameters) = match msg.command { Status => panic!("Got status command that should already have been handled"), AcceptAlert => { @@ -788,6 +810,7 @@ impl MarionetteCommand { (Some("WebDriver:AcceptDialog"), None) } AddCookie(ref x) => (Some("WebDriver:AddCookie"), Some(x.to_marionette())), + NewWindow(ref x) => (Some("WebDriver:NewWindow"), Some(x.to_marionette())), CloseWindow => (Some("WebDriver:CloseWindow"), None), DeleteCookie(ref x) => { let mut data = Map::new(); @@ -828,13 +851,13 @@ impl MarionetteCommand { ExecuteScript(ref x) => (Some("WebDriver:ExecuteScript"), Some(x.to_marionette())), FindElement(ref x) => (Some("WebDriver:FindElement"), Some(x.to_marionette())), FindElementElement(ref e, ref x) => { - let mut data = try!(x.to_marionette()); + let mut data = x.to_marionette()?; data.insert("element".to_string(), Value::String(e.id.clone())); (Some("WebDriver:FindElement"), Some(Ok(data))) } FindElements(ref x) => (Some("WebDriver:FindElements"), Some(x.to_marionette())), FindElementElements(ref e, ref x) => { - let mut data = try!(x.to_marionette()); + let mut data = x.to_marionette()?; data.insert("element".to_string(), Value::String(e.id.clone())); (Some("WebDriver:FindElements"), Some(Ok(data))) } @@ -918,8 +941,7 @@ impl MarionetteCommand { SwitchToWindow(ref x) => (Some("WebDriver:SwitchToWindow"), Some(x.to_marionette())), TakeElementScreenshot(ref e) => { let mut data = Map::new(); - data.insert("element".to_string(), serde_json::to_value(e)?); - // data.insert("id".to_string(), e.id.to_json()); + data.insert("id".to_string(), Value::String(e.id.clone())); data.insert("highlights".to_string(), Value::Array(vec![])); data.insert("full".to_string(), Value::Bool(false)); (Some("WebDriver:TakeScreenshot"), Some(Ok(data))) @@ -932,28 +954,35 @@ impl MarionetteCommand { (Some("WebDriver:TakeScreenshot"), Some(Ok(data))) } Extension(ref extension) => match extension { - &GeckoExtensionCommand::GetContext => (Some("Marionette:GetContext"), None), - &GeckoExtensionCommand::InstallAddon(ref x) => { + GetContext => (Some("Marionette:GetContext"), None), + InstallAddon(x) => { (Some("Addon:Install"), Some(x.to_marionette())) } - &GeckoExtensionCommand::SetContext(ref x) => { + SetContext(x) => { (Some("Marionette:SetContext"), Some(x.to_marionette())) } - &GeckoExtensionCommand::UninstallAddon(ref x) => { + UninstallAddon(x) => { (Some("Addon:Uninstall"), Some(x.to_marionette())) } - &GeckoExtensionCommand::XblAnonymousByAttribute(ref e, ref x) => { - let mut data = try!(x.to_marionette()); + XblAnonymousByAttribute(e, x) => { + let mut data = x.to_marionette()?; data.insert("element".to_string(), Value::String(e.id.clone())); (Some("WebDriver:FindElement"), Some(Ok(data))) } - &GeckoExtensionCommand::XblAnonymousChildren(ref e) => { + XblAnonymousChildren(e) => { let mut data = Map::new(); data.insert("using".to_owned(), serde_json::to_value("anon")?); data.insert("value".to_owned(), Value::Null); data.insert("element".to_string(), serde_json::to_value(e.id.clone())?); (Some("WebDriver:FindElements"), Some(Ok(data))) } + TakeFullScreenshot => { + let mut data = Map::new(); + data.insert("id".to_string(), Value::Null); + data.insert("highlights".to_string(), Value::Array(vec![])); + data.insert("full".to_string(), Value::Bool(true)); + (Some("WebDriver:TakeScreenshot"), Some(Ok(data))) + } }, }; @@ -962,7 +991,7 @@ impl MarionetteCommand { ErrorStatus::UnsupportedOperation, "Operation not supported" ); - let parameters = try!(opt_parameters.unwrap_or(Ok(Map::new()))); + let parameters = opt_parameters.unwrap_or(Ok(Map::new()))?; Ok(MarionetteCommand::new(id, name.into(), parameters)) } @@ -1040,24 +1069,27 @@ impl Into<WebDriverError> for MarionetteError { } } -fn get_free_port() -> IoResult<u16> { - TcpListener::bind((DEFAULT_HOST, 0)) +fn get_free_port(host: &str) -> IoResult<u16> { + TcpListener::bind((host, 0)) .and_then(|stream| stream.local_addr()) .map(|x| x.port()) } pub struct MarionetteConnection { + host: String, port: u16, stream: Option<TcpStream>, pub session: MarionetteSession, } impl MarionetteConnection { - pub fn new(port: u16, session_id: Option<String>) -> MarionetteConnection { + pub fn new(host: String, port: u16, session_id: Option<String>) -> MarionetteConnection { + let session = MarionetteSession::new(session_id); MarionetteConnection { - port: port, + host, + port, stream: None, - session: MarionetteSession::new(session_id), + session, } } @@ -1069,7 +1101,7 @@ impl MarionetteConnection { debug!( "Waiting {}s to connect to browser on {}:{}", timeout.as_secs(), - DEFAULT_HOST, + self.host, self.port ); loop { @@ -1093,7 +1125,7 @@ impl MarionetteConnection { } } - match TcpStream::connect(&(DEFAULT_HOST, self.port)) { + match TcpStream::connect((&self.host[..], self.port)) { Ok(stream) => { self.stream = Some(stream); break; @@ -1113,7 +1145,7 @@ impl MarionetteConnection { debug!( "Connection established on {}:{}. Waiting for Marionette handshake", - DEFAULT_HOST, self.port, + self.host, self.port, ); let data = self.handshake()?; @@ -1237,7 +1269,7 @@ impl MarionetteConnection { let stream = self.stream.as_mut().unwrap(); loop { let buf = &mut [0 as u8]; - let num_read = try!(stream.read(buf)); + let num_read = stream.read(buf)?; let byte = match num_read { 0 => { return Err(IoError::new( @@ -1262,7 +1294,7 @@ impl MarionetteConnection { let mut payload = Vec::with_capacity(bytes); let mut total_read = 0; while total_read < bytes { - let num_read = try!(stream.read(buf)); + let num_read = stream.read(buf)?; if num_read == 0 { return Err(IoError::new( ErrorKind::Other, @@ -1369,7 +1401,7 @@ impl ToMarionette for FrameId { FrameId::Short(x) => data.insert("id".to_string(), serde_json::to_value(x)?), FrameId::Element(ref x) => data.insert( "element".to_string(), - Value::Object(try!(x.to_marionette())), + Value::Object(x.to_marionette()?), ), }; Ok(data) @@ -1398,10 +1430,11 @@ impl ToMarionette for GetParameters { impl ToMarionette for JavascriptCommandParameters { fn to_marionette(&self) -> WebDriverResult<Map<String, Value>> { - let mut data = serde_json::to_value(self)?.as_object().unwrap().clone(); - data.insert("newSandbox".to_string(), Value::Bool(false)); - data.insert("specialPowers".to_string(), Value::Bool(false)); - Ok(data) + Ok(try_opt!( + serde_json::to_value(self)?.as_object(), + ErrorStatus::UnknownError, + "Expected an object" + ).clone()) } } @@ -1415,6 +1448,16 @@ impl ToMarionette for LocatorParameters { } } +impl ToMarionette for NewWindowParameters { + fn to_marionette(&self) -> WebDriverResult<Map<String, Value>> { + let mut data = Map::new(); + if let Some(ref x) = self.type_hint { + data.insert("type".to_string(), serde_json::to_value(x)?); + } + Ok(data) + } +} + impl ToMarionette for SwitchToFrameParameters { fn to_marionette(&self) -> WebDriverResult<Map<String, Value>> { let mut data = Map::new(); @@ -1441,18 +1484,6 @@ impl ToMarionette for SwitchToWindowParameters { } } -impl ToMarionette for TakeScreenshotParameters { - fn to_marionette(&self) -> WebDriverResult<Map<String, Value>> { - let mut data = Map::new(); - let element = match self.element { - None => Value::Null, - Some(ref x) => Value::Object(try!(x.to_marionette())), - }; - data.insert("element".to_string(), element); - Ok(data) - } -} - impl ToMarionette for TimeoutsParameters { fn to_marionette(&self) -> WebDriverResult<Map<String, Value>> { Ok(try_opt!( diff --git a/src/prefs.rs b/src/prefs.rs index 9f058e6..171ccb6 100644 --- a/src/prefs.rs +++ b/src/prefs.rs @@ -2,28 +2,37 @@ use mozprofile::preferences::Pref; // ALL CHANGES TO THIS FILE MUST HAVE REVIEW FROM A GECKODRIVER PEER! // -// The Marionette Python client is used out-of-tree with release -// channel builds of Firefox. Removing a preference from this file -// will cause regressions, so please be careful and get review from -// a Testing :: Marionette peer before you make any changes to this file. - +// All preferences in this file are not immediately effective, and +// require a restart of Firefox, or have to be set in the profile before +// Firefox gets started the first time. If a preference has to be added, +// which is immediately effective, it needs to be done in Marionette +// (marionette.js). +// +// Note: geckodriver is used out-of-tree with various builds of Firefox. +// Removing a preference from this file will cause regressions, +// so please be careful and get review from a Testing :: geckodriver peer +// before you make any changes to this file. lazy_static! { pub static ref DEFAULT: Vec<(&'static str, Pref)> = vec![ // Make sure Shield doesn't hit the network. ("app.normandy.api_url", Pref::new("")), - // Disable automatic downloading of new releases - ("app.update.auto", Pref::new(false)), + // Disable Firefox old build background check + ("app.update.checkInstallTime", Pref::new(false)), // Disable automatically upgrading Firefox + // + // Note: Possible update tests could reset or flip the value to allow + // updates to be downloaded and applied. ("app.update.disabledForTesting", Pref::new(true)), - // app.update.enabled is being removed. Once Firefox 62 becomes stable, - // the line below can be removed as well. - ("app.update.enabled", Pref::new(false)), + // !!! For backward compatibility up to Firefox 64. Only remove + // when this Firefox version is no longer supported by geckodriver !!! + ("app.update.auto", Pref::new(false)), // Enable the dump function, which sends messages to the system // console ("browser.dom.window.dump.enabled", Pref::new(true)), + ("devtools.console.stdout.chrome", Pref::new(true)), // Disable safebrowsing components ("browser.safebrowsing.blockedURIs.enabled", Pref::new(false)), |