/* This Source Code Form is subject to the terms of the Mozilla Public *License,v.2.0.IfacopyoftheMPLwasnotdistributedwiththis
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
usecrate::command::LogOptions; usecrate::logging::Level; usecrate::marionette::MarionetteSettings; use base64::prelude::BASE64_STANDARD; use base64::Engine; use mozdevice::AndroidStorageInput; use mozprofile::preferences::Pref; use mozprofile::profile::Profile; use mozrunner::firefox_args::{get_arg_value, parse_args, Arg}; use mozrunner::runner::platform::firefox_default_path; use mozversion::{firefox_binary_version, firefox_version, Version}; use regex::bytes::Regex; use serde_json::{Map, Value}; use std::collections::BTreeMap; use std::default::Default; use std::ffi::OsString; use std::fs; use std::io; use std::io::BufWriter; use std::io::Cursor; use std::path::{Path, PathBuf}; use std::str::{self, FromStr}; use thiserror::Error; use webdriver::capabilities::{BrowserCapabilities, Capabilities}; use webdriver::error::{ErrorStatus, WebDriverError, WebDriverResult};
/// Provides matching of `moz:firefoxOptions` and resolutionnized of which Firefox /// binary to use. /// /// `FirefoxCapabilities` is constructed with the fallback binary, should /// `moz:firefoxOptions` not contain a binary entry. This may either be the /// system Firefox installation or an override, for example given to the /// `--binary` flag of geckodriver. pubstruct FirefoxCapabilities<'a> { pub chosen_binary: Option<PathBuf>,
fallback_binary: Option<&'a PathBuf>,
version_cache: BTreeMap<PathBuf, Result<Version, VersionError>>,
}
fn validate_custom(&mutself, name: &str, value: &Value) -> WebDriverResult<()> { if !name.starts_with("moz:") { return Ok(());
} match name { "moz:firefoxOptions" => { let data = try_opt!(
value.as_object(),
ErrorStatus::InvalidArgument, "moz:firefoxOptions is not an object"
); for (key, value) in data.iter() { match &**key { "androidActivity"
| "androidDeviceSerial"
| "androidPackage"
| "profile" => { if !value.is_string() { return Err(WebDriverError::new(
ErrorStatus::InvalidArgument,
format!("{} is not a string", &**key),
));
}
} "androidIntentArguments" | "args" => { if !try_opt!(
value.as_array(),
ErrorStatus::InvalidArgument,
format!("{} is not an array", &**key)
)
.iter()
.all(|value| value.is_string())
{ return Err(WebDriverError::new(
ErrorStatus::InvalidArgument,
format!("{} entry is not a string", &**key),
));
}
} "binary" => { iflet Some(binary) = value.as_str() { if !data.contains_key("androidPackage")
&& self.version(Some(Path::new(binary))).is_err()
{ return Err(WebDriverError::new(
ErrorStatus::InvalidArgument,
format!("{} is not a Firefox executable", &**key),
));
}
} else { return Err(WebDriverError::new(
ErrorStatus::InvalidArgument,
format!("{} is not a string", &**key),
));
}
} "env" => { let env_data = try_opt!(
value.as_object(),
ErrorStatus::InvalidArgument, "env value is not an object"
); if !env_data.values().all(Value::is_string) { return Err(WebDriverError::new(
ErrorStatus::InvalidArgument, "Environment values were not all strings",
));
}
} "log" => { let log_data = try_opt!(
value.as_object(),
ErrorStatus::InvalidArgument, "log value is not an object"
); for (log_key, log_value) in log_data.iter() { match &**log_key { "level" => { let level = try_opt!(
log_value.as_str(),
ErrorStatus::InvalidArgument, "log level is not a string"
); if Level::from_str(level).is_err() { return Err(WebDriverError::new(
ErrorStatus::InvalidArgument,
format!("Not a valid log level: {}", level),
));
}
}
x => { return Err(WebDriverError::new(
ErrorStatus::InvalidArgument,
format!("Invalid log field {}", x),
))
}
}
}
} "prefs" => { let prefs_data = try_opt!(
value.as_object(),
ErrorStatus::InvalidArgument, "prefs value is not an object"
); let is_pref_value_type = |x: &Value| {
x.is_string() || x.is_i64() || x.is_u64() || x.is_boolean()
}; if !prefs_data.values().all(is_pref_value_type) { return Err(WebDriverError::new(
ErrorStatus::InvalidArgument, "Preference values not all string or integer or boolean",
));
}
}
x => { return Err(WebDriverError::new(
ErrorStatus::InvalidArgument,
format!("Invalid moz:firefoxOptions field {}", x),
))
}
}
}
} "moz:webdriverClick" => { if !value.is_boolean() { return Err(WebDriverError::new(
ErrorStatus::InvalidArgument, "moz:webdriverClick is not a boolean",
));
}
} "moz:debuggerAddress" => { if !value.is_boolean() { return Err(WebDriverError::new(
ErrorStatus::InvalidArgument, "moz:debuggerAddress is not a boolean",
));
}
}
_ => { return Err(WebDriverError::new(
ErrorStatus::InvalidArgument,
format!("Unrecognised option {}", name),
))
}
}
Ok(())
}
iflet Some(args) = rv.args.as_ref() { let os_args = parse_args(args.iter().map(OsString::from).collect::<Vec<_>>().iter());
iflet Some(path) = get_arg_value(os_args.iter(), Arg::Profile) { iflet ProfileType::Path(_) = rv.profile { return Err(WebDriverError::new(
ErrorStatus::InvalidArgument, "Can't provide both a --profile argument and a profile",
));
} let path_buf = PathBuf::from(path);
rv.profile = ProfileType::Path(Profile::new_from_path(&path_buf)?);
}
if get_arg_value(os_args.iter(), Arg::NamedProfile).is_some() { iflet ProfileType::Path(_) = rv.profile { return Err(WebDriverError::new(
ErrorStatus::InvalidArgument, "Can't provide both a -P argument and a profile",
));
} // See bug 1757720
warn!("Firefox was configured to use a named profile (`-P <name>`). \
Support for named profiles will be removed in a future geckodriver release. \
Please instead use the `--profile <path>` Firefox argument to start with an existing profile");
rv.profile = ProfileType::Named;
}
// Block these Firefox command line arguments that should not be settable // via session capabilities. iflet Some(arg) = os_args
.iter()
.filter_map(|(opt_arg, _)| opt_arg.as_ref())
.find(|arg| {
matches!(
arg,
Arg::Marionette
| Arg::RemoteAllowHosts
| Arg::RemoteAllowOrigins
| Arg::RemoteDebuggingPort
)
})
{ return Err(WebDriverError::new(
ErrorStatus::InvalidArgument,
format!("Argument {} can't be set via capabilities", arg),
));
};
}
let has_web_socket_url = matched
.get("webSocketUrl")
.and_then(|x| x.as_bool())
.unwrap_or(false);
let has_debugger_address = matched
.remove("moz:debuggerAddress")
.and_then(|x| x.as_bool())
.unwrap_or(false);
// Set a command line provided port for the Remote Agent for now. // It needs to be the same on the host and the Android device. if has_web_socket_url || has_debugger_address {
rv.use_websocket = true;
// Bug 1722863: Setting of command line arguments would be // better suited in the individual Browser implementations. letmut remote_args = Vec::new();
remote_args.push("--remote-debugging-port".to_owned());
remote_args.push(settings.websocket_port.to_string());
fn load_profile(
profile_root: Option<&Path>,
options: &Capabilities,
) -> WebDriverResult<Option<Profile>> { iflet Some(profile_json) = options.get("profile") { let profile_base64 = profile_json.as_str().ok_or_else(|| {
WebDriverError::new(ErrorStatus::InvalidArgument, "Profile is not a string")
})?; let profile_zip = &*BASE64_STANDARD.decode(profile_base64)?;
// Create an emtpy profile directory let profile = Profile::new(profile_root)?;
unzip_buffer(
profile_zip,
profile
.temp_dir
.as_ref()
.expect("Profile doesn't have a path")
.path(),
)?;
Ok(Some(profile))
} else {
Ok(None)
}
}
fn load_args(options: &Capabilities) -> WebDriverResult<Option<Vec<String>>> { iflet Some(args_json) = options.get("args") { let args_array = args_json.as_array().ok_or_else(|| {
WebDriverError::new(ErrorStatus::InvalidArgument, "Arguments were not an array")
})?; let args = args_array
.iter()
.map(|x| x.as_str().map(|x| x.to_owned()))
.collect::<Option<Vec<String>>>()
.ok_or_else(|| {
WebDriverError::new(
ErrorStatus::InvalidArgument, "Arguments entries were not all strings",
)
})?;
Ok(Some(args))
} else {
Ok(None)
}
}
pubfn load_env(options: &Capabilities) -> WebDriverResult<Option<Vec<(String, String)>>> { iflet Some(env_data) = options.get("env") { let env = env_data.as_object().ok_or_else(|| {
WebDriverError::new(ErrorStatus::InvalidArgument, "Env was not an object")
})?; letmut rv = Vec::with_capacity(env.len()); for (key, value) in env.iter() {
rv.push((
key.clone(),
value
.as_str()
.ok_or_else(|| {
WebDriverError::new(
ErrorStatus::InvalidArgument, "Env value is not a string",
)
})?
.to_string(),
));
}
Ok(Some(rv))
} else {
Ok(None)
}
}
fn load_log(options: &Capabilities) -> WebDriverResult<LogOptions> { iflet Some(json) = options.get("log") { let log = json.as_object().ok_or_else(|| {
WebDriverError::new(ErrorStatus::InvalidArgument, "Log section is not an object")
})?;
let level = match log.get("level") {
Some(json) => { let s = json.as_str().ok_or_else(|| {
WebDriverError::new(
ErrorStatus::InvalidArgument, "Log level is not a string",
)
})?;
Some(Level::from_str(s).ok().ok_or_else(|| {
WebDriverError::new(ErrorStatus::InvalidArgument, "Log level is unknown")
})?)
}
None => None,
};
android.activity = match options.get("androidActivity") {
Some(json) => { let activity = json
.as_str()
.ok_or_else(|| {
WebDriverError::new(
ErrorStatus::InvalidArgument, "androidActivity is not a string",
)
})?
.to_owned();
if activity.contains('/') { return Err(WebDriverError::new(
ErrorStatus::InvalidArgument, "androidActivity should not contain '/",
));
}
Some(activity)
}
None => { match package.as_str() { "org.mozilla.firefox"
| "org.mozilla.firefox_beta"
| "org.mozilla.fenix"
| "org.mozilla.fenix.debug"
| "org.mozilla.reference.browser" => {
Some("org.mozilla.fenix.IntentReceiverActivity".to_string())
} "org.mozilla.focus"
| "org.mozilla.focus.debug"
| "org.mozilla.klar"
| "org.mozilla.klar.debug" => {
Some("org.mozilla.focus.activity.IntentReceiverActivity".to_string())
} // For all other applications fallback to auto-detection.
_ => None,
}
}
};
android.device_serial = match options.get("androidDeviceSerial") {
Some(json) => Some(
json.as_str()
.ok_or_else(|| {
WebDriverError::new(
ErrorStatus::InvalidArgument, "androidDeviceSerial is not a string",
)
})?
.to_owned(),
),
None => None,
};
android.intent_arguments = match options.get("androidIntentArguments") {
Some(json) => { let args_array = json.as_array().ok_or_else(|| {
WebDriverError::new(
ErrorStatus::InvalidArgument, "androidIntentArguments is not an array",
)
})?; let args = args_array
.iter()
.map(|x| x.as_str().map(|x| x.to_owned()))
.collect::<Option<Vec<String>>>()
.ok_or_else(|| {
WebDriverError::new(
ErrorStatus::InvalidArgument, "androidIntentArguments entries are not all strings",
)
})?;
Some(args)
}
None => { // All GeckoView based applications support this view, // and allow to open a blank page in a Gecko window.
Some(vec![ "-a".to_string(), "android.intent.action.VIEW".to_string(), "-d".to_string(), "about:blank".to_string(),
])
}
};
Ok(Some(android))
} else {
Ok(None)
}
}
}
fn pref_from_json(value: &Value) -> WebDriverResult<Pref> { match *value {
Value::String(ref x) => Ok(Pref::new(x.clone())),
Value::Number(ref x) => Ok(Pref::new(x.as_i64().unwrap())),
Value::Bool(x) => Ok(Pref::new(x)),
_ => Err(WebDriverError::new(
ErrorStatus::UnknownError, "Could not convert pref value to string, boolean, or integer",
)),
}
}
fn unzip_buffer(buf: &[u8], dest_dir: &Path) -> WebDriverResult<()> { let reader = Cursor::new(buf); letmut zip = zip::ZipArchive::new(reader)
.map_err(|_| WebDriverError::new(ErrorStatus::UnknownError, "Failed to unzip profile"))?;
for i in0..zip.len() { letmut file = zip.by_index(i).map_err(|_| {
WebDriverError::new(
ErrorStatus::UnknownError, "Processing profile zip file failed",
)
})?; let unzip_path = { let name = file.name(); let is_dir = name.ends_with('/'); let rel_path = Path::new(name); let dest_path = dest_dir.join(rel_path);
{ let create_dir = if is_dir {
Some(dest_path.as_path())
} else {
dest_path.parent()
}; iflet Some(dir) = create_dir { if !dir.exists() {
debug!("Creating profile directory tree {}", dir.to_string_lossy());
fs::create_dir_all(dir)?;
}
}
}
if is_dir {
None
} else {
Some(dest_path)
}
};
iflet Some(unzip_path) = unzip_path {
debug!("Extracting profile to {}", unzip_path.to_string_lossy()); let dest = fs::File::create(unzip_path)?; if file.size() > 0 { letmut writer = BufWriter::new(dest);
io::copy(&mut file, &mut writer)?;
}
}
}
Ok(())
}
#[cfg(test)] mod tests { externcrate mozprofile;
useself::mozprofile::preferences::Pref; usesuper::*; use serde_json::{json, Map, Value}; use std::fs::File; use std::io::Read; use url::{Host, Url}; use webdriver::capabilities::Capabilities;
let marionette_settings = Default::default();
FirefoxOptions::from_capabilities(None, &marionette_settings, &mut caps)
.expect_err("Firefox options need to be of type object");
}
¤ Diese beiden folgenden Angebotsgruppen bietet das Unternehmen0.25Angebot
(Wie Sie bei der Firma Beratungs- und Dienstleistungen beauftragen können 2026-06-18)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.