Bug 1436830 - Roll own log::Log implementation in geckodriver. r?rillian,jgraham
This provides a custom implementaiton of log::Log for geckodriver,
effectively making slog redundant in central.
slog is, for our purposes, an unnecessarily complicated crate
to depend on. With Rust 1.24 transitioning to stable soon,
we need to prepare code and crates in central for this change.
geckodriver only uses slog for the single purpose of changing the
log level at runtime, and it would be less work to roll our own
logger implementation than it would be to grok the slog API changes.
MozReview-Commit-ID: 2wyS8e6s2mr

usehyper::method::Method;usemozprofile::preferences::Pref;usemozprofile::profile::Profile;usemozrunner::runner::{FirefoxRunner,FirefoxProcess,Runner,RunnerProcess};useregex::Captures;userustc_serialize::base64::FromBase64;userustc_serialize::json;userustc_serialize::json::{Json,ToJson};usestd::collections::BTreeMap;usestd::env;usestd::error::Error;usestd::fs::File;usestd::io::ErrorasIoError;usestd::io::ErrorKind;usestd::io::prelude::*;usestd::path::PathBuf;usestd::io::ResultasIoResult;usestd::net::{TcpListener,TcpStream};usestd::sync::Mutex;usestd::thread::sleep;usestd::time::Duration;useuuid::Uuid;usewebdriver::capabilities::CapabilitiesMatching;usewebdriver::command::{WebDriverCommand,WebDriverMessage,Parameters,WebDriverExtensionCommand};usewebdriver::command::WebDriverCommand::{NewSession,DeleteSession,Status,Get,GetCurrentUrl,GoBack,GoForward,Refresh,GetTitle,GetPageSource,GetWindowHandle,GetWindowHandles,CloseWindow,SetWindowRect,GetWindowRect,MinimizeWindow,MaximizeWindow,FullscreenWindow,SwitchToWindow,SwitchToFrame,SwitchToParentFrame,FindElement,FindElements,FindElementElement,FindElementElements,GetActiveElement,IsDisplayed,IsSelected,GetElementAttribute,GetElementProperty,GetCSSValue,GetElementText,GetElementTagName,GetElementRect,IsEnabled,ElementClick,ElementTap,ElementClear,ElementSendKeys,ExecuteScript,ExecuteAsyncScript,GetCookies,GetNamedCookie,AddCookie,DeleteCookies,DeleteCookie,GetTimeouts,SetTimeouts,DismissAlert,AcceptAlert,GetAlertText,SendAlertText,TakeScreenshot,TakeElementScreenshot,Extension,PerformActions,ReleaseActions};usewebdriver::command::{NewSessionParameters,GetParameters,WindowRectParameters,SwitchToWindowParameters,SwitchToFrameParameters,LocatorParameters,JavascriptCommandParameters,GetNamedCookieParameters,AddCookieParameters,TimeoutsParameters,ActionsParameters,TakeScreenshotParameters};usewebdriver::response::{CloseWindowResponse,Cookie,CookieResponse,CookiesResponse,ElementRectResponse,NewSessionResponse,TimeoutsResponse,ValueResponse,WebDriverResponse,WindowRectResponse};usewebdriver::common::{Date,ELEMENT_KEY,FrameId,Nullable,WebElement};usewebdriver::error::{ErrorStatus,WebDriverError,WebDriverResult};usewebdriver::server::{WebDriverHandler,Session};usewebdriver::httpapi::{WebDriverExtensionRoute};usecapabilities::{FirefoxCapabilities,FirefoxOptions};uselogging;useprefs;constDEFAULT_HOST:&'staticstr="localhost";// Firefox' integrated background monitor which observes long running threads during// shutdown kills those after 65s. Wait some additional seconds for a safe shutdownconstTIMEOUT_BROWSER_SHUTDOWN:u64=70*1000;pubfnextension_routes()->Vec<(Method,&'staticstr,GeckoExtensionRoute)>{returnvec![(Method::Get,"/session/{sessionId}/moz/context",GeckoExtensionRoute::GetContext),(Method::Post,"/session/{sessionId}/moz/context",GeckoExtensionRoute::SetContext),(Method::Post,"/session/{sessionId}/moz/xbl/{elementId}/anonymous_children",GeckoExtensionRoute::XblAnonymousChildren),(Method::Post,"/session/{sessionId}/moz/xbl/{elementId}/anonymous_by_attribute",GeckoExtensionRoute::XblAnonymousByAttribute),(Method::Post,"/session/{sessionId}/moz/addon/install",GeckoExtensionRoute::InstallAddon),(Method::Post,"/session/{sessionId}/moz/addon/uninstall",GeckoExtensionRoute::UninstallAddon)];}#[derive(Clone, PartialEq)]pubenumGeckoExtensionRoute{GetContext,SetContext,XblAnonymousChildren,XblAnonymousByAttribute,InstallAddon,UninstallAddon,}implWebDriverExtensionRouteforGeckoExtensionRoute{typeCommand=GeckoExtensionCommand;fncommand(&self,captures:&Captures,body_data:&Json)->WebDriverResult<WebDriverCommand<GeckoExtensionCommand>>{letcommand=matchself{&GeckoExtensionRoute::GetContext=>GeckoExtensionCommand::GetContext,&GeckoExtensionRoute::SetContext=>{letparameters:GeckoContextParameters=try!(Parameters::from_json(&body_data));GeckoExtensionCommand::SetContext(parameters)}&GeckoExtensionRoute::XblAnonymousChildren=>{letelement_id=try!(captures.name("elementId").ok_or(WebDriverError::new(ErrorStatus::InvalidArgument,"Missing elementId parameter")));GeckoExtensionCommand::XblAnonymousChildren(element_id.as_str().into())}&GeckoExtensionRoute::XblAnonymousByAttribute=>{letelement_id=try!(captures.name("elementId").ok_or(WebDriverError::new(ErrorStatus::InvalidArgument,"Missing elementId parameter")));letparameters:AttributeParameters=try!(Parameters::from_json(&body_data));GeckoExtensionCommand::XblAnonymousByAttribute(element_id.as_str().into(),parameters)}&GeckoExtensionRoute::InstallAddon=>{letparameters:AddonInstallParameters=try!(Parameters::from_json(&body_data));GeckoExtensionCommand::InstallAddon(parameters)}&GeckoExtensionRoute::UninstallAddon=>{letparameters:AddonUninstallParameters=try!(Parameters::from_json(&body_data));GeckoExtensionCommand::UninstallAddon(parameters)}};Ok(WebDriverCommand::Extension(command))}}#[derive(Clone, PartialEq)]pubenumGeckoExtensionCommand{GetContext,SetContext(GeckoContextParameters),XblAnonymousChildren(WebElement),XblAnonymousByAttribute(WebElement,AttributeParameters),InstallAddon(AddonInstallParameters),UninstallAddon(AddonUninstallParameters)}implWebDriverExtensionCommandforGeckoExtensionCommand{fnparameters_json(&self)->Option<Json>{matchself{&GeckoExtensionCommand::GetContext=>None,&GeckoExtensionCommand::SetContext(refx)=>Some(x.to_json()),&GeckoExtensionCommand::XblAnonymousChildren(_)=>None,&GeckoExtensionCommand::XblAnonymousByAttribute(_,refx)=>Some(x.to_json()),&GeckoExtensionCommand::InstallAddon(refx)=>Some(x.to_json()),&GeckoExtensionCommand::UninstallAddon(refx)=>Some(x.to_json()),}}}#[derive(Clone, Debug, PartialEq)]enumGeckoContext{Content,Chrome,}implToJsonforGeckoContext{fnto_json(&self)->Json{matchself{&GeckoContext::Content=>Json::String("content".to_owned()),&GeckoContext::Chrome=>Json::String("chrome".to_owned()),}}}#[derive(Clone, Debug, PartialEq)]pubstructGeckoContextParameters{context:GeckoContext}implParametersforGeckoContextParameters{fnfrom_json(body:&Json)->WebDriverResult<GeckoContextParameters>{letdata=try!(body.as_object().ok_or(WebDriverError::new(ErrorStatus::InvalidArgument,"Message body was not an object")));letcontext_value=try!(data.get("context").ok_or(WebDriverError::new(ErrorStatus::InvalidArgument,"Missing context key")));letvalue=try!(context_value.as_string().ok_or(WebDriverError::new(ErrorStatus::InvalidArgument,"context was not a string")));letcontext=try!(matchvalue{"chrome"=>Ok(GeckoContext::Chrome),"content"=>Ok(GeckoContext::Content),_=>Err(WebDriverError::new(ErrorStatus::InvalidArgument,format!("{} is not a valid context",value)))});Ok(GeckoContextParameters{context:context})}}implToMarionetteforGeckoContextParameters{fnto_marionette(&self)->WebDriverResult<BTreeMap<String,Json>>{letmutdata=BTreeMap::new();data.insert("value".to_owned(),self.context.to_json());Ok(data)}}implToJsonforGeckoContextParameters{fnto_json(&self)->Json{letmutdata=BTreeMap::new();data.insert("context".to_owned(),self.context.to_json());Json::Object(data)}}#[derive(Clone, Debug, PartialEq)]pubstructAttributeParameters{name:String,value:String}implParametersforAttributeParameters{fnfrom_json(body:&Json)->WebDriverResult<AttributeParameters>{letdata=try!(body.as_object().ok_or(WebDriverError::new(ErrorStatus::InvalidArgument,"Message body was not an object")));letname=try!(try!(data.get("name").ok_or(WebDriverError::new(ErrorStatus::InvalidArgument,"Missing 'name' parameter"))).as_string().ok_or(WebDriverError::new(ErrorStatus::InvalidArgument,"'name' parameter is not a string")));letvalue=try!(try!(data.get("value").ok_or(WebDriverError::new(ErrorStatus::InvalidArgument,"Missing 'value' parameter"))).as_string().ok_or(WebDriverError::new(ErrorStatus::InvalidArgument,"'value' parameter is not a string")));Ok(AttributeParameters{name:name.to_owned(),value:value.to_owned(),})}}implToJsonforAttributeParameters{fnto_json(&self)->Json{letmutdata=BTreeMap::new();data.insert("name".to_owned(),self.name.to_json());data.insert("value".to_owned(),self.value.to_json());Json::Object(data)}}implToMarionetteforAttributeParameters{fnto_marionette(&self)->WebDriverResult<BTreeMap<String,Json>>{letmutdata=BTreeMap::new();data.insert("using".to_owned(),"anon attribute".to_json());letmutvalue=BTreeMap::new();value.insert(self.name.to_owned(),self.value.to_json());data.insert("value".to_owned(),Json::Object(value));Ok(data)}}#[derive(Clone, Debug, PartialEq)]pubstructAddonInstallParameters{pubpath:String,pubtemporary:bool}implParametersforAddonInstallParameters{fnfrom_json(body:&Json)->WebDriverResult<AddonInstallParameters>{letdata=try!(body.as_object().ok_or(WebDriverError::new(ErrorStatus::InvalidArgument,"Message body was not an object")));letbase64=matchdata.get("addon"){Some(x)=>{lets=try_opt!(x.as_string(),ErrorStatus::InvalidArgument,"'addon' is not a string").to_string();letaddon_path=env::temp_dir().as_path().join(format!("addon-{}.xpi",Uuid::new_v4()));letmutaddon_file=try!(File::create(&addon_path));letaddon_buf=try!(s.from_base64());try!(addon_file.write(addon_buf.as_slice()));Some(try_opt!(addon_path.to_str(),ErrorStatus::UnknownError,"could not write addon to file").to_string())},None=>None,};letpath=matchdata.get("path"){Some(x)=>Some(try_opt!(x.as_string(),ErrorStatus::InvalidArgument,"'path' is not a string").to_string()),None=>None,};if(base64.is_none()&&path.is_none())||(base64.is_some()&&path.is_some()){returnErr(WebDriverError::new(ErrorStatus::InvalidArgument,"Must specify exactly one of 'path' and 'addon'"));}lettemporary=matchdata.get("temporary"){Some(x)=>try_opt!(x.as_boolean(),ErrorStatus::InvalidArgument,"Failed to convert 'temporary' to boolean"),None=>false};returnOk(AddonInstallParameters{path:base64.or(path).unwrap(),temporary:temporary,})}}implToJsonforAddonInstallParameters{fnto_json(&self)->Json{letmutdata=BTreeMap::new();data.insert("path".to_string(),self.path.to_json());data.insert("temporary".to_string(),self.temporary.to_json());Json::Object(data)}}implToMarionetteforAddonInstallParameters{fnto_marionette(&self)->WebDriverResult<BTreeMap<String,Json>>{letmutdata=BTreeMap::new();data.insert("path".to_string(),self.path.to_json());data.insert("temporary".to_string(),self.temporary.to_json());Ok(data)}}#[derive(Clone, Debug, PartialEq)]pubstructAddonUninstallParameters{pubid:String}implParametersforAddonUninstallParameters{fnfrom_json(body:&Json)->WebDriverResult<AddonUninstallParameters>{letdata=try!(body.as_object().ok_or(WebDriverError::new(ErrorStatus::InvalidArgument,"Message body was not an object")));letid=try_opt!(try_opt!(data.get("id"),ErrorStatus::InvalidArgument,"Missing 'id' parameter").as_string(),ErrorStatus::InvalidArgument,"'id' is not a string").to_string();returnOk(AddonUninstallParameters{id:id})}}implToJsonforAddonUninstallParameters{fnto_json(&self)->Json{letmutdata=BTreeMap::new();data.insert("id".to_string(),self.id.to_json());Json::Object(data)}}implToMarionetteforAddonUninstallParameters{fnto_marionette(&self)->WebDriverResult<BTreeMap<String,Json>>{letmutdata=BTreeMap::new();data.insert("id".to_string(),self.id.to_json());Ok(data)}}#[derive(Default)]pubstructLogOptions{publevel:Option<logging::Level>,}#[derive(Default)]pubstructMarionetteSettings{pubport:Option<u16>,pubbinary:Option<PathBuf>,pubconnect_existing:bool,/// Brings up the Browser Toolbox when starting Firefox,/// letting you debug internals.pubjsdebugger:bool,}pubstructMarionetteHandler{connection:Mutex<Option<MarionetteConnection>>,settings:MarionetteSettings,browser:Option<FirefoxProcess>,}implMarionetteHandler{pubfnnew(settings:MarionetteSettings)->MarionetteHandler{MarionetteHandler{connection:Mutex::new(None),settings,browser:None,}}fncreate_connection(&mutself,session_id:&Option<String>,new_session_parameters:&NewSessionParameters)->WebDriverResult<BTreeMap<String,Json>>{let(options,capabilities)={letmutfx_capabilities=FirefoxCapabilities::new(self.settings.binary.as_ref());letmutcapabilities=try!(try!(new_session_parameters.match_browser(&mutfx_capabilities)).ok_or(WebDriverError::new(ErrorStatus::SessionNotCreated,"Unable to find a matching set of capabilities")));letoptions=try!(FirefoxOptions::from_capabilities(fx_capabilities.chosen_binary,&mutcapabilities));(options,capabilities)};ifletSome(l)=options.log.level{logging::set_max_level(l);}letport=self.settings.port.unwrap_or(try!(get_free_port()));if!self.settings.connect_existing{try!(self.start_browser(port,options));}letmutconnection=MarionetteConnection::new(port,session_id.clone());try!(connection.connect(&mutself.browser));self.connection=Mutex::new(Some(connection));Ok(capabilities)}fnstart_browser(&mutself,port:u16,options:FirefoxOptions)->WebDriverResult<()>{letbinary=options.binary.ok_or(WebDriverError::new(ErrorStatus::SessionNotCreated,"Expected browser binary location, but unable to find \ binary in default location, no \ 'moz:firefoxOptions.binary' capability provided, and \ no binary flag set on the command line"))?;letis_custom_profile=options.profile.is_some();letmutprofile=matchoptions.profile{Some(x)=>x,None=>Profile::new(None)?};self.set_prefs(port,&mutprofile,is_custom_profile,options.prefs).map_err(|e|{WebDriverError::new(ErrorStatus::SessionNotCreated,format!("Failed to set preferences: {}",e))})?;letmutrunner=FirefoxRunner::new(&binary,profile);// https://developer.mozilla.org/docs/Environment_variables_affecting_crash_reportingrunner.env("MOZ_CRASHREPORTER","1").env("MOZ_CRASHREPORTER_NO_REPORT","1").env("MOZ_CRASHREPORTER_SHUTDOWN","1");// double-dashed flags are not accepted on Windows systemsrunner.arg("-marionette");ifself.settings.jsdebugger{runner.arg("-jsdebugger");}ifletSome(args)=options.args.as_ref(){runner.args(args);}letbrowser_proc=runner.start().map_err(|e|{WebDriverError::new(ErrorStatus::SessionNotCreated,format!("Failed to start browser {}: {}",binary.display(),e))})?;self.browser=Some(browser_proc);Ok(())}pubfnset_prefs(&self,port:u16,profile:&mutProfile,custom_profile:bool,extra_prefs:Vec<(String,Pref)>)->WebDriverResult<()>{letprefs=try!(profile.user_prefs().map_err(|_|WebDriverError::new(ErrorStatus::UnknownError,"Unable to read profile preferences file")));for&(refname,refvalue)inprefs::DEFAULT.iter(){if!custom_profile||!prefs.contains_key(name){prefs.insert((*name).clone(),(*value).clone());}}prefs.insert_slice(&extra_prefs[..]);ifself.settings.jsdebugger{prefs.insert("devtools.browsertoolbox.panel",Pref::new("jsdebugger".to_owned()));prefs.insert("devtools.debugger.remote-enabled",Pref::new(true));prefs.insert("devtools.chrome.enabled",Pref::new(true));prefs.insert("devtools.debugger.prompt-connection",Pref::new(false));prefs.insert("marionette.debugging.clicktostart",Pref::new(true));}prefs.insert("marionette.log.level",Pref::new(logging::max_level().to_string()));prefs.insert("marionette.port",Pref::new(portasi64));prefs.write().map_err(|_|WebDriverError::new(ErrorStatus::UnknownError,"Unable to write Firefox profile"))}}implWebDriverHandler<GeckoExtensionRoute>forMarionetteHandler{fnhandle_command(&mutself,_:&Option<Session>,msg:WebDriverMessage<GeckoExtensionRoute>)->WebDriverResult<WebDriverResponse>{letmutresolved_capabilities=None;{letmutcapabilities_options=None;// First handle the status message which doesn't actually require a marionette// connection or messageifmsg.command==Status{let(ready,message)=self.connection.lock().map(|refconnection|connection.as_ref().map(|_|(false,"Session already started")).unwrap_or((true,""))).unwrap_or((false,"geckodriver internal error"));letmutvalue=BTreeMap::new();value.insert("ready".to_string(),Json::Boolean(ready));value.insert("message".to_string(),Json::String(message.into()));returnOk(WebDriverResponse::Generic(ValueResponse::new(Json::Object(value))));}matchself.connection.lock(){Ok(refconnection)=>{ifconnection.is_none(){matchmsg.command{NewSession(refcapabilities)=>{capabilities_options=Some(capabilities);},_=>{returnErr(WebDriverError::new(ErrorStatus::SessionNotCreated,"Tried to run command without establishing a connection"));}}}},Err(_)=>{returnErr(WebDriverError::new(ErrorStatus::UnknownError,"Failed to aquire Marionette connection"))}}ifletSome(capabilities)=capabilities_options{resolved_capabilities=Some(try!(self.create_connection(&msg.session_id,&capabilities)));}}matchself.connection.lock(){Ok(refmutconnection)=>{matchconnection.as_mut(){Some(conn)=>{conn.send_command(resolved_capabilities,&msg).map_err(|muterr|{// Shutdown the browser if no session can// be established due to errors.ifletNewSession(_)=msg.command{err.delete_session=true;}err})},None=>panic!("Connection missing")}},Err(_)=>{Err(WebDriverError::new(ErrorStatus::UnknownError,"Failed to aquire Marionette connection"))}}}fndelete_session(&mutself,session:&Option<Session>){// If there is still an active session send a delete session command// and wait for the browser to quitifletSome(refs)=*session{letdelete_session=WebDriverMessage{session_id:Some(s.id.clone()),command:WebDriverCommand::DeleteSession};let_=self.handle_command(session,delete_session);ifletSome(refmutrunner)=self.browser{lettimeout=TIMEOUT_BROWSER_SHUTDOWN;letpoll_interval=100;letpoll_attempts=timeout/poll_interval;letmutpoll_attempt=0;whilerunner.is_running(){ifpoll_attempt<=poll_attempts{debug!("Waiting for the browser process to shutdown");poll_attempt+=1;sleep(Duration::from_millis(poll_interval));}else{warn!("Browser process did not shutdown");break;}}}}ifletOk(refmutconnection)=self.connection.lock(){ifletSome(conn)=connection.as_mut(){conn.close();}}// If the browser is still open then kill the processifletSome(refmutrunner)=self.browser{ifrunner.is_running(){info!("Forcing a shutdown of the browser process");ifrunner.stop().is_err(){error!("Failed to kill browser process");};}}self.connection=Mutex::new(None);self.browser=None;}}pubstructMarionetteSession{pubsession_id:String,protocol:Option<String>,application_type:Option<String>,command_id:u64}implMarionetteSession{pubfnnew(session_id:Option<String>)->MarionetteSession{letinitital_id=session_id.unwrap_or("".to_string());MarionetteSession{session_id:initital_id,protocol:None,application_type:None,command_id:0}}pubfnupdate(&mutself,msg:&WebDriverMessage<GeckoExtensionRoute>,resp:&MarionetteResponse)->WebDriverResult<()>{matchmsg.command{NewSession(_)=>{letsession_id=try_opt!(try_opt!(resp.result.find("sessionId"),ErrorStatus::SessionNotCreated,"Unable to get session id").as_string(),ErrorStatus::SessionNotCreated,"Unable to convert session id to string");self.session_id=session_id.to_string().clone();},_=>{}}Ok(())}fnto_web_element(&self,json_data:&Json)->WebDriverResult<WebElement>{letdata=try_opt!(json_data.as_object(),ErrorStatus::UnknownError,"Failed to convert data to an object");letid=try_opt!(try_opt!(matchdata.get("ELEMENT"){Some(id)=>Some(id),None=>{matchdata.get(ELEMENT_KEY){Some(id)=>Some(id),None=>None}}},ErrorStatus::UnknownError,"Failed to extract Web Element from response").as_string(),ErrorStatus::UnknownError,"Failed to convert id value to string").to_string();Ok(WebElement::new(id))}pubfnnext_command_id(&mutself)->u64{self.command_id=self.command_id+1;self.command_id}pubfnresponse(&mutself,msg:&WebDriverMessage<GeckoExtensionRoute>,resp:MarionetteResponse)->WebDriverResult<WebDriverResponse>{ifresp.id!=self.command_id{returnErr(WebDriverError::new(ErrorStatus::UnknownError,format!("Marionette responses arrived out of sequence, expected {}, got {}",self.command_id,resp.id)));}ifletSome(error)=resp.error{returnErr(error.into());}try!(self.update(msg,&resp));Ok(matchmsg.command{// Everything that doesn't have a response valueGet(_)|GoBack|GoForward|Refresh|SetTimeouts(_)|SwitchToWindow(_)|SwitchToFrame(_)|SwitchToParentFrame|AddCookie(_)|DeleteCookies|DeleteCookie(_)|DismissAlert|AcceptAlert|SendAlertText(_)|ElementClick(_)|ElementTap(_)|ElementClear(_)|ElementSendKeys(_,_)|PerformActions(_)|ReleaseActions=>{WebDriverResponse::Void},// Things that simply return the contents of the marionette "value" propertyGetCurrentUrl|GetTitle|GetPageSource|GetWindowHandle|IsDisplayed(_)|IsSelected(_)|GetElementAttribute(_,_)|GetElementProperty(_,_)|GetCSSValue(_,_)|GetElementText(_)|GetElementTagName(_)|IsEnabled(_)|ExecuteScript(_)|ExecuteAsyncScript(_)|GetAlertText|TakeScreenshot|TakeElementScreenshot(_)=>{letvalue=try_opt!(resp.result.find("value"),ErrorStatus::UnknownError,"Failed to find value field");//TODO: Convert webelement keysWebDriverResponse::Generic(ValueResponse::new(value.clone()))},GetTimeouts=>{letscript=try_opt!(try_opt!(resp.result.find("script"),ErrorStatus::UnknownError,"Missing field: script").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.letpage_load=try_opt!(try_opt!(resp.result.find("pageLoad").or(resp.result.find("page load")),ErrorStatus::UnknownError,"Missing field: pageLoad").as_u64(),ErrorStatus::UnknownError,"Failed to interpret page load duration as u64");letimplicit=try_opt!(try_opt!(resp.result.find("implicit"),ErrorStatus::UnknownError,"Missing field: implicit").as_u64(),ErrorStatus::UnknownError,"Failed to interpret implicit search duration as u64");WebDriverResponse::Timeouts(TimeoutsResponse{script:script,pageLoad:page_load,implicit:implicit,})},Status=>panic!("Got status command that should already have been handled"),GetWindowHandles=>{WebDriverResponse::Generic(ValueResponse::new(resp.result.clone()))},CloseWindow=>{letdata=try_opt!(resp.result.as_array(),ErrorStatus::UnknownError,"Failed to interpret value as array");lethandles=try!(data.iter().map(|x|{Ok(try_opt!(x.as_string(),ErrorStatus::UnknownError,"Failed to interpret window handle as string").to_owned())}).collect());WebDriverResponse::CloseWindow(CloseWindowResponse{window_handles:handles})},GetElementRect(_)=>{letx=try_opt!(try_opt!(resp.result.find("x"),ErrorStatus::UnknownError,"Failed to find x field").as_f64(),ErrorStatus::UnknownError,"Failed to interpret x as float");lety=try_opt!(try_opt!(resp.result.find("y"),ErrorStatus::UnknownError,"Failed to find y field").as_f64(),ErrorStatus::UnknownError,"Failed to interpret y as float");letwidth=try_opt!(try_opt!(resp.result.find("width"),ErrorStatus::UnknownError,"Failed to find width field").as_f64(),ErrorStatus::UnknownError,"Failed to interpret width as float");letheight=try_opt!(try_opt!(resp.result.find("height"),ErrorStatus::UnknownError,"Failed to find height field").as_f64(),ErrorStatus::UnknownError,"Failed to interpret width as float");letrect=ElementRectResponse{x,y,width,height};WebDriverResponse::ElementRect(rect)},FullscreenWindow|MinimizeWindow|MaximizeWindow|GetWindowRect|SetWindowRect(_)=>{letwidth=try_opt!(try_opt!(resp.result.find("width"),ErrorStatus::UnknownError,"Failed to find width field").as_u64(),ErrorStatus::UnknownError,"Failed to interpret width as positive integer");letheight=try_opt!(try_opt!(resp.result.find("height"),ErrorStatus::UnknownError,"Failed to find heigenht field").as_u64(),ErrorStatus::UnknownError,"Failed to interpret height as positive integer");letx=try_opt!(try_opt!(resp.result.find("x"),ErrorStatus::UnknownError,"Failed to find x field").as_i64(),ErrorStatus::UnknownError,"Failed to interpret x as integer");lety=try_opt!(try_opt!(resp.result.find("y"),ErrorStatus::UnknownError,"Failed to find y field").as_i64(),ErrorStatus::UnknownError,"Failed to interpret y as integer");letrect=WindowRectResponse{x:xasi32,y:yasi32,width:widthasi32,height:heightasi32,};WebDriverResponse::WindowRect(rect)},GetCookies=>{letcookies=try!(self.process_cookies(&resp.result));WebDriverResponse::Cookies(CookiesResponse{value:cookies})},GetNamedCookie(refname)=>{letmutcookies=try!(self.process_cookies(&resp.result));cookies.retain(|x|x.name==*name);letcookie=try_opt!(cookies.pop(),ErrorStatus::NoSuchCookie,format!("No cookie with name {}",name));WebDriverResponse::Cookie(CookieResponse{value:cookie})}FindElement(_)|FindElementElement(_,_)=>{letelement=try!(self.to_web_element(try_opt!(resp.result.find("value"),ErrorStatus::UnknownError,"Failed to find value field")));WebDriverResponse::Generic(ValueResponse::new(element.to_json()))},FindElements(_)|FindElementElements(_,_)=>{letelement_vec=try_opt!(resp.result.as_array(),ErrorStatus::UnknownError,"Failed to interpret value as array");letelements=try!(element_vec.iter().map(|x|{self.to_web_element(x)}).collect::<Result<Vec<_>,_>>());WebDriverResponse::Generic(ValueResponse::new(Json::Array(elements.iter().map(|x|{x.to_json()}).collect())))},GetActiveElement=>{letelement=try!(self.to_web_element(try_opt!(resp.result.find("value"),ErrorStatus::UnknownError,"Failed to find value field")));WebDriverResponse::Generic(ValueResponse::new(element.to_json()))},NewSession(_)=>{letmutsession_id=try_opt!(try_opt!(resp.result.find("sessionId"),ErrorStatus::InvalidSessionId,"Failed to find sessionId field").as_string(),ErrorStatus::InvalidSessionId,"sessionId was not a string");ifsession_id.starts_with("{")&&session_id.ends_with("}"){session_id=&session_id[1..session_id.len()-1];}letcapabilities=try_opt!(try_opt!(resp.result.find("capabilities"),ErrorStatus::UnknownError,"Failed to find capabilities field").as_object(),ErrorStatus::UnknownError,"capabiltites field was not an Object");WebDriverResponse::NewSession(NewSessionResponse::new(session_id.to_string(),Json::Object(capabilities.clone())))},DeleteSession=>{WebDriverResponse::DeleteSession},Extension(refextension)=>{matchextension{&GeckoExtensionCommand::GetContext=>{letvalue=try_opt!(resp.result.find("value"),ErrorStatus::UnknownError,"Failed to find value field");WebDriverResponse::Generic(ValueResponse::new(value.clone()))},&GeckoExtensionCommand::SetContext(_)=>WebDriverResponse::Void,&GeckoExtensionCommand::XblAnonymousChildren(_)=>{letels_vec=try_opt!(resp.result.as_array(),ErrorStatus::UnknownError,"Failed to interpret body as array");letels=try!(els_vec.iter().map(|x|self.to_web_element(x)).collect::<Result<Vec<_>,_>>());WebDriverResponse::Generic(ValueResponse::new(Json::Array(els.iter().map(|el|el.to_json()).collect())))},&GeckoExtensionCommand::XblAnonymousByAttribute(_,_)=>{letel=try!(self.to_web_element(try_opt!(resp.result.find("value"),ErrorStatus::UnknownError,"Failed to find value field")));WebDriverResponse::Generic(ValueResponse::new(el.to_json()))},&GeckoExtensionCommand::InstallAddon(_)=>{letvalue=try_opt!(resp.result.find("value"),ErrorStatus::UnknownError,"Failed to find value field");WebDriverResponse::Generic(ValueResponse::new(value.clone()))},&GeckoExtensionCommand::UninstallAddon(_)=>WebDriverResponse::Void}}})}fnprocess_cookies(&self,json_data:&Json)->WebDriverResult<Vec<Cookie>>{letvalue=try_opt!(json_data.as_array(),ErrorStatus::UnknownError,"Failed to interpret value as array");value.iter().map(|x|{letname=try_opt!(try_opt!(x.find("name"),ErrorStatus::UnknownError,"Cookie must have a name field").as_string(),ErrorStatus::UnknownError,"Cookie must have string name").to_string();letvalue=try_opt!(try_opt!(x.find("value"),ErrorStatus::UnknownError,"Cookie must have a value field").as_string(),ErrorStatus::UnknownError,"Cookie must have a string value").to_string();letpath=try!(Nullable::from_json(x.find("path").unwrap_or(&Json::Null),|x|{Ok((try_opt!(x.as_string(),ErrorStatus::UnknownError,"Cookie path must be string")).to_string())}));letdomain=try!(Nullable::from_json(x.find("domain").unwrap_or(&Json::Null),|x|{Ok((try_opt!(x.as_string(),ErrorStatus::UnknownError,"Cookie domain must be string")).to_string())}));letexpiry=try!(Nullable::from_json(x.find("expiry").unwrap_or(&Json::Null),|x|{Ok(Date::new((try_opt!(x.as_u64(),ErrorStatus::UnknownError,"Cookie expiry must be a positive integer"))))}));letsecure=try_opt!(x.find("secure").map_or(Some(false),|x|x.as_boolean()),ErrorStatus::UnknownError,"Cookie secure flag must be boolean");lethttp_only=try_opt!(x.find("httpOnly").map_or(Some(false),|x|x.as_boolean()),ErrorStatus::UnknownError,"Cookie httpOnly flag must be boolean");letnew_cookie=Cookie{name:name,value:value,path:path,domain:domain,expiry:expiry,secure:secure,httpOnly:http_only,};Ok(new_cookie)}).collect::<Result<Vec<_>,_>>()}}pubstructMarionetteCommand{pubid:u64,pubname:String,pubparams:BTreeMap<String,Json>}implMarionetteCommand{fnnew(id:u64,name:String,params:BTreeMap<String,Json>)->MarionetteCommand{MarionetteCommand{id:id,name:name,params:params,}}fnfrom_webdriver_message(id:u64,capabilities:Option<BTreeMap<String,Json>>,msg:&WebDriverMessage<GeckoExtensionRoute>)->WebDriverResult<MarionetteCommand>{let(opt_name,opt_parameters)=matchmsg.command{NewSession(_)=>{letcaps=capabilities.expect("Tried to create new session without processing capabilities");letmutdata=BTreeMap::new();for(k,v)incaps.iter(){data.insert(k.to_string(),v.to_json());}// duplicate in capabilities.desiredCapabilities for legacy compatletmutlegacy_caps=BTreeMap::new();legacy_caps.insert("desiredCapabilities".to_string(),caps.to_json());data.insert("capabilities".to_string(),legacy_caps.to_json());(Some("newSession"),Some(Ok(data)))},DeleteSession=>{letmutbody=BTreeMap::new();body.insert("flags".to_owned(),vec!["eForceQuit".to_json()].to_json());(Some("quit"),Some(Ok(body)))},Status=>panic!("Got status command that should already have been handled"),Get(refx)=>(Some("get"),Some(x.to_marionette())),GetCurrentUrl=>(Some("getCurrentUrl"),None),GoBack=>(Some("goBack"),None),GoForward=>(Some("goForward"),None),Refresh=>(Some("refresh"),None),GetTitle=>(Some("getTitle"),None),GetPageSource=>(Some("getPageSource"),None),GetWindowHandle=>(Some("getWindowHandle"),None),GetWindowHandles=>(Some("getWindowHandles"),None),CloseWindow=>(Some("close"),None),GetTimeouts=>(Some("getTimeouts"),None),SetTimeouts(refx)=>(Some("setTimeouts"),Some(x.to_marionette())),SetWindowRect(refx)=>(Some("setWindowRect"),Some(x.to_marionette())),GetWindowRect=>(Some("getWindowRect"),None),MinimizeWindow=>(Some("WebDriver:MinimizeWindow"),None),MaximizeWindow=>(Some("maximizeWindow"),None),FullscreenWindow=>(Some("fullscreen"),None),SwitchToWindow(refx)=>(Some("switchToWindow"),Some(x.to_marionette())),SwitchToFrame(refx)=>(Some("switchToFrame"),Some(x.to_marionette())),SwitchToParentFrame=>(Some("switchToParentFrame"),None),FindElement(refx)=>(Some("findElement"),Some(x.to_marionette())),FindElements(refx)=>(Some("findElements"),Some(x.to_marionette())),FindElementElement(refe,refx)=>{letmutdata=try!(x.to_marionette());data.insert("element".to_string(),e.id.to_json());(Some("findElement"),Some(Ok(data)))},FindElementElements(refe,refx)=>{letmutdata=try!(x.to_marionette());data.insert("element".to_string(),e.id.to_json());(Some("findElements"),Some(Ok(data)))},GetActiveElement=>(Some("getActiveElement"),None),IsDisplayed(refx)=>(Some("isElementDisplayed"),Some(x.to_marionette())),IsSelected(refx)=>(Some("isElementSelected"),Some(x.to_marionette())),GetElementAttribute(refe,refx)=>{letmutdata=BTreeMap::new();data.insert("id".to_string(),e.id.to_json());data.insert("name".to_string(),x.to_json());(Some("getElementAttribute"),Some(Ok(data)))},GetElementProperty(refe,refx)=>{letmutdata=BTreeMap::new();data.insert("id".to_string(),e.id.to_json());data.insert("name".to_string(),x.to_json());(Some("getElementProperty"),Some(Ok(data)))},GetCSSValue(refe,refx)=>{letmutdata=BTreeMap::new();data.insert("id".to_string(),e.id.to_json());data.insert("propertyName".to_string(),x.to_json());(Some("getElementValueOfCssProperty"),Some(Ok(data)))},GetElementText(refx)=>(Some("getElementText"),Some(x.to_marionette())),GetElementTagName(refx)=>(Some("getElementTagName"),Some(x.to_marionette())),GetElementRect(refx)=>(Some("getElementRect"),Some(x.to_marionette())),IsEnabled(refx)=>(Some("isElementEnabled"),Some(x.to_marionette())),PerformActions(refx)=>(Some("performActions"),Some(x.to_marionette())),ReleaseActions=>(Some("releaseActions"),None),ElementClick(refx)=>(Some("clickElement"),Some(x.to_marionette())),ElementTap(refx)=>(Some("singleTap"),Some(x.to_marionette())),ElementClear(refx)=>(Some("clearElement"),Some(x.to_marionette())),ElementSendKeys(refe,refx)=>{letmutdata=BTreeMap::new();data.insert("id".to_string(),e.id.to_json());data.insert("text".to_string(),x.text.to_json());data.insert("value".to_string(),x.text.chars().map(|x|x.to_string()).collect::<Vec<String>>().to_json());(Some("sendKeysToElement"),Some(Ok(data)))},ExecuteScript(refx)=>(Some("executeScript"),Some(x.to_marionette())),ExecuteAsyncScript(refx)=>(Some("executeAsyncScript"),Some(x.to_marionette())),GetCookies|GetNamedCookie(_)=>(Some("getCookies"),None),DeleteCookies=>(Some("deleteAllCookies"),None),DeleteCookie(refx)=>{letmutdata=BTreeMap::new();data.insert("name".to_string(),x.to_json());(Some("deleteCookie"),Some(Ok(data)))},AddCookie(refx)=>(Some("addCookie"),Some(x.to_marionette())),DismissAlert=>(Some("dismissDialog"),None),AcceptAlert=>(Some("acceptDialog"),None),GetAlertText=>(Some("getTextFromDialog"),None),SendAlertText(refx)=>{letmutdata=BTreeMap::new();data.insert("text".to_string(),x.text.to_json());data.insert("value".to_string(),x.text.chars().map(|x|x.to_string()).collect::<Vec<String>>().to_json());(Some("sendKeysToDialog"),Some(Ok(data)))},TakeScreenshot=>{letmutdata=BTreeMap::new();data.insert("id".to_string(),Json::Null);data.insert("highlights".to_string(),Json::Array(vec![]));data.insert("full".to_string(),Json::Boolean(false));(Some("takeScreenshot"),Some(Ok(data)))},TakeElementScreenshot(refe)=>{letmutdata=BTreeMap::new();data.insert("id".to_string(),e.id.to_json());data.insert("highlights".to_string(),Json::Array(vec![]));data.insert("full".to_string(),Json::Boolean(false));(Some("takeScreenshot"),Some(Ok(data)))},Extension(refextension)=>{matchextension{&GeckoExtensionCommand::GetContext=>(Some("getContext"),None),&GeckoExtensionCommand::SetContext(refx)=>{(Some("setContext"),Some(x.to_marionette()))},&GeckoExtensionCommand::XblAnonymousChildren(refe)=>{letmutdata=BTreeMap::new();data.insert("using".to_owned(),"anon".to_json());data.insert("value".to_owned(),Json::Null);data.insert("element".to_string(),e.id.to_json());(Some("findElements"),Some(Ok(data)))},&GeckoExtensionCommand::XblAnonymousByAttribute(refe,refx)=>{letmutdata=try!(x.to_marionette());data.insert("element".to_string(),e.id.to_json());(Some("findElement"),Some(Ok(data)))},&GeckoExtensionCommand::InstallAddon(refx)=>{(Some("addon:install"),Some(x.to_marionette()))},&GeckoExtensionCommand::UninstallAddon(refx)=>{(Some("addon:uninstall"),Some(x.to_marionette()))}}}};letname=try_opt!(opt_name,ErrorStatus::UnsupportedOperation,"Operation not supported");letparameters=try!(opt_parameters.unwrap_or(Ok(BTreeMap::new())));Ok(MarionetteCommand::new(id,name.into(),parameters))}}implToJsonforMarionetteCommand{fnto_json(&self)->Json{Json::Array(vec![Json::U64(0),self.id.to_json(),self.name.to_json(),self.params.to_json()])}}pubstructMarionetteResponse{pubid:u64,puberror:Option<MarionetteError>,pubresult:Json,}implMarionetteResponse{fnfrom_json(data:&Json)->WebDriverResult<MarionetteResponse>{letdata_array=try_opt!(data.as_array(),ErrorStatus::UnknownError,"Expected a json array");ifdata_array.len()!=4{returnErr(WebDriverError::new(ErrorStatus::UnknownError,"Expected an array of length 4"));}ifdata_array[0].as_u64()!=Some(1){returnErr(WebDriverError::new(ErrorStatus::UnknownError,"Expected 1 in first element of response"));};letid=try_opt!(data[1].as_u64(),ErrorStatus::UnknownError,"Expected an integer id");leterror=ifdata[2].is_object(){Some(try!(MarionetteError::from_json(&data[2])))}elseifdata[2].is_null(){None}else{returnErr(WebDriverError::new(ErrorStatus::UnknownError,"Expected object or null error"));};letresult=ifdata[3].is_null()||data[3].is_object()||data[3].is_array(){data[3].clone()}else{returnErr(WebDriverError::new(ErrorStatus::UnknownError,"Expected object params"));};Ok(MarionetteResponse{id:id,error:error,result:result})}}implToJsonforMarionetteResponse{fnto_json(&self)->Json{Json::Array(vec![Json::U64(1),self.id.to_json(),self.error.to_json(),self.result.clone()])}}#[derive(RustcEncodable, RustcDecodable)]pubstructMarionetteError{pubcode:String,pubmessage:String,pubstacktrace:Option<String>}implMarionetteError{fnfrom_json(data:&Json)->WebDriverResult<MarionetteError>{if!data.is_object(){returnErr(WebDriverError::new(ErrorStatus::UnknownError,"Expected an error object"));}letcode=try_opt!(try_opt!(data.find("error"),ErrorStatus::UnknownError,"Error value has no error code").as_string(),ErrorStatus::UnknownError,"Error status was not a string").into();letmessage=try_opt!(try_opt!(data.find("message"),ErrorStatus::UnknownError,"Error value has no message").as_string(),ErrorStatus::UnknownError,"Error message was not a string").into();letstacktrace=matchdata.find("stacktrace"){None|Some(&Json::Null)=>None,Some(x)=>Some(try_opt!(x.as_string(),ErrorStatus::UnknownError,"Error message was not a string").into()),};Ok(MarionetteError{code,message,stacktrace})}}implToJsonforMarionetteError{fnto_json(&self)->Json{letmutdata=BTreeMap::new();data.insert("error".into(),self.code.to_json());data.insert("message".into(),self.message.to_json());data.insert("stacktrace".into(),self.stacktrace.to_json());Json::Object(data)}}implInto<WebDriverError>forMarionetteError{fninto(self)->WebDriverError{letstatus=ErrorStatus::from(self.code);letmessage=self.message;ifletSome(stack)=self.stacktrace{WebDriverError::new_with_stack(status,message,stack)}else{WebDriverError::new(status,message)}}}fnget_free_port()->IoResult<u16>{TcpListener::bind(&("localhost",0)).and_then(|stream|stream.local_addr()).map(|x|x.port())}pubstructMarionetteConnection{port:u16,stream:Option<TcpStream>,pubsession:MarionetteSession}implMarionetteConnection{pubfnnew(port:u16,session_id:Option<String>)->MarionetteConnection{MarionetteConnection{port:port,stream:None,session:MarionetteSession::new(session_id)}}pubfnconnect(&mutself,browser:&mutOption<FirefoxProcess>)->WebDriverResult<()>{lettimeout=60*1000;// msletpoll_interval=100;// msletpoll_attempts=timeout/poll_interval;letmutpoll_attempt=0;loop{// If the process is gone, immediately abort the connection attemptsiflet&mutSome(refmutrunner)=browser{letstatus=runner.status();ifstatus.is_err()||status.as_ref().map(|x|*x).unwrap_or(None)!=None{returnErr(WebDriverError::new(ErrorStatus::UnknownError,format!("Process unexpectedly closed with status: {}",status.ok().and_then(|x|x).and_then(|x|x.code()).map(|x|x.to_string()).unwrap_or("{unknown}".into()))));}}matchTcpStream::connect(&(DEFAULT_HOST,self.port)){Ok(stream)=>{self.stream=Some(stream);break},Err(e)=>{trace!(" connection attempt {}/{}",poll_attempt,poll_attempts);ifpoll_attempt<=poll_attempts{poll_attempt+=1;sleep(Duration::from_millis(poll_interval));}else{returnErr(WebDriverError::new(ErrorStatus::UnknownError,e.description().to_owned()));}}}};debug!("Connected to Marionette on {}:{}",DEFAULT_HOST,self.port);try!(self.handshake());Ok(())}fnhandshake(&mutself)->WebDriverResult<()>{letresp=try!(self.read_resp());lethandshake_data=try!(Json::from_str(&*resp));letdata=try_opt!(handshake_data.as_object(),ErrorStatus::UnknownError,"Expected a json object in handshake");self.session.protocol=Some(try_opt!(data.get("marionetteProtocol"),ErrorStatus::UnknownError,"Missing 'marionetteProtocol' field in handshake").to_string());self.session.application_type=Some(try_opt!(data.get("applicationType"),ErrorStatus::UnknownError,"Missing 'applicationType' field in handshake").to_string());ifself.session.protocol!=Some("3".into()){returnErr(WebDriverError::new(ErrorStatus::UnknownError,format!("Unsupported Marionette protocol version {}, required 3",self.session.protocol.as_ref().unwrap_or(&"<unknown>".into()))));}Ok(())}pubfnclose(&self){}fnencode_msg(&self,msg:Json)->String{letdata=json::encode(&msg).unwrap();format!("{}:{}",data.len(),data)}pubfnsend_command(&mutself,capabilities:Option<BTreeMap<String,Json>>,msg:&WebDriverMessage<GeckoExtensionRoute>)->WebDriverResult<WebDriverResponse>{letid=self.session.next_command_id();letcommand=try!(MarionetteCommand::from_webdriver_message(id,capabilities,msg));letresp_data=try!(self.send(command.to_json()));letjson_data:Json=try!(Json::from_str(&*resp_data));self.session.response(msg,try!(MarionetteResponse::from_json(&json_data)))}fnsend(&mutself,msg:Json)->WebDriverResult<String>{letdata=self.encode_msg(msg);trace!("-> {}",data);matchself.stream{Some(refmutstream)=>{ifstream.write(&*data.as_bytes()).is_err(){letmuterr=WebDriverError::new(ErrorStatus::UnknownError,"Failed to write response to stream");err.delete_session=true;returnErr(err);}}None=>{letmuterr=WebDriverError::new(ErrorStatus::UnknownError,"Tried to write before opening stream");err.delete_session=true;returnErr(err);}}matchself.read_resp(){Ok(resp)=>Ok(resp),Err(_)=>{letmuterr=WebDriverError::new(ErrorStatus::UnknownError,"Failed to decode response from marionette");err.delete_session=true;Err(err)}}}fnread_resp(&mutself)->IoResult<String>{letmutbytes=0usize;// TODO(jgraham): Check before we unwrap?letstream=self.stream.as_mut().unwrap();loop{letbuf=&mut[0asu8];letnum_read=try!(stream.read(buf));letbyte=matchnum_read{0=>{returnErr(IoError::new(ErrorKind::Other,"EOF reading marionette message",))}1=>buf[0]aschar,_=>panic!("Expected one byte got more"),};matchbyte{'0'...'9'=>{bytes=bytes*10;bytes+=byteasusize-'0'asusize;}':'=>break,_=>{}}}letbuf=&mut[0asu8;8192];letmutpayload=Vec::with_capacity(bytes);letmuttotal_read=0;whiletotal_read<bytes{letnum_read=try!(stream.read(buf));ifnum_read==0{returnErr(IoError::new(ErrorKind::Other,"EOF reading marionette message"))}total_read+=num_read;forxin&buf[..num_read]{payload.push(*x);}}// TODO(jgraham): Need to handle the error hereletdata=String::from_utf8(payload).unwrap();trace!("<- {}",data);Ok(data)}}traitToMarionette{fnto_marionette(&self)->WebDriverResult<BTreeMap<String,Json>>;}implToMarionetteforGetParameters{fnto_marionette(&self)->WebDriverResult<BTreeMap<String,Json>>{Ok(try_opt!(self.to_json().as_object(),ErrorStatus::UnknownError,"Expected an object").clone())}}implToMarionetteforTimeoutsParameters{fnto_marionette(&self)->WebDriverResult<BTreeMap<String,Json>>{Ok(try_opt!(self.to_json().as_object(),ErrorStatus::UnknownError,"Expected an object").clone())}}implToMarionetteforWindowRectParameters{fnto_marionette(&self)->WebDriverResult<BTreeMap<String,Json>>{Ok(try_opt!(self.to_json().as_object(),ErrorStatus::UnknownError,"Expected an object").clone())}}implToMarionetteforSwitchToWindowParameters{fnto_marionette(&self)->WebDriverResult<BTreeMap<String,Json>>{letmutdata=BTreeMap::new();data.insert("name".to_string(),self.handle.to_json());Ok(data)}}implToMarionetteforLocatorParameters{fnto_marionette(&self)->WebDriverResult<BTreeMap<String,Json>>{Ok(try_opt!(self.to_json().as_object(),ErrorStatus::UnknownError,"Expected an object").clone())}}implToMarionetteforSwitchToFrameParameters{fnto_marionette(&self)->WebDriverResult<BTreeMap<String,Json>>{letmutdata=BTreeMap::new();letkey=matchself.id{FrameId::Null=>None,FrameId::Short(_)=>Some("id"),FrameId::Element(_)=>Some("element"),};ifletSome(x)=key{data.insert(x.to_string(),self.id.to_json());}Ok(data)}}implToMarionetteforJavascriptCommandParameters{fnto_marionette(&self)->WebDriverResult<BTreeMap<String,Json>>{letmutdata=self.to_json().as_object().unwrap().clone();data.insert("newSandbox".to_string(),false.to_json());data.insert("specialPowers".to_string(),false.to_json());data.insert("scriptTimeout".to_string(),Json::Null);Ok(data)}}implToMarionetteforActionsParameters{fnto_marionette(&self)->WebDriverResult<BTreeMap<String,Json>>{Ok(try_opt!(self.to_json().as_object(),ErrorStatus::UnknownError,"Expected an object").clone())}}implToMarionetteforGetNamedCookieParameters{fnto_marionette(&self)->WebDriverResult<BTreeMap<String,Json>>{Ok(try_opt!(self.to_json().as_object(),ErrorStatus::UnknownError,"Expected an object").clone())}}implToMarionetteforAddCookieParameters{fnto_marionette(&self)->WebDriverResult<BTreeMap<String,Json>>{letmutcookie=BTreeMap::new();cookie.insert("name".to_string(),self.name.to_json());cookie.insert("value".to_string(),self.value.to_json());ifself.path.is_value(){cookie.insert("path".to_string(),self.path.to_json());}ifself.domain.is_value(){cookie.insert("domain".to_string(),self.domain.to_json());}ifself.expiry.is_value(){cookie.insert("expiry".to_string(),self.expiry.to_json());}cookie.insert("secure".to_string(),self.secure.to_json());cookie.insert("httpOnly".to_string(),self.httpOnly.to_json());letmutdata=BTreeMap::new();data.insert("cookie".to_string(),Json::Object(cookie));Ok(data)}}implToMarionetteforTakeScreenshotParameters{fnto_marionette(&self)->WebDriverResult<BTreeMap<String,Json>>{letmutdata=BTreeMap::new();letelement=matchself.element{Nullable::Null=>Json::Null,Nullable::Value(refx)=>Json::Object(try!(x.to_marionette()))};data.insert("element".to_string(),element);Ok(data)}}implToMarionetteforWebElement{fnto_marionette(&self)->WebDriverResult<BTreeMap<String,Json>>{letmutdata=BTreeMap::new();data.insert("id".to_string(),self.id.to_json());Ok(data)}}impl<T:ToJson>ToMarionetteforNullable<T>{fnto_marionette(&self)->WebDriverResult<BTreeMap<String,Json>>{//Note this is a terrible hack. We don't want Nullable<T: ToJson+ToMarionette>//so in cases where ToJson != ToMarionette you have to deal with the Nullable//explicitly. This kind of suggests that the whole design is wrong.Ok(try_opt!(self.to_json().as_object(),ErrorStatus::UnknownError,"Expected an object").clone())}}implToMarionetteforFrameId{fnto_marionette(&self)->WebDriverResult<BTreeMap<String,Json>>{letmutdata=BTreeMap::new();match*self{FrameId::Short(x)=>data.insert("id".to_string(),x.to_json()),FrameId::Element(refx)=>data.insert("element".to_string(),Json::Object(try!(x.to_marionette()))),FrameId::Null=>None};Ok(data)}}#[cfg(test)]modtests{usemarionette::{AddonInstallParameters,Parameters};userustc_serialize::json::Json;usestd::io::Read;usestd::fs::File;usewebdriver::error::WebDriverResult;#[test]fntest_addon_install_params_missing_path(){letjson_data:Json=Json::from_str(r#"{"temporary": true}"#).unwrap();letres:WebDriverResult<AddonInstallParameters>=Parameters::from_json(&json_data);assert!(res.is_err());}#[test]fntest_addon_install_params_with_both_path_and_base64(){letjson_data:Json=Json::from_str(r#"{"path": "/path/to.xpi", "addon": "aGVsbG8=", "temporary": true}"#).unwrap();letres:WebDriverResult<AddonInstallParameters>=Parameters::from_json(&json_data);assert!(res.is_err());}#[test]fntest_addon_install_params_with_path(){letjson_data:Json=Json::from_str(r#"{"path": "/path/to.xpi", "temporary": true}"#).unwrap();letparameters:AddonInstallParameters=Parameters::from_json(&json_data).unwrap();assert_eq!(parameters.path,"/path/to.xpi");assert_eq!(parameters.temporary,true);}#[test]fntest_addon_install_params_with_base64(){letjson_data:Json=Json::from_str(r#"{"addon": "aGVsbG8=", "temporary": true}"#).unwrap();letparameters:AddonInstallParameters=Parameters::from_json(&json_data).unwrap();assert_eq!(parameters.temporary,true);letmutfile=File::open(parameters.path).unwrap();letmutcontents=String::new();file.read_to_string(&mutcontents).unwrap();assert_eq!("hello",contents);}}