bug 1483996: geckodriver: add --marionette-host flag; r=ato
authorMarc Fisher <fisherii@google.com>
Fri, 07 Dec 2018 18:31:41 +0000
changeset 508905 b2eb9000daed2a2cff623eae8ca2387bb59e4601
parent 508904 d4f6a34f0d1ceb00f224c818cedc132dfe4b2582
child 508906 904080f44dbd2ae3cc92c794a817b281d6cb7237
push id1905
push userffxbld-merge
push dateMon, 21 Jan 2019 12:33:13 +0000
treeherdermozilla-release@c2fca1944d8c [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersato
bugs1483996
milestone65.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
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(())