summaryrefslogtreecommitdiffstats
path: root/testing/mozbase/rust/mozrunner/src/firefox_args.rs
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 17:32:43 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 17:32:43 +0000
commit6bf0a5cb5034a7e684dcc3500e841785237ce2dd (patch)
treea68f146d7fa01f0134297619fbe7e33db084e0aa /testing/mozbase/rust/mozrunner/src/firefox_args.rs
parentInitial commit. (diff)
downloadthunderbird-6bf0a5cb5034a7e684dcc3500e841785237ce2dd.tar.xz
thunderbird-6bf0a5cb5034a7e684dcc3500e841785237ce2dd.zip
Adding upstream version 1:115.7.0.upstream/1%115.7.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'testing/mozbase/rust/mozrunner/src/firefox_args.rs')
-rw-r--r--testing/mozbase/rust/mozrunner/src/firefox_args.rs384
1 files changed, 384 insertions, 0 deletions
diff --git a/testing/mozbase/rust/mozrunner/src/firefox_args.rs b/testing/mozbase/rust/mozrunner/src/firefox_args.rs
new file mode 100644
index 0000000000..49f873f9dc
--- /dev/null
+++ b/testing/mozbase/rust/mozrunner/src/firefox_args.rs
@@ -0,0 +1,384 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+//! 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
+//! `/screenshot`. Elsewhere, including Windows, arguments may be prefixed
+//! with both single (`-screenshot`) and double (`--screenshot`) 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 crate::runner::platform;
+use std::ffi::{OsStr, OsString};
+use std::fmt;
+
+/// Parse an argument string into a name and value
+///
+/// Given an argument like `"--arg=value"` this will split it into
+/// `(Some("arg"), Some("value")). For a case like `"--arg"` it will
+/// return `(Some("arg"), None)` and where the input doesn't look like
+/// an argument e.g. `"value"` it will return `(None, Some("value"))`
+fn parse_arg_name_value<T>(arg: T) -> (Option<String>, 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 name_start = 0;
+ let mut name_end = 0;
+
+ // Look for an argument name at the start of the
+ // string
+ 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 != '-' {
+ name_start = i;
+ name_end = name_start + 1;
+ } else {
+ name_start = i + 1;
+ name_end = name_start;
+ }
+ } else {
+ name_end += 1;
+ if name_end_char(c) {
+ name_end -= 1;
+ break;
+ }
+ }
+ }
+
+ let name = if name_start > 0 && name_end > name_start {
+ Some(arg_str[name_start..name_end].into())
+ } else {
+ None
+ };
+
+ // If there are characters in the string after the argument, read
+ // them as the value, excluding the seperator (e.g. "=") if
+ // present.
+ let mut value_start = name_end;
+ let value_end = arg_str.len();
+ let value = if value_start < value_end {
+ if let Some(c) = arg_str[value_start..value_end].chars().next() {
+ if name_end_char(c) {
+ value_start += 1;
+ }
+ }
+ Some(arg_str[value_start..value_end].into())
+ } else {
+ None
+ };
+ (name, value)
+}
+
+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. As such Firefox only supports it on MacOS.
+ Foreground,
+
+ /// --marionette enables Marionette in the application which is used
+ /// by WebDriver HTTP.
+ Marionette,
+
+ /// `-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),
+
+ /// --remote-allow-hosts contains comma-separated values of the Host header
+ /// to allow for incoming WebSocket requests of the Remote Agent.
+ RemoteAllowHosts,
+
+ /// --remote-allow-origins contains comma-separated values of the Origin header
+ /// to allow for incoming WebSocket requests of the Remote Agent.
+ RemoteAllowOrigins,
+
+ /// --remote-debugging-port enables the Remote Agent in the application
+ /// which is used for the WebDriver BiDi and CDP remote debugging protocols.
+ RemoteDebuggingPort,
+
+ /// Not an argument.
+ None,
+}
+
+impl Arg {
+ pub fn new(name: &str) -> Arg {
+ match name {
+ "foreground" => Arg::Foreground,
+ "marionette" => Arg::Marionette,
+ "no-remote" => Arg::NoRemote,
+ "profile" => Arg::Profile,
+ "P" => Arg::NamedProfile,
+ "ProfileManager" => Arg::ProfileManager,
+ "remote-allow-hosts" => Arg::RemoteAllowHosts,
+ "remote-allow-origins" => Arg::RemoteAllowOrigins,
+ "remote-debugging-port" => Arg::RemoteDebuggingPort,
+ _ => Arg::Other(name.into()),
+ }
+ }
+}
+
+impl<'a> From<&'a OsString> for Arg {
+ fn from(arg_str: &OsString) -> Arg {
+ if let (Some(name), _) = parse_arg_name_value(arg_str) {
+ Arg::new(&name)
+ } else {
+ Arg::None
+ }
+ }
+}
+
+impl fmt::Display for Arg {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ f.write_str(&match self {
+ Arg::Foreground => "--foreground".to_string(),
+ Arg::Marionette => "--marionette".to_string(),
+ Arg::NamedProfile => "-P".to_string(),
+ Arg::None => "".to_string(),
+ Arg::NoRemote => "--no-remote".to_string(),
+ Arg::Other(x) => format!("--{}", x),
+ Arg::Profile => "--profile".to_string(),
+ Arg::ProfileManager => "--ProfileManager".to_string(),
+ Arg::RemoteAllowHosts => "--remote-allow-hosts".to_string(),
+ Arg::RemoteAllowOrigins => "--remote-allow-origins".to_string(),
+ Arg::RemoteDebuggingPort => "--remote-debugging-port".to_string(),
+ })
+ }
+}
+
+/// Parse an iterator over arguments into an vector of (name, value)
+/// tuples
+///
+/// Each entry in the input argument will produce a single item in the
+/// output. Because we don't know anything about the specific
+/// arguments, something that doesn't parse as a named argument may
+/// either be the value of a previous named argument, or may be a
+/// positional argument.
+pub fn parse_args<'a>(
+ args: impl Iterator<Item = &'a OsString>,
+) -> Vec<(Option<Arg>, Option<String>)> {
+ args.map(parse_arg_name_value)
+ .map(|(name, value)| {
+ if let Some(arg_name) = name {
+ (Some(Arg::new(&arg_name)), value)
+ } else {
+ (None, value)
+ }
+ })
+ .collect()
+}
+
+/// Given an iterator over all arguments, get the value of an argument
+///
+/// This assumes that the argument takes a single value and that is
+/// either provided as a single argument entry
+/// (e.g. `["--name=value"]`) or as the following argument
+/// (e.g. `["--name", "value"])
+pub fn get_arg_value<'a>(
+ mut parsed_args: impl Iterator<Item = &'a (Option<Arg>, Option<String>)>,
+ arg: Arg,
+) -> Option<String> {
+ let mut found_value = None;
+ for (arg_name, arg_value) in &mut parsed_args {
+ if let (Some(name), value) = (arg_name, arg_value) {
+ if *name == arg {
+ found_value = value.clone();
+ break;
+ }
+ }
+ }
+ if found_value.is_none() {
+ // If there wasn't a value, check if the following argument is a value
+ if let Some((None, value)) = parsed_args.next() {
+ found_value = value.clone();
+ }
+ }
+ found_value
+}
+
+#[cfg(test)]
+mod tests {
+ use super::{get_arg_value, parse_arg_name_value, parse_args, Arg};
+ use std::ffi::OsString;
+
+ fn parse(arg: &str, name: Option<&str>) {
+ let (result, _) = parse_arg_name_value(arg);
+ assert_eq!(result, name.map(|x| x.to_string()));
+ }
+
+ #[test]
+ fn test_parse_arg_name_value() {
+ 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_value_windows() {
+ parse("/profile", Some("profile"));
+ }
+
+ #[cfg(not(target_os = "windows"))]
+ #[test]
+ fn test_parse_arg_name_value_non_windows() {
+ parse("/profile", None);
+ }
+
+ #[test]
+ fn test_arg_from_osstring() {
+ assert_eq!(Arg::from(&OsString::from("--foreground")), Arg::Foreground);
+ assert_eq!(Arg::from(&OsString::from("-foreground")), Arg::Foreground);
+
+ assert_eq!(Arg::from(&OsString::from("--marionette")), Arg::Marionette);
+ assert_eq!(Arg::from(&OsString::from("-marionette")), Arg::Marionette);
+
+ assert_eq!(Arg::from(&OsString::from("--no-remote")), Arg::NoRemote);
+ assert_eq!(Arg::from(&OsString::from("-no-remote")), Arg::NoRemote);
+
+ 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("--remote-debugging-port")),
+ Arg::RemoteDebuggingPort
+ );
+ assert_eq!(
+ Arg::from(&OsString::from("-remote-debugging-port")),
+ Arg::RemoteDebuggingPort
+ );
+ assert_eq!(
+ Arg::from(&OsString::from("--remote-debugging-port 9222")),
+ Arg::RemoteDebuggingPort
+ );
+
+ assert_eq!(
+ Arg::from(&OsString::from("--remote-allow-hosts")),
+ Arg::RemoteAllowHosts
+ );
+ assert_eq!(
+ Arg::from(&OsString::from("-remote-allow-hosts")),
+ Arg::RemoteAllowHosts
+ );
+ assert_eq!(
+ Arg::from(&OsString::from("--remote-allow-hosts 9222")),
+ Arg::RemoteAllowHosts
+ );
+
+ assert_eq!(
+ Arg::from(&OsString::from("--remote-allow-origins")),
+ Arg::RemoteAllowOrigins
+ );
+ assert_eq!(
+ Arg::from(&OsString::from("-remote-allow-origins")),
+ Arg::RemoteAllowOrigins
+ );
+ assert_eq!(
+ Arg::from(&OsString::from("--remote-allow-origins http://foo")),
+ Arg::RemoteAllowOrigins
+ );
+ }
+
+ #[test]
+ fn test_get_arg_value() {
+ let args = vec!["-P", "ProfileName", "--profile=/path/", "--no-remote"]
+ .iter()
+ .map(|x| OsString::from(x))
+ .collect::<Vec<OsString>>();
+ let parsed_args = parse_args(args.iter());
+ assert_eq!(
+ get_arg_value(parsed_args.iter(), Arg::NamedProfile),
+ Some("ProfileName".into())
+ );
+ assert_eq!(
+ get_arg_value(parsed_args.iter(), Arg::Profile),
+ Some("/path/".into())
+ );
+ assert_eq!(get_arg_value(parsed_args.iter(), Arg::NoRemote), None);
+
+ let args = vec!["--profile=", "-P test"]
+ .iter()
+ .map(|x| OsString::from(x))
+ .collect::<Vec<OsString>>();
+ let parsed_args = parse_args(args.iter());
+ assert_eq!(
+ get_arg_value(parsed_args.iter(), Arg::NamedProfile),
+ Some("test".into())
+ );
+ assert_eq!(
+ get_arg_value(parsed_args.iter(), Arg::Profile),
+ Some("".into())
+ );
+ }
+}