geckodriver: Merge pull request #162 from mozilla/firefox_prefs
authorJames Graham <james@hoppipolla.co.uk>
Tue, 06 Sep 2016 17:15:14 +0100
changeset 407679 56f959b9aec5fe316055a59b0435e225854760e0
parent 407678 cb88e73b57d35fce734aba488118681f1a467b36
child 407680 bdcb1480d612d1d3036a7c59f372cb69b0c558d0
push id7391
push usermtabara@mozilla.com
push dateMon, 12 Jun 2017 13:08:53 +0000
treeherdermozilla-beta@2191d7f87e2e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone55.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
geckodriver: Merge pull request #162 from mozilla/firefox_prefs Add prefs capability to firefoxOptions Source-Repo: https://github.com/mozilla/geckodriver Source-Revision: 804def9600fc9a486a78b7cc2a2527661296b8af
testing/geckodriver/README.md
testing/geckodriver/src/main.rs
testing/geckodriver/src/marionette.rs
--- a/testing/geckodriver/README.md
+++ b/testing/geckodriver/README.md
@@ -71,16 +71,23 @@ contain any of the following fields:
     <tr>
         <td><code>profile</code>
         <td>String
         <td>New, empty profile
         <td>Base64-encoded zip of a profile directory
           to use as the profile for the Firefox instance.
           This may be used to e.g. install extensions or custom certificates.
     </tr>
+    <tr>
+        <td><code>prefs</code>
+        <td>Object &lt;string, (string|boolean|integer)&gt
+        <td>
+        <td>Map of preference name to preference value, which can be a
+            string, a boolean or an integer.
+    </tr>
 </table>
 
 ## Building
 
 geckodriver is written in [Rust](https://www.rust-lang.org/)
 and you need the [Rust toolchain](https://rustup.rs/) to compile it.
 
 To build the project for release,
--- a/testing/geckodriver/src/main.rs
+++ b/testing/geckodriver/src/main.rs
@@ -165,50 +165,87 @@ fn main() {
 
     std::io::stdout().flush().unwrap();
     std::process::exit(exit_code as i32);
 }
 
 #[cfg(test)]
 mod tests {
     use std::collections::BTreeMap;
-    use marionette::{FirefoxOptions};
+    use marionette::{FirefoxOptions, MarionetteHandler};
     use webdriver::command::NewSessionParameters;
     use rustc_serialize::json::Json;
     use std::fs::File;
     use rustc_serialize::base64::{ToBase64, Config, CharacterSet, Newline};
     use mozprofile::preferences::Pref;
     use std::io::Read;
+    use std::default::Default;
 
-    #[test]
-    fn test_profile() {
+    fn example_profile() -> Json {
         let mut profile_data = Vec::with_capacity(1024);
         let mut profile = File::open("src/tests/profile.zip").unwrap();
         profile.read_to_end(&mut profile_data).unwrap();
         let base64_config = Config {
             char_set: CharacterSet::Standard,
             newline: Newline::LF,
             pad: true,
             line_length: None
         };
-        let encoded_profile = Json::String(profile_data.to_base64(base64_config));
+        Json::String(profile_data.to_base64(base64_config))
+    }
 
+    fn capabilities() -> NewSessionParameters {
         let desired: BTreeMap<String, Json> = BTreeMap::new();
-        let mut required: BTreeMap<String, Json> = BTreeMap::new();
+        let required: BTreeMap<String, Json> = BTreeMap::new();
+        NewSessionParameters {
+            desired: desired,
+            required: required
+        }
+    }
+
+    #[test]
+    fn test_profile() {
+        let encoded_profile = example_profile();
+
+        let mut capabilities = capabilities();
         let mut firefox_options: BTreeMap<String, Json> = BTreeMap::new();
         firefox_options.insert("profile".into(), encoded_profile);
-        required.insert("firefoxOptions".into(), Json::Object(firefox_options));
-        let mut capabilities = NewSessionParameters {
-            desired: desired,
-            required: required
-        };
+        capabilities.required.insert("firefoxOptions".into(), Json::Object(firefox_options));
 
         let options = FirefoxOptions::from_capabilities(&mut capabilities).unwrap();
         let mut profile = options.profile.unwrap();
         let prefs = profile.user_prefs().unwrap();
 
         println!("{:?}",prefs.prefs);
 
         assert_eq!(prefs.get("startup.homepage_welcome_url"),
                    Some(&Pref::new("data:text/html,PASS")));
     }
 
+    #[test]
+    fn test_prefs() {
+        let encoded_profile = example_profile();
+
+        let mut capabilities = capabilities();
+        let mut firefox_options: BTreeMap<String, Json> = BTreeMap::new();
+        firefox_options.insert("profile".into(), encoded_profile);
+        let mut prefs: BTreeMap<String, Json> = BTreeMap::new();
+        prefs.insert("browser.display.background_color".into(), Json::String("#00ff00".into()));
+        firefox_options.insert("prefs".into(), Json::Object(prefs));
+        capabilities.required.insert("firefoxOptions".into(), Json::Object(firefox_options));
+
+
+        let options = FirefoxOptions::from_capabilities(&mut capabilities).unwrap();
+        let mut profile = options.profile.unwrap();
+
+        let handler = MarionetteHandler::new(Default::default());
+        handler.set_prefs(2828, &mut profile, true, options.prefs).unwrap();
+
+        let prefs_set = profile.user_prefs().unwrap();
+        println!("{:?}",prefs_set.prefs);
+        assert_eq!(prefs_set.get("startup.homepage_welcome_url"),
+                   Some(&Pref::new("data:text/html,PASS")));
+        assert_eq!(prefs_set.get("browser.display.background_color"),
+                   Some(&Pref::new("#00ff00")));
+        assert_eq!(prefs_set.get("marionette.defaultPrefs.port"),
+                   Some(&Pref::new(2828)));
+    }
 }
--- a/testing/geckodriver/src/marionette.rs
+++ b/testing/geckodriver/src/marionette.rs
@@ -1,12 +1,12 @@
 use hyper::method::Method;
 use mozprofile::preferences::Pref;
 use mozprofile::profile::Profile;
-use mozrunner::runner::{Runner, FirefoxRunner, RunnerError};
+use mozrunner::runner::{Runner, FirefoxRunner};
 use mozrunner::runner::platform::firefox_default_path;
 use regex::Captures;
 use rustc_serialize::base64::FromBase64;
 use rustc_serialize::json::{Json, ToJson};
 use rustc_serialize::json;
 use std::collections::BTreeMap;
 use std::error::Error;
 use std::fs;
@@ -339,34 +339,37 @@ impl FromStr for LogLevel {
     }
 }
 
 #[derive(Default)]
 pub struct FirefoxOptions {
     pub binary: Option<PathBuf>,
     pub profile: Option<Profile>,
     pub args: Option<Vec<String>>,
+    pub prefs: Vec<(String, Pref)>
 }
 
 
 impl FirefoxOptions {
     pub fn from_capabilities(capabilities: &mut NewSessionParameters) -> WebDriverResult<FirefoxOptions> {
         if let Some(options) = capabilities.consume("firefoxOptions") {
             let firefox_options = try!(options
                                        .as_object()
                                        .ok_or(WebDriverError::new(
                                            ErrorStatus::InvalidArgument,
                                            "'firefoxOptions' capability was not an object")));
             let binary = try!(FirefoxOptions::load_binary(&firefox_options));
             let profile = try!(FirefoxOptions::load_profile(&firefox_options));
             let args = try!(FirefoxOptions::load_args(&firefox_options));
+            let prefs = try!(FirefoxOptions::load_prefs(&firefox_options));
             Ok(FirefoxOptions {
                 binary: binary,
                 profile: profile,
                 args: args,
+                prefs: prefs,
             })
         } else {
             Ok(Default::default())
         }
     }
 
     fn load_binary(options: &BTreeMap<String, Json>) -> WebDriverResult<Option<PathBuf>> {
         if let Some(path) = options.get("binary") {
@@ -415,16 +418,31 @@ impl FirefoxOptions {
                             .ok_or(WebDriverError::new(
                                 ErrorStatus::UnknownError,
                                 "Arguments entries were not all strings")));
             Ok(Some(args))
         } else {
             Ok(None)
         }
     }
+
+    pub fn load_prefs(options: &BTreeMap<String, Json>) -> WebDriverResult<Vec<(String, Pref)>> {
+        if let Some(prefs_data) = options.get("prefs") {
+            let prefs = try!(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))));
+            };
+            Ok(rv)
+        } else {
+            Ok(vec![])
+        }
+    }
 }
 
 pub struct MarionetteSettings {
     pub port: Option<u16>,
     pub binary: Option<PathBuf>,
     pub connect_existing: bool,
 
     /// Optionally increase Marionette's verbosity by providing a log
@@ -456,25 +474,25 @@ impl MarionetteHandler {
             connection: Mutex::new(None),
             settings: settings,
             browser: None,
         }
     }
 
     fn create_connection(&mut self, session_id: &Option<String>,
                          capabilities: &mut NewSessionParameters) -> WebDriverResult<()> {
-
         let options = try!(FirefoxOptions::from_capabilities(capabilities));
 
         let port = self.settings.port.unwrap_or(try!(get_free_port()));
 
         if !self.settings.connect_existing {
             try!(self.start_browser(port, options));
         };
 
+
         let mut connection = MarionetteConnection::new(port, session_id.clone());
         try!(connection.connect());
         self.connection = Mutex::new(Some(connection));
 
         Ok(())
     }
 
     fn start_browser(&mut self, port: u16, mut options: FirefoxOptions) -> WebDriverResult<()> {
@@ -489,17 +507,17 @@ impl MarionetteHandler {
 
         let mut runner = try!(FirefoxRunner::new(&binary, options.profile.take())
                               .map_err(|e| WebDriverError::new(ErrorStatus::UnknownError,
                                                                e.description().to_owned())));
         if let Some(args) = options.args.take() {
             runner.args().extend(args);
         };
 
-        try!(self.set_prefs(port, &mut runner.profile, custom_profile)
+        try!(self.set_prefs(port, &mut runner.profile, custom_profile, options.prefs)
              .map_err(|e| WebDriverError::new(ErrorStatus::UnknownError,
                                               format!("Failed to set preferences:\n{}",
                                                       e.description()))));
 
         info!("Starting browser {}", binary.to_string_lossy());
         try!(runner.start()
              .map_err(|e| WebDriverError::new(ErrorStatus::UnknownError,
                                               format!("Failed to start browser:\n{}",
@@ -511,31 +529,50 @@ impl MarionetteHandler {
     }
 
     fn binary_path(&self, options: &mut FirefoxOptions) -> Option<PathBuf> {
         options.binary.take()
             .or_else(|| self.settings.binary.as_ref().map(|x| x.clone()))
             .or_else(|| firefox_default_path())
     }
 
-    pub fn set_prefs(&self, port: u16, profile: &mut Profile, custom_profile: bool)
-                 -> Result<(), RunnerError> {
-        let prefs = try!(profile.user_prefs());
+
+    pub fn set_prefs(&self, port: u16, profile: &mut Profile, custom_profile: bool,
+                     extra_prefs: Vec<(String, Pref)>)
+                 -> WebDriverResult<()> {
+        let prefs = try!(profile.user_prefs()
+                         .map_err(|_| WebDriverError::new(ErrorStatus::UnknownError,
+                                                          "Unable to read profile preferences file")));
+
         prefs.insert("marionette.defaultPrefs.port", Pref::new(port as i64));
 
-        prefs.insert_slice(&FIREFOX_REQUIRED_PREFERENCES[..]);
         if !custom_profile {
             prefs.insert_slice(&FIREFOX_DEFAULT_PREFERENCES[..]);
         };
-        if let Some(ref l) = self.settings.log_level {
-            prefs.insert("marionette.logging", Pref::new(l.to_string()));
+        prefs.insert_slice(&extra_prefs[..]);
+
+        prefs.insert_slice(&FIREFOX_REQUIRED_PREFERENCES[..]);
+
+        if let Some(ref level) = self.settings.log_level {
+            prefs.insert("marionette.logging", Pref::new(level.to_string()));
         };
 
-        try!(prefs.write());
-        Ok(())
+        prefs.write().map_err(|_| WebDriverError::new(ErrorStatus::UnknownError,
+                                                      "Unable to write Firefox profile"))
+    }
+}
+
+fn pref_from_json(value: &Json) -> WebDriverResult<Pref> {
+    match value {
+        &Json::String(ref x) => Ok(Pref::new(x.clone())),
+        &Json::I64(x) => Ok(Pref::new(x)),
+        &Json::U64(x) => Ok(Pref::new(x as i64)),
+        &Json::Boolean(x) => Ok(Pref::new(x)),
+        _ => Err(WebDriverError::new(ErrorStatus::UnknownError,
+                                     "Could not convert pref value to string, boolean, or integer"))
     }
 }
 
 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")
     }));