Bug 1466573 - Start Firefox with -foreground and -no-remote. r?jgraham draft
authorAndreas Tolfsen <ato@sny.no>
Tue, 05 Jun 2018 18:02:34 +0100
changeset 807497 da8c831ca050a4c1e54305aa8d829da6e271af83
parent 807496 1de7a3680aa29396029e34cbd60163694ff015f6
push id113125
push userbmo:ato@sny.no
push dateThu, 14 Jun 2018 17:50:24 +0000
reviewersjgraham
bugs1466573
milestone62.0a1
Bug 1466573 - Start Firefox with -foreground and -no-remote. r?jgraham Start Firefox with -foreground and -no-remote arguments if they have not already been given by the user. -foreground will ensure the application window gets focus when Firefox is started, and -no-remote will prevent remote commands to this instance of Firefox and also ensure we always start a new instance. MozReview-Commit-ID: LGEqgyHYapc
testing/mozbase/rust/mozrunner/src/firefox_args.rs
testing/mozbase/rust/mozrunner/src/lib.rs
testing/mozbase/rust/mozrunner/src/runner.rs
new file mode 100644
--- /dev/null
+++ b/testing/mozbase/rust/mozrunner/src/firefox_args.rs
@@ -0,0 +1,175 @@
+//! Argument string parsing and matching functions for Firefox.
+//!
+//! Which arguments Firefox accepts and in what style depends on the platform.
+//! On Windows only, arguments can be prefixed with `/` (slash), such as
+//! `/foreground`.  Elsewhere, including Windows, arguments may be prefixed
+//! with both single (`-foreground`) and double (`--foreground`) dashes.
+//!
+//! An argument's name is determined by a space or an assignment operator (`=`)
+//! so that for the string `-foo=bar`, `foo` is considered the argument's
+//! basename.
+
+use std::ffi::{OsStr, OsString};
+
+use runner::platform;
+
+fn parse_arg_name<T>(arg: T) -> Option<String>
+where
+    T: AsRef<OsStr>,
+{
+    let arg_os_str: &OsStr = arg.as_ref();
+    let arg_str = arg_os_str.to_string_lossy();
+
+    let mut start = 0;
+    let mut end = 0;
+
+    for (i, c) in arg_str.chars().enumerate() {
+        if i == 0 {
+            if !platform::arg_prefix_char(c) {
+                break;
+            }
+        } else if i == 1 {
+            if name_end_char(c) {
+                break;
+            } else if c != '-' {
+                start = i;
+                end = start + 1;
+            } else {
+                start = i + 1;
+                end = start;
+            }
+        } else {
+            end += 1;
+            if name_end_char(c) {
+                end -= 1;
+                break;
+            }
+        }
+    }
+
+    if start > 0 && end > start {
+        Some(arg_str[start..end].into())
+    } else {
+        None
+    }
+}
+
+fn name_end_char(c: char) -> bool {
+    c == ' ' || c == '='
+}
+
+/// Represents a Firefox command-line argument.
+#[derive(Debug, PartialEq)]
+pub enum Arg {
+    /// `-foreground` ensures application window gets focus, which is not the
+    /// default on macOS.
+    Foreground,
+
+    /// `-no-remote` prevents remote commands to this instance of Firefox, and
+    /// ensure we always start a new instance.
+    NoRemote,
+
+    /// `-P NAME` starts Firefox with a profile with a given name.
+    NamedProfile,
+
+    /// `-profile PATH` starts Firefox with the profile at the specified path.
+    Profile,
+
+    /// `-ProfileManager` starts Firefox with the profile chooser dialogue.
+    ProfileManager,
+
+    /// All other arguments.
+    Other(String),
+
+    /// Not an argument.
+    None,
+}
+
+impl<'a> From<&'a OsString> for Arg {
+    fn from(arg_str: &OsString) -> Arg {
+        if let Some(basename) = parse_arg_name(arg_str) {
+            match &*basename {
+                "profile" => Arg::Profile,
+                "P" => Arg::NamedProfile,
+                "ProfileManager" => Arg::ProfileManager,
+                "foreground" => Arg::Foreground,
+                "no-remote" => Arg::NoRemote,
+                _ => Arg::Other(basename),
+            }
+        } else {
+           Arg::None
+        }
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::{Arg, parse_arg_name};
+    use std::ffi::OsString;
+
+    fn parse(arg: &str, name: Option<&str>) {
+        let result = parse_arg_name(arg);
+        assert_eq!(result, name.map(|x| x.to_string()));
+    }
+
+    #[test]
+    fn test_parse_arg_name() {
+        parse("-p", Some("p"));
+        parse("--p", Some("p"));
+        parse("--profile foo", Some("profile"));
+        parse("--profile", Some("profile"));
+        parse("--", None);
+        parse("", None);
+        parse("-=", None);
+        parse("--=", None);
+        parse("-- foo", None);
+        parse("foo", None);
+        parse("/ foo", None);
+        parse("/- foo", None);
+        parse("/=foo", None);
+        parse("foo", None);
+        parse("-profile", Some("profile"));
+        parse("-profile=foo", Some("profile"));
+        parse("-profile = foo", Some("profile"));
+        parse("-profile abc", Some("profile"));
+        parse("-profile /foo", Some("profile"));
+    }
+
+    #[cfg(target_os = "windows")]
+    #[test]
+    fn test_parse_arg_name_windows() {
+        parse("/profile", Some("profile"));
+    }
+
+    #[cfg(not(target_os = "windows"))]
+    #[test]
+    fn test_parse_arg_name_non_windows() {
+        parse("/profile", None);
+    }
+
+    #[test]
+    fn test_arg_from_osstring() {
+        assert_eq!(Arg::from(&OsString::from("-- profile")), Arg::None);
+        assert_eq!(Arg::from(&OsString::from("profile")), Arg::None);
+        assert_eq!(Arg::from(&OsString::from("profile -P")), Arg::None);
+        assert_eq!(Arg::from(&OsString::from("-profiled")), Arg::Other("profiled".into()));
+        assert_eq!(Arg::from(&OsString::from("-PROFILEMANAGER")), Arg::Other("PROFILEMANAGER".into()));
+
+        assert_eq!(Arg::from(&OsString::from("--profile")), Arg::Profile);
+        assert_eq!(Arg::from(&OsString::from("-profile foo")), Arg::Profile);
+
+        assert_eq!(Arg::from(&OsString::from("--ProfileManager")), Arg::ProfileManager);
+        assert_eq!(Arg::from(&OsString::from("-ProfileManager")), Arg::ProfileManager);
+
+        // TODO: -Ptest is valid
+        //assert_eq!(Arg::from(&OsString::from("-Ptest")), Arg::NamedProfile);
+        assert_eq!(Arg::from(&OsString::from("-P")), Arg::NamedProfile);
+        assert_eq!(Arg::from(&OsString::from("-P test")), Arg::NamedProfile);
+
+        assert_eq!(Arg::from(&OsString::from("--foreground")), Arg::Foreground);
+        assert_eq!(Arg::from(&OsString::from("-foreground")), Arg::Foreground);
+
+        assert_eq!(Arg::from(&OsString::from("--no-remote")), Arg::NoRemote);
+        assert_eq!(Arg::from(&OsString::from("-no-remote")), Arg::NoRemote);
+    }
+}
--- a/testing/mozbase/rust/mozrunner/src/lib.rs
+++ b/testing/mozbase/rust/mozrunner/src/lib.rs
@@ -1,9 +1,11 @@
-#[macro_use] extern crate log;
+#[macro_use]
+extern crate log;
 extern crate mozprofile;
 #[cfg(target_os = "windows")]
 extern crate winreg;
 
+pub mod firefox_args;
 pub mod path;
 pub mod runner;
 
 pub use runner::platform::firefox_default_path;
--- a/testing/mozbase/rust/mozrunner/src/runner.rs
+++ b/testing/mozbase/rust/mozrunner/src/runner.rs
@@ -8,16 +8,18 @@ use std::fmt;
 use std::io;
 use std::io::ErrorKind;
 use std::path::{Path, PathBuf};
 use std::process;
 use std::process::{Child, Command, Stdio};
 use std::thread;
 use std::time;
 
+use firefox_args::Arg;
+
 pub trait Runner {
     type Process;
 
     fn arg<'a, S>(&'a mut self, arg: S) -> &'a mut Self
     where
         S: AsRef<OsStr>;
 
     fn args<'a, I, S>(&'a mut self, args: I) -> &'a mut Self
@@ -257,91 +259,46 @@ impl Runner for FirefoxRunner {
         let stderr = self.stderr.unwrap_or_else(|| Stdio::inherit());
 
         let mut cmd = Command::new(&self.binary);
         cmd.args(&self.args[..])
             .envs(&self.envs)
             .stdout(stdout)
             .stderr(stderr);
 
-        if !self.args.iter().any(|x| is_profile_arg(x)) {
+        let mut seen_foreground = false;
+        let mut seen_no_remote = false;
+        let mut seen_profile = false;
+        for arg in self.args.iter() {
+            match arg.into() {
+                Arg::Foreground => seen_foreground = true,
+                Arg::NoRemote => seen_no_remote = true,
+                Arg::Profile | Arg::NamedProfile | Arg::ProfileManager => seen_profile = true,
+                Arg::Other(_) | Arg::None => {},
+            }
+        }
+        if !seen_foreground {
+            cmd.arg("-foreground");
+        }
+        if !seen_no_remote {
+            cmd.arg("-no-remote");
+        }
+        if !seen_profile {
             cmd.arg("-profile").arg(&self.profile.path);
         }
 
         info!("Running command: {:?}", cmd);
         let process = cmd.spawn()?;
         Ok(FirefoxProcess {
             process,
-            profile: self.profile
+            profile: self.profile,
         })
     }
 }
 
-fn parse_arg_name<T>(arg: T) -> Option<String>
-where
-    T: AsRef<OsStr>,
-{
-    let arg_os_str: &OsStr = arg.as_ref();
-    let arg_str = arg_os_str.to_string_lossy();
-
-    let mut start = 0;
-    let mut end = 0;
-
-    for (i, c) in arg_str.chars().enumerate() {
-        if i == 0 {
-            if !platform::arg_prefix_char(c) {
-                break;
-            }
-        } else if i == 1 {
-            if name_end_char(c) {
-                break;
-            } else if c != '-' {
-                start = i;
-                end = start + 1;
-            } else {
-                start = i + 1;
-                end = start;
-            }
-        } else {
-            end += 1;
-            if name_end_char(c) {
-                end -= 1;
-                break;
-            }
-        }
-    }
-
-    if start > 0 && end > start {
-        Some(arg_str[start..end].into())
-    } else {
-        None
-    }
-}
-
-fn name_end_char(c: char) -> bool {
-    c == ' ' || c == '='
-}
-
-/// Check if an argument string affects the Firefox profile
-///
-/// Returns a boolean indicating whether a given string
-/// contains one of the `-P`, `-Profile` or `-ProfileManager`
-/// arguments, respecting the various platform-specific conventions.
-pub fn is_profile_arg<T>(arg: T) -> bool
-where
-    T: AsRef<OsStr>,
-{
-    if let Some(name) = parse_arg_name(arg) {
-        name.eq_ignore_ascii_case("profile") || name.eq_ignore_ascii_case("p")
-            || name.eq_ignore_ascii_case("profilemanager")
-    } else {
-        false
-    }
-}
-
 #[cfg(target_os = "linux")]
 pub mod platform {
     use path::find_binary;
     use std::path::PathBuf;
 
     /// Searches the system path for `firefox`.
     pub fn firefox_default_path() -> Option<PathBuf> {
         find_binary("firefox")
@@ -461,65 +418,8 @@ pub mod platform {
     pub fn firefox_default_path() -> Option<PathBuf> {
         None
     }
 
     pub fn arg_prefix_char(c: char) -> bool {
         c == '-'
     }
 }
-
-#[cfg(test)]
-mod tests {
-    use super::{is_profile_arg, parse_arg_name};
-
-    fn parse(arg: &str, name: Option<&str>) {
-        let result = parse_arg_name(arg);
-        assert_eq!(result, name.map(|x| x.to_string()));
-    }
-
-    #[test]
-    fn test_parse_arg_name() {
-        parse("-p", Some("p"));
-        parse("--p", Some("p"));
-        parse("--profile foo", Some("profile"));
-        parse("--profile", Some("profile"));
-        parse("--", None);
-        parse("", None);
-        parse("-=", None);
-        parse("--=", None);
-        parse("-- foo", None);
-        parse("foo", None);
-        parse("/ foo", None);
-        parse("/- foo", None);
-        parse("/=foo", None);
-        parse("foo", None);
-        parse("-profile", Some("profile"));
-        parse("-profile=foo", Some("profile"));
-        parse("-profile = foo", Some("profile"));
-        parse("-profile abc", Some("profile"));
-    }
-
-    #[cfg(target_os = "windows")]
-    #[test]
-    fn test_parse_arg_name_windows() {
-        parse("/profile", Some("profile"));
-    }
-
-    #[cfg(not(target_os = "windows"))]
-    #[test]
-    fn test_parse_arg_name_non_windows() {
-        parse("/profile", None);
-    }
-
-    #[test]
-    fn test_is_profile_arg() {
-        assert!(is_profile_arg("--profile"));
-        assert!(is_profile_arg("-p"));
-        assert!(is_profile_arg("-PROFILEMANAGER"));
-        assert!(is_profile_arg("-ProfileMANAGER"));
-        assert!(!is_profile_arg("-- profile"));
-        assert!(!is_profile_arg("-profiled"));
-        assert!(!is_profile_arg("-p1"));
-        assert!(is_profile_arg("-p test"));
-        assert!(is_profile_arg("-profile /foo"));
-    }
-}