bug 1483996: geckodriver: add --marionette-host flag; r=ato
authorMarc Fisher <fisherii@google.com>
Fri, 07 Dec 2018 18:31:41 +0000
changeset 449667 b2eb9000daed2a2cff623eae8ca2387bb59e4601
parent 449666 d4f6a34f0d1ceb00f224c818cedc132dfe4b2582
child 449668 904080f44dbd2ae3cc92c794a817b281d6cb7237
push idunknown
push userunknown
push dateunknown
reviewersato
bugs1483996
milestone65.0a1
bug 1483996: geckodriver: add --marionette-host flag; r=ato
testing/geckodriver/CHANGES.md
testing/geckodriver/src/main.rs
testing/geckodriver/src/marionette.rs
--- a/testing/geckodriver/CHANGES.md
+++ b/testing/geckodriver/CHANGES.md
@@ -15,16 +15,19 @@ Unreleased
   should be applied to `<input type=file>` elements.  As strict
   interactability checks are off by default, there is a change
   in behaviour when using [Element Send Keys] with hidden file
   upload controls.
 
 - Added new endpoint `GET /session/{session id}/moz/screenshot/full`
   for taking full document screenshots, thanks to Greg Fraley.
 
+- Added new `--marionette-host <HOSTNAME>` flag for binding to a
+  particular interface/IP layer on the system.
+
 # Changed
 
 - Allow file uploads to hidden `<input type=file>` elements
 
   Through a series of changes to the WebDriver specification,
   geckodriver is now aligned with chromedriver’s behaviour that
   allows interaction with hidden `<input type=file>` elements.
 
--- a/testing/geckodriver/src/main.rs
+++ b/testing/geckodriver/src/main.rs
@@ -91,20 +91,27 @@ fn app<'a, 'b>() -> App<'a, 'b> {
             Arg::with_name("binary")
                 .short("b")
                 .long("binary")
                 .value_name("BINARY")
                 .help("Path to the Firefox binary")
                 .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(
             Arg::with_name("connect_existing")
                 .long("connect-existing")
                 .requires("marionette_port")
                 .help("Connect to an existing Firefox instance"),
         )
@@ -157,16 +164,18 @@ fn run() -> ProgramResult {
     };
     let addr = match IpAddr::from_str(host) {
         Ok(addr) => SocketAddr::new(addr, port),
         Err(_) => return Err((ExitCode::Usage, "invalid host address".into())),
     };
 
     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),
             Err(_) => return Err((ExitCode::Usage, "invalid Marionette port".into())),
         },
         None => None,
     };
 
@@ -181,16 +190,17 @@ fn run() -> ProgramResult {
     };
     if let Some(ref level) = log_level {
         logging::init_with_level(*level).unwrap();
     } else {
         logging::init().unwrap();
     }
 
     let settings = MarionetteSettings {
+        host: marionette_host,
         port: marionette_port,
         binary,
         connect_existing: matches.is_present("connect_existing"),
         jsdebugger: matches.is_present("jsdebugger"),
     };
     let handler = MarionetteHandler::new(settings);
     let listening = webdriver::server::start(addr, handler, &extension_routes()[..])
         .map_err(|err| (ExitCode::Unavailable, err.to_string()))?;
--- a/testing/geckodriver/src/marionette.rs
+++ b/testing/geckodriver/src/marionette.rs
@@ -50,30 +50,27 @@ use webdriver::response::{CloseWindowRes
                           ValueResponse, WebDriverResponse, WindowRectResponse};
 use webdriver::server::{Session, WebDriverHandler};
 
 use crate::build::BuildInfo;
 use crate::capabilities::{FirefoxCapabilities, FirefoxOptions};
 use crate::logging;
 use crate::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";
-
 #[derive(Debug, PartialEq, Deserialize)]
 pub struct MarionetteHandshake {
     #[serde(rename = "marionetteProtocol")]
     protocol: u16,
     #[serde(rename = "applicationType")]
     application_type: String,
 }
 
 #[derive(Default)]
 pub struct MarionetteSettings {
+    pub host: String,
     pub port: Option<u16>,
     pub binary: Option<PathBuf>,
     pub connect_existing: bool,
 
     /// Brings up the Browser Toolbox when starting Firefox,
     /// letting you debug internals.
     pub jsdebugger: bool,
 }
@@ -114,22 +111,23 @@ impl MarionetteHandler {
             )?;
             (options, capabilities)
         };
 
         if let Some(l) = options.log.level {
             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()?;
             }
             Err(e)
         })?;
         self.connection = Mutex::new(Some(connection));
         Ok(capabilities)
@@ -1040,46 +1038,49 @@ impl Into<WebDriverError> for Marionette
         if let Some(stack) = self.stacktrace {
             WebDriverError::new_with_stack(status, message, stack)
         } else {
             WebDriverError::new(status, message)
         }
     }
 }
 
-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,
         }
     }
 
     pub fn connect(&mut self, browser: &mut Option<FirefoxProcess>) -> WebDriverResult<()> {
         let timeout = time::Duration::from_secs(60);
         let poll_interval = time::Duration::from_millis(100);
         let now = time::Instant::now();
 
         debug!(
             "Waiting {}s to connect to browser on {}:{}",
             timeout.as_secs(),
-            DEFAULT_HOST,
+            self.host,
             self.port
         );
         loop {
             // immediately abort connection attempts if process disappears
             if let &mut Some(ref mut runner) = browser {
                 let exit_status = match runner.try_wait() {
                     Ok(Some(status)) => Some(
                         status
@@ -1093,17 +1094,17 @@ impl MarionetteConnection {
                 if let Some(s) = exit_status {
                     return Err(WebDriverError::new(
                         ErrorStatus::UnknownError,
                         format!("Process unexpectedly closed with status {}", s),
                     ));
                 }
             }
 
-            match TcpStream::connect(&(DEFAULT_HOST, self.port)) {
+            match TcpStream::connect((&self.host[..], self.port)) {
                 Ok(stream) => {
                     self.stream = Some(stream);
                     break;
                 }
                 Err(e) => {
                     if now.elapsed() < timeout {
                         thread::sleep(poll_interval);
                     } else {
@@ -1113,17 +1114,17 @@ impl MarionetteConnection {
                         ));
                     }
                 }
             }
         }
 
         debug!(
             "Connection established on {}:{}. Waiting for Marionette handshake",
-            DEFAULT_HOST, self.port,
+            self.host, self.port,
         );
 
         let data = self.handshake()?;
         self.session.application_type = Some(data.application_type);
         self.session.protocol = Some(data.protocol);
 
         debug!("Connected to Marionette");
         Ok(())