diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:22:09 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:22:09 +0000 |
commit | 43a97878ce14b72f0981164f87f2e35e14151312 (patch) | |
tree | 620249daf56c0258faa40cbdcf9cfba06de2a846 /testing/mozbase/rust/mozrunner/src/firefox_args.rs | |
parent | Initial commit. (diff) | |
download | firefox-43a97878ce14b72f0981164f87f2e35e14151312.tar.xz firefox-43a97878ce14b72f0981164f87f2e35e14151312.zip |
Adding upstream version 110.0.1.upstream/110.0.1upstream
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.rs | 384 |
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()) + ); + } +} |