Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/mozilla/geckodriver.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/android.rs6
-rw-r--r--src/browser.rs186
-rw-r--r--src/capabilities.rs229
-rw-r--r--src/logging.rs13
-rw-r--r--src/main.rs278
-rw-r--r--src/marionette.rs158
-rw-r--r--src/prefs.rs15
7 files changed, 672 insertions, 213 deletions
diff --git a/src/android.rs b/src/android.rs
index 8969fb1..3cc87b0 100644
--- a/src/android.rs
+++ b/src/android.rs
@@ -235,7 +235,7 @@ impl AndroidHandler {
.split_terminator('\n')
.filter(|line| line.starts_with("package:"))
.map(|line| line.rsplit(':').next().expect("Package name found"));
- if packages.find(|x| x == &options.package.as_str()).is_none() {
+ if !packages.any(|x| x == options.package.as_str()) {
return Err(AndroidError::PackageNotFound(options.package.clone()));
}
@@ -498,7 +498,7 @@ mod test {
};
assert_eq!(handler.test_root, test_root);
- let mut profile = test_root.clone();
+ let mut profile = test_root;
profile.push(format!("{}-geckodriver-profile", &package));
assert_eq!(handler.profile, profile);
}
@@ -507,7 +507,7 @@ mod test {
#[ignore]
fn android_handler_storage_as_app() {
let package = "org.mozilla.geckoview_example";
- run_handler_storage_test(&package, AndroidStorageInput::App);
+ run_handler_storage_test(package, AndroidStorageInput::App);
}
#[test]
diff --git a/src/browser.rs b/src/browser.rs
index b777a0a..306bfe1 100644
--- a/src/browser.rs
+++ b/src/browser.rs
@@ -3,16 +3,16 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
use crate::android::AndroidHandler;
-use crate::capabilities::FirefoxOptions;
+use crate::capabilities::{FirefoxOptions, ProfileType};
use crate::logging;
use crate::prefs;
use mozprofile::preferences::Pref;
use mozprofile::profile::{PrefFile, Profile};
use mozrunner::runner::{FirefoxProcess, FirefoxRunner, Runner, RunnerProcess};
use std::fs;
-use std::path::PathBuf;
+use std::io::Read;
+use std::path::{Path, PathBuf};
use std::time;
-
use webdriver::error::{ErrorStatus, WebDriverError, WebDriverResult};
/// A running Gecko instance.
@@ -22,7 +22,7 @@ pub(crate) enum Browser {
Remote(RemoteBrowser),
/// An existing browser instance not controlled by GeckoDriver
- Existing,
+ Existing(u16),
}
impl Browser {
@@ -30,7 +30,29 @@ impl Browser {
match self {
Browser::Local(x) => x.close(wait_for_shutdown),
Browser::Remote(x) => x.close(),
- Browser::Existing => Ok(()),
+ Browser::Existing(_) => Ok(()),
+ }
+ }
+
+ pub(crate) fn marionette_port(&mut self) -> WebDriverResult<Option<u16>> {
+ match self {
+ Browser::Local(x) => x.marionette_port(),
+ Browser::Remote(x) => x.marionette_port(),
+ Browser::Existing(x) => Ok(Some(*x)),
+ }
+ }
+
+ pub(crate) fn update_marionette_port(&mut self, port: u16) {
+ match self {
+ Browser::Local(x) => x.update_marionette_port(port),
+ Browser::Remote(x) => x.update_marionette_port(port),
+ Browser::Existing(x) => {
+ if port != *x {
+ error!(
+ "Cannot re-assign Marionette port when connected to an existing browser"
+ );
+ }
+ }
}
}
}
@@ -38,8 +60,10 @@ impl Browser {
#[derive(Debug)]
/// A local Firefox process, running on this (host) device.
pub(crate) struct LocalBrowser {
- process: FirefoxProcess,
+ marionette_port: u16,
prefs_backup: Option<PrefsBackup>,
+ process: FirefoxProcess,
+ profile_path: Option<PathBuf>,
}
impl LocalBrowser {
@@ -58,26 +82,34 @@ impl LocalBrowser {
)
})?;
- let is_custom_profile = options.profile.is_some();
+ let is_custom_profile = matches!(options.profile, ProfileType::Path(_));
let mut profile = match options.profile {
- Some(x) => x,
- None => Profile::new()?,
+ ProfileType::Named => None,
+ ProfileType::Path(x) => Some(x),
+ ProfileType::Temporary => Some(Profile::new()?),
};
- let prefs_backup = set_prefs(
- marionette_port,
- &mut profile,
- is_custom_profile,
- options.prefs,
- jsdebugger,
- )
- .map_err(|e| {
- WebDriverError::new(
- ErrorStatus::SessionNotCreated,
- format!("Failed to set preferences: {}", e),
+ let (profile_path, prefs_backup) = if let Some(ref mut profile) = profile {
+ let profile_path = profile.path.clone();
+ let prefs_backup = set_prefs(
+ marionette_port,
+ profile,
+ is_custom_profile,
+ options.prefs,
+ jsdebugger,
)
- })?;
+ .map_err(|e| {
+ WebDriverError::new(
+ ErrorStatus::SessionNotCreated,
+ format!("Failed to set preferences: {}", e),
+ )
+ })?;
+ (Some(profile_path), prefs_backup)
+ } else {
+ warn!("Unable to set geckodriver prefs when using a named profile");
+ (None, None)
+ };
let mut runner = FirefoxRunner::new(&binary, profile);
@@ -109,8 +141,10 @@ impl LocalBrowser {
};
Ok(LocalBrowser {
- process,
+ marionette_port,
prefs_backup,
+ process,
+ profile_path,
})
}
@@ -132,6 +166,26 @@ impl LocalBrowser {
Ok(())
}
+ fn marionette_port(&mut self) -> WebDriverResult<Option<u16>> {
+ if self.marionette_port != 0 {
+ return Ok(Some(self.marionette_port));
+ }
+
+ if let Some(profile_path) = self.profile_path.as_ref() {
+ return Ok(read_marionette_port(profile_path));
+ }
+
+ // This should be impossible, but it isn't enforced
+ Err(WebDriverError::new(
+ ErrorStatus::SessionNotCreated,
+ "Port not known when using named profile",
+ ))
+ }
+
+ fn update_marionette_port(&mut self, port: u16) {
+ self.marionette_port = port;
+ }
+
pub(crate) fn check_status(&mut self) -> Option<String> {
match self.process.try_wait() {
Ok(Some(status)) => Some(
@@ -146,10 +200,33 @@ impl LocalBrowser {
}
}
+fn read_marionette_port(profile_path: &Path) -> Option<u16> {
+ let port_file = profile_path.join("MarionetteActivePort");
+ let mut port_str = String::with_capacity(6);
+ let mut file = match fs::File::open(&port_file) {
+ Ok(file) => file,
+ Err(_) => {
+ trace!("Failed to open {}", &port_file.to_string_lossy());
+ return None;
+ }
+ };
+ if let Err(e) = file.read_to_string(&mut port_str) {
+ trace!("Failed to read {}: {}", &port_file.to_string_lossy(), e);
+ return None;
+ };
+ println!("Read port: {}", port_str);
+ let port = port_str.parse::<u16>().ok();
+ if port.is_none() {
+ warn!("Failed fo convert {} to u16", &port_str);
+ }
+ port
+}
+
#[derive(Debug)]
/// A remote instance, running on a (target) Android device.
pub(crate) struct RemoteBrowser {
handler: AndroidHandler,
+ marionette_port: u16,
}
impl RemoteBrowser {
@@ -163,9 +240,16 @@ impl RemoteBrowser {
let handler = AndroidHandler::new(&android_options, marionette_port, websocket_port)?;
// Profile management.
- let is_custom_profile = options.profile.is_some();
-
- let mut profile = options.profile.unwrap_or(Profile::new()?);
+ let (mut profile, is_custom_profile) = match options.profile {
+ ProfileType::Named => {
+ return Err(WebDriverError::new(
+ ErrorStatus::SessionNotCreated,
+ "Cannot use a named profile on Android",
+ ));
+ }
+ ProfileType::Path(x) => (x, true),
+ ProfileType::Temporary => (Profile::new()?, false),
+ };
set_prefs(
handler.marionette_target_port,
@@ -185,13 +269,24 @@ impl RemoteBrowser {
handler.launch()?;
- Ok(RemoteBrowser { handler })
+ Ok(RemoteBrowser {
+ handler,
+ marionette_port,
+ })
}
fn close(self) -> WebDriverResult<()> {
self.handler.force_stop()?;
Ok(())
}
+
+ fn marionette_port(&mut self) -> WebDriverResult<Option<u16>> {
+ Ok(Some(self.marionette_port))
+ }
+
+ fn update_marionette_port(&mut self, port: u16) {
+ self.marionette_port = port;
+ }
}
fn set_prefs(
@@ -209,14 +304,14 @@ fn set_prefs(
})?;
let backup_prefs = if custom_profile && prefs.path.exists() {
- Some(PrefsBackup::new(&prefs)?)
+ Some(PrefsBackup::new(prefs)?)
} else {
None
};
- for &(ref name, ref value) in prefs::DEFAULT.iter() {
+ for &(name, ref value) in prefs::DEFAULT.iter() {
if !custom_profile || !prefs.contains_key(name) {
- prefs.insert((*name).to_string(), (*value).clone());
+ prefs.insert(name.to_string(), (*value).clone());
}
}
@@ -232,9 +327,6 @@ fn set_prefs(
prefs.insert("marionette.port", Pref::new(port));
prefs.insert("remote.log.level", logging::max_level().into());
- // Deprecated since Firefox 91.
- prefs.insert("marionette.log.level", logging::max_level().into());
-
prefs.write().map_err(|e| {
WebDriverError::new(
ErrorStatus::UnknownError,
@@ -284,12 +376,15 @@ impl PrefsBackup {
#[cfg(test)]
mod tests {
use super::set_prefs;
- use crate::capabilities::FirefoxOptions;
+ use crate::browser::read_marionette_port;
+ use crate::capabilities::{FirefoxOptions, ProfileType};
use mozprofile::preferences::{Pref, PrefValue};
use mozprofile::profile::Profile;
use serde_json::{Map, Value};
use std::fs::File;
use std::io::{Read, Write};
+ use std::path::Path;
+ use tempfile::tempdir;
fn example_profile() -> Value {
let mut profile_data = Vec::with_capacity(1024);
@@ -342,7 +437,10 @@ mod tests {
let opts = FirefoxOptions::from_capabilities(None, &marionette_settings, &mut caps)
.expect("Valid profile and prefs");
- let mut profile = opts.profile.expect("valid firefox profile");
+ let mut profile = match opts.profile {
+ ProfileType::Path(profile) => profile,
+ _ => panic!("Expected ProfileType::Path"),
+ };
set_prefs(2828, &mut profile, true, opts.prefs, false).expect("set preferences");
@@ -420,4 +518,24 @@ mod tests {
.unwrap();
assert_eq!(final_prefs_data, initial_prefs_data);
}
+
+ #[test]
+ fn test_local_read_marionette_port() {
+ fn create_port_file(profile_path: &Path, data: &[u8]) {
+ let port_path = profile_path.join("MarionetteActivePort");
+ let mut file = File::create(&port_path).unwrap();
+ file.write_all(data).unwrap();
+ }
+
+ let profile_dir = tempdir().unwrap();
+ let profile_path = profile_dir.path();
+ assert_eq!(read_marionette_port(profile_path), None);
+ assert_eq!(read_marionette_port(profile_path), None);
+ create_port_file(profile_path, b"");
+ assert_eq!(read_marionette_port(profile_path), None);
+ create_port_file(profile_path, b"1234");
+ assert_eq!(read_marionette_port(profile_path), Some(1234));
+ create_port_file(profile_path, b"1234abc");
+ assert_eq!(read_marionette_port(profile_path), None);
+ }
}
diff --git a/src/capabilities.rs b/src/capabilities.rs
index 278530e..97e1e28 100644
--- a/src/capabilities.rs
+++ b/src/capabilities.rs
@@ -178,10 +178,8 @@ impl<'a> BrowserCapabilities for FirefoxCapabilities<'a> {
Ok(true)
}
- fn web_socket_url(&mut self, caps: &Capabilities) -> WebDriverResult<bool> {
- self.browser_version(caps)?
- .map(|v| self.compare_browser_version(&v, ">=90"))
- .unwrap_or(Ok(false))
+ fn web_socket_url(&mut self, _: &Capabilities) -> WebDriverResult<bool> {
+ Ok(true)
}
fn validate_custom(&mut self, name: &str, value: &Value) -> WebDriverResult<()> {
@@ -369,6 +367,19 @@ impl AndroidOptions {
}
}
+#[derive(Debug, PartialEq)]
+pub enum ProfileType {
+ Path(Profile),
+ Named,
+ Temporary,
+}
+
+impl Default for ProfileType {
+ fn default() -> Self {
+ ProfileType::Temporary
+ }
+}
+
/// Rust representation of `moz:firefoxOptions`.
///
/// Calling `FirefoxOptions::from_capabilities(binary, capabilities)` causes
@@ -378,7 +389,7 @@ impl AndroidOptions {
#[derive(Default, Debug)]
pub struct FirefoxOptions {
pub binary: Option<PathBuf>,
- pub profile: Option<Profile>,
+ pub profile: ProfileType,
pub args: Option<Vec<String>>,
pub env: Option<Vec<(String, String)>>,
pub log: LogOptions,
@@ -409,33 +420,71 @@ impl FirefoxOptions {
)
})?;
- rv.android = FirefoxOptions::load_android(settings.android_storage, &options)?;
- rv.args = FirefoxOptions::load_args(&options)?;
- rv.env = FirefoxOptions::load_env(&options)?;
- rv.log = FirefoxOptions::load_log(&options)?;
- rv.prefs = FirefoxOptions::load_prefs(&options)?;
- rv.profile = FirefoxOptions::load_profile(&options)?;
+ if options.get("androidPackage").is_some() && options.get("binary").is_some() {
+ return Err(WebDriverError::new(
+ ErrorStatus::InvalidArgument,
+ "androidPackage and binary are mutual exclusive",
+ ));
+ }
+
+ rv.android = FirefoxOptions::load_android(settings.android_storage, options)?;
+ rv.args = FirefoxOptions::load_args(options)?;
+ rv.env = FirefoxOptions::load_env(options)?;
+ rv.log = FirefoxOptions::load_log(options)?;
+ rv.prefs = FirefoxOptions::load_prefs(options)?;
+ if let Some(profile) = FirefoxOptions::load_profile(options)? {
+ rv.profile = ProfileType::Path(profile);
+ }
}
if let Some(args) = rv.args.as_ref() {
let os_args = parse_args(args.iter().map(OsString::from).collect::<Vec<_>>().iter());
+
if let Some(path) = get_arg_value(os_args.iter(), Arg::Profile) {
- if rv.profile.is_some() {
+ if let 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 = Some(Profile::new_from_path(&path_buf)?);
+ rv.profile = ProfileType::Path(Profile::new_from_path(&path_buf)?);
}
- if get_arg_value(os_args.iter(), Arg::NamedProfile).is_some() && rv.profile.is_some() {
+ if get_arg_value(os_args.iter(), Arg::NamedProfile).is_some() {
+ if let 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.
+ if let 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,
- "Can't provide both a -P argument and a profile",
+ format!("Argument {} can't be set via capabilities", arg),
));
- }
+ };
}
let has_web_socket_url = matched
@@ -459,6 +508,32 @@ impl FirefoxOptions {
remote_args.push("--remote-debugging-port".to_owned());
remote_args.push(settings.websocket_port.to_string());
+ // Handle additional hosts for WebDriver BiDi WebSocket connections
+ if !settings.allow_hosts.is_empty() {
+ remote_args.push("--remote-allow-hosts".to_owned());
+ remote_args.push(
+ settings
+ .allow_hosts
+ .iter()
+ .map(|host| host.to_string())
+ .collect::<Vec<String>>()
+ .join(","),
+ );
+ }
+
+ // Handle additional origins for WebDriver BiDi WebSocket connections
+ if !settings.allow_origins.is_empty() {
+ remote_args.push("--remote-allow-origins".to_owned());
+ remote_args.push(
+ settings
+ .allow_origins
+ .iter()
+ .map(|origin| origin.to_string())
+ .collect::<Vec<String>>()
+ .join(","),
+ );
+ }
+
if let Some(ref mut args) = rv.args {
args.append(&mut remote_args);
} else {
@@ -506,11 +581,7 @@ impl FirefoxOptions {
fn load_args(options: &Capabilities) -> WebDriverResult<Option<Vec<String>>> {
if let 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",
- )
+ WebDriverError::new(ErrorStatus::InvalidArgument, "Arguments were not an array")
})?;
let args = args_array
.iter()
@@ -522,6 +593,7 @@ impl FirefoxOptions {
"Arguments entries were not all strings",
)
})?;
+
Ok(Some(args))
} else {
Ok(None)
@@ -792,7 +864,7 @@ mod tests {
use serde_json::{json, Map, Value};
use std::fs::File;
use std::io::Read;
-
+ use url::{Host, Url};
use webdriver::capabilities::Capabilities;
fn example_profile() -> Value {
@@ -863,6 +935,23 @@ mod tests {
}
#[test]
+ fn fx_options_from_capabilities_with_blocked_firefox_arguments() {
+ let blocked_args = vec![
+ "--marionette",
+ "--remote-allow-hosts",
+ "--remote-allow-origins",
+ "--remote-debugging-port",
+ ];
+
+ for arg in blocked_args {
+ let mut firefox_opts = Capabilities::new();
+ firefox_opts.insert("args".into(), json!([arg]));
+
+ make_options(firefox_opts, None).expect_err("invalid firefox options");
+ }
+ }
+
+ #[test]
fn fx_options_from_capabilities_with_websocket_url_not_set() {
let mut caps = Capabilities::new();
@@ -905,12 +994,56 @@ mod tests {
if let Some(args) = opts.args {
let mut iter = args.iter();
- assert!(iter
- .find(|&arg| arg == &"--remote-debugging-port".to_owned())
- .is_some());
+ assert!(iter.any(|arg| arg == &"--remote-debugging-port".to_owned()));
assert_eq!(iter.next(), Some(&"1234".to_owned()));
} else {
- assert!(false, "CLI arguments for Firefox not found");
+ panic!("CLI arguments for Firefox not found");
+ }
+ }
+
+ #[test]
+ fn fx_options_from_capabilities_with_websocket_and_allow_hosts() {
+ let mut caps = Capabilities::new();
+ caps.insert("webSocketUrl".into(), json!(true));
+
+ let mut marionette_settings: MarionetteSettings = Default::default();
+ marionette_settings.allow_hosts = vec![
+ Host::parse("foo").expect("host"),
+ Host::parse("bar").expect("host"),
+ ];
+ let opts = FirefoxOptions::from_capabilities(None, &marionette_settings, &mut caps)
+ .expect("Valid Firefox options");
+
+ if let Some(args) = opts.args {
+ let mut iter = args.iter();
+ assert!(iter.any(|arg| arg == &"--remote-allow-hosts".to_owned()));
+ assert_eq!(iter.next(), Some(&"foo,bar".to_owned()));
+ assert!(!iter.any(|arg| arg == &"--remote-allow-origins".to_owned()));
+ } else {
+ panic!("CLI arguments for Firefox not found");
+ }
+ }
+
+ #[test]
+ fn fx_options_from_capabilities_with_websocket_and_allow_origins() {
+ let mut caps = Capabilities::new();
+ caps.insert("webSocketUrl".into(), json!(true));
+
+ let mut marionette_settings: MarionetteSettings = Default::default();
+ marionette_settings.allow_origins = vec![
+ Url::parse("http://foo/").expect("url"),
+ Url::parse("http://bar/").expect("url"),
+ ];
+ let opts = FirefoxOptions::from_capabilities(None, &marionette_settings, &mut caps)
+ .expect("Valid Firefox options");
+
+ if let Some(args) = opts.args {
+ let mut iter = args.iter();
+ assert!(iter.any(|arg| arg == &"--remote-allow-origins".to_owned()));
+ assert_eq!(iter.next(), Some(&"http://foo/,http://bar/".to_owned()));
+ assert!(!iter.any(|arg| arg == &"--remote-allow-hosts".to_owned()));
+ } else {
+ panic!("CLI arguments for Firefox not found");
}
}
@@ -951,12 +1084,10 @@ mod tests {
if let Some(args) = opts.args {
let mut iter = args.iter();
- assert!(iter
- .find(|&arg| arg == &"--remote-debugging-port".to_owned())
- .is_some());
+ assert!(iter.any(|arg| arg == &"--remote-debugging-port".to_owned()));
assert_eq!(iter.next(), Some(&"1234".to_owned()));
} else {
- assert!(false, "CLI arguments for Firefox not found");
+ panic!("CLI arguments for Firefox not found");
}
assert!(opts
@@ -976,6 +1107,16 @@ mod tests {
}
#[test]
+ fn fx_options_android_package_and_binary() {
+ let mut firefox_opts = Capabilities::new();
+ firefox_opts.insert("androidPackage".into(), json!("foo"));
+ firefox_opts.insert("binary".into(), json!("bar"));
+
+ make_options(firefox_opts, None)
+ .expect_err("androidPackage and binary are mutual exclusive");
+ }
+
+ #[test]
fn fx_options_android_no_package() {
let mut firefox_opts = Capabilities::new();
firefox_opts.insert("androidAvtivity".into(), json!("foo"));
@@ -1199,7 +1340,7 @@ mod tests {
let env = Value::Number(1.into());
let mut firefox_opts = Capabilities::new();
- firefox_opts.insert("env".into(), env.into());
+ firefox_opts.insert("env".into(), env);
make_options(firefox_opts, None).expect_err("invalid firefox options");
}
@@ -1222,7 +1363,10 @@ mod tests {
firefox_opts.insert("profile".into(), encoded_profile);
let opts = make_options(firefox_opts, None).expect("valid firefox options");
- let mut profile = opts.profile.expect("valid firefox profile");
+ let mut profile = match opts.profile {
+ ProfileType::Path(profile) => profile,
+ _ => panic!("Expected ProfileType::Path"),
+ };
let prefs = profile.user_prefs().expect("valid preferences");
println!("{:#?}", prefs.prefs);
@@ -1238,7 +1382,26 @@ mod tests {
let mut firefox_opts = Capabilities::new();
firefox_opts.insert("args".into(), json!(["--profile", "foo"]));
- make_options(firefox_opts, None).expect("Valid args");
+ let options = make_options(firefox_opts, None).expect("Valid args");
+ assert!(matches!(options.profile, ProfileType::Path(_)));
+ }
+
+ #[test]
+ fn fx_options_args_named_profile() {
+ let mut firefox_opts = Capabilities::new();
+ firefox_opts.insert("args".into(), json!(["-P", "foo"]));
+
+ let options = make_options(firefox_opts, None).expect("Valid args");
+ assert!(matches!(options.profile, ProfileType::Named));
+ }
+
+ #[test]
+ fn fx_options_args_no_profile() {
+ let mut firefox_opts = Capabilities::new();
+ firefox_opts.insert("args".into(), json!(["--headless"]));
+
+ let options = make_options(firefox_opts, None).expect("Valid args");
+ assert!(matches!(options.profile, ProfileType::Temporary));
}
#[test]
diff --git a/src/logging.rs b/src/logging.rs
index de477dc..73ce062 100644
--- a/src/logging.rs
+++ b/src/logging.rs
@@ -219,8 +219,6 @@ mod tests {
use std::str::FromStr;
use std::sync::Mutex;
- use chrono;
- use log;
use mozprofile::preferences::{Pref, PrefValue};
lazy_static! {
@@ -239,17 +237,6 @@ mod tests {
}
#[test]
- fn test_level_eq() {
- assert_eq!(Level::Fatal, Level::Fatal);
- assert_eq!(Level::Error, Level::Error);
- assert_eq!(Level::Warn, Level::Warn);
- assert_eq!(Level::Info, Level::Info);
- assert_eq!(Level::Config, Level::Config);
- assert_eq!(Level::Debug, Level::Debug);
- assert_eq!(Level::Trace, Level::Trace);
- }
-
- #[test]
fn test_level_from_log() {
assert_eq!(Level::from(log::Level::Error), Level::Error);
assert_eq!(Level::from(log::Level::Warn), Level::Warn);
diff --git a/src/main.rs b/src/main.rs
index c6c5503..115d650 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -17,6 +17,7 @@ extern crate serde;
extern crate serde_derive;
extern crate serde_json;
extern crate serde_yaml;
+extern crate url;
extern crate uuid;
extern crate webdriver;
extern crate zip;
@@ -27,12 +28,12 @@ extern crate log;
use std::env;
use std::fmt;
use std::io;
-use std::net::{IpAddr, SocketAddr};
+use std::net::{IpAddr, SocketAddr, ToSocketAddrs};
use std::path::PathBuf;
use std::result;
use std::str::FromStr;
-use clap::{App, Arg};
+use clap::{App, AppSettings, Arg};
macro_rules! try_opt {
($expr:expr, $err_type:expr, $err_msg:expr) => {{
@@ -59,6 +60,7 @@ use crate::command::extension_routes;
use crate::logging::Level;
use crate::marionette::{MarionetteHandler, MarionetteSettings};
use mozdevice::AndroidStorageInput;
+use url::{Host, Url};
const EXIT_SUCCESS: i32 = 0;
const EXIT_USAGE: i32 = 64;
@@ -103,7 +105,7 @@ impl fmt::Display for FatalError {
let s = match *self {
Parsing(ref err) => err.to_string(),
Usage(ref s) => format!("error: {}", s),
- Server(ref err) => format!("error: {}", err.to_string()),
+ Server(ref err) => format!("error: {}", err),
};
write!(f, "{}", s)
}
@@ -111,67 +113,162 @@ impl fmt::Display for FatalError {
macro_rules! usage {
($msg:expr) => {
- return Err(FatalError::Usage($msg.to_string()));
+ return Err(FatalError::Usage($msg.to_string()))
};
($fmt:expr, $($arg:tt)+) => {
- return Err(FatalError::Usage(format!($fmt, $($arg)+)));
+ return Err(FatalError::Usage(format!($fmt, $($arg)+)))
};
}
type ProgramResult<T> = result::Result<T, FatalError>;
+#[allow(clippy::large_enum_variant)]
enum Operation {
Help,
Version,
Server {
log_level: Option<Level>,
- host: String,
address: SocketAddr,
+ allow_hosts: Vec<Host>,
+ allow_origins: Vec<Url>,
settings: MarionetteSettings,
deprecated_storage_arg: bool,
},
}
+/// Get a socket address from the provided host and port
+///
+/// # Arguments
+/// * `webdriver_host` - The hostname on which the server will listen
+/// * `webdriver_port` - The port on which the server will listen
+///
+/// When the host and port resolve to multiple addresses, prefer
+/// IPv4 addresses vs IPv6.
+fn server_address(webdriver_host: &str, webdriver_port: u16) -> ProgramResult<SocketAddr> {
+ let mut socket_addrs = match format!("{}:{}", webdriver_host, webdriver_port).to_socket_addrs()
+ {
+ Ok(addrs) => addrs.collect::<Vec<_>>(),
+ Err(e) => usage!("{}: {}:{}", e, webdriver_host, webdriver_port),
+ };
+ if socket_addrs.is_empty() {
+ usage!(
+ "Unable to resolve host: {}:{}",
+ webdriver_host,
+ webdriver_port
+ )
+ }
+ // Prefer ipv4 address
+ socket_addrs.sort_by(|a, b| {
+ let a_val = if a.ip().is_ipv4() { 0 } else { 1 };
+ let b_val = if b.ip().is_ipv4() { 0 } else { 1 };
+ a_val.partial_cmp(&b_val).expect("Comparison failed")
+ });
+ Ok(socket_addrs.remove(0))
+}
+
+/// Parse a given string into a Host
+fn parse_hostname(webdriver_host: &str) -> Result<Host, url::ParseError> {
+ let host_str = if let Ok(ip_addr) = IpAddr::from_str(webdriver_host) {
+ // In this case we have an IP address as the host
+ if ip_addr.is_ipv6() {
+ // Convert to quoted form
+ format!("[{}]", &webdriver_host)
+ } else {
+ webdriver_host.into()
+ }
+ } else {
+ webdriver_host.into()
+ };
+
+ Host::parse(&host_str)
+}
+
+/// Get a list of default hostnames to allow
+///
+/// This only covers domain names, not IP addresses, since IP adresses
+/// are always accepted.
+fn get_default_allowed_hosts(ip: IpAddr) -> Vec<Result<Host, url::ParseError>> {
+ let localhost_is_loopback = ("localhost".to_string(), 80)
+ .to_socket_addrs()
+ .map(|addr_iter| {
+ addr_iter
+ .map(|addr| addr.ip())
+ .filter(|ip| ip.is_loopback())
+ })
+ .iter()
+ .len()
+ > 0;
+ if ip.is_loopback() && localhost_is_loopback {
+ vec![Host::parse("localhost")]
+ } else {
+ vec![]
+ }
+}
+
+fn get_allowed_hosts(
+ host: Host,
+ allow_hosts: Option<clap::Values>,
+) -> Result<Vec<Host>, url::ParseError> {
+ allow_hosts
+ .map(|hosts| hosts.map(Host::parse).collect::<Vec<_>>())
+ .unwrap_or_else(|| match host {
+ Host::Domain(_) => {
+ vec![Ok(host.clone())]
+ }
+ Host::Ipv4(ip) => get_default_allowed_hosts(IpAddr::V4(ip)),
+ Host::Ipv6(ip) => get_default_allowed_hosts(IpAddr::V6(ip)),
+ })
+ .into_iter()
+ .collect::<Result<Vec<Host>, url::ParseError>>()
+}
+
+fn get_allowed_origins(allow_origins: Option<clap::Values>) -> Result<Vec<Url>, url::ParseError> {
+ allow_origins
+ .map(|origins| {
+ origins
+ .map(Url::parse)
+ .collect::<Result<Vec<Url>, url::ParseError>>()
+ })
+ .unwrap_or_else(|| Ok(vec![]))
+}
+
fn parse_args(app: &mut App) -> ProgramResult<Operation> {
- let matches = app.get_matches_from_safe_borrow(env::args())?;
+ let args = app.try_get_matches_from_mut(env::args())?;
- let log_level = if matches.is_present("log_level") {
- Level::from_str(matches.value_of("log_level").unwrap()).ok()
+ if args.is_present("help") {
+ return Ok(Operation::Help);
+ } else if args.is_present("version") {
+ return Ok(Operation::Version);
+ }
+
+ let log_level = if args.is_present("log_level") {
+ Level::from_str(args.value_of("log_level").unwrap()).ok()
} else {
- Some(match matches.occurrences_of("verbosity") {
+ Some(match args.occurrences_of("verbosity") {
0 => Level::Info,
1 => Level::Debug,
_ => Level::Trace,
})
};
- let host = matches.value_of("webdriver_host").unwrap();
- let port = {
- let s = matches.value_of("webdriver_port").unwrap();
+ let webdriver_host = args.value_of("webdriver_host").unwrap();
+ let webdriver_port = {
+ let s = args.value_of("webdriver_port").unwrap();
match u16::from_str(s) {
Ok(n) => n,
Err(e) => usage!("invalid --port: {}: {}", e, s),
}
};
- let address = match IpAddr::from_str(host) {
- Ok(addr) => SocketAddr::new(addr, port),
- Err(e) => usage!("{}: {}:{}", e, host, port),
- };
- if !address.ip().is_loopback() {
- usage!(
- "invalid --host: {}. Must be a local loopback interface",
- host
- )
- }
- let android_storage = value_t!(matches, "android_storage", AndroidStorageInput)
+ let android_storage = args
+ .value_of_t::<AndroidStorageInput>("android_storage")
.unwrap_or(AndroidStorageInput::Auto);
- let binary = matches.value_of("binary").map(PathBuf::from);
+ let binary = args.value_of("binary").map(PathBuf::from);
- let marionette_host = matches.value_of("marionette_host").unwrap();
- let marionette_port = match matches.value_of("marionette_port") {
+ let marionette_host = args.value_of("marionette_host").unwrap();
+ let marionette_port = match args.value_of("marionette_port") {
Some(s) => match u16::from_str(s) {
Ok(n) => Some(n),
Err(e) => usage!("invalid --marionette-port: {}", e),
@@ -181,7 +278,7 @@ fn parse_args(app: &mut App) -> ProgramResult<Operation> {
// For Android the port on the device must be the same as the one on the
// host. For now default to 9222, which is the default for --remote-debugging-port.
- let websocket_port = match matches.value_of("websocket_port") {
+ let websocket_port = match args.value_of("websocket_port") {
Some(s) => match u16::from_str(s) {
Ok(n) => n,
Err(e) => usage!("invalid --websocket-port: {}", e),
@@ -189,30 +286,42 @@ fn parse_args(app: &mut App) -> ProgramResult<Operation> {
None => 9222,
};
- let op = if matches.is_present("help") {
- Operation::Help
- } else if matches.is_present("version") {
- Operation::Version
- } else {
- let settings = MarionetteSettings {
- binary,
- connect_existing: matches.is_present("connect_existing"),
- host: marionette_host.to_string(),
- port: marionette_port,
- websocket_port,
- jsdebugger: matches.is_present("jsdebugger"),
- android_storage,
- };
- Operation::Server {
- log_level,
- host: host.into(),
- address,
- settings,
- deprecated_storage_arg: matches.is_present("android_storage"),
- }
+ let host = match parse_hostname(webdriver_host) {
+ Ok(name) => name,
+ Err(e) => usage!("invalid --host {}: {}", webdriver_host, e),
+ };
+
+ let allow_hosts = match get_allowed_hosts(host, args.values_of("allow_hosts")) {
+ Ok(hosts) => hosts,
+ Err(e) => usage!("invalid --allow-hosts {}", e),
};
- Ok(op)
+ let allow_origins = match get_allowed_origins(args.values_of("allow_origins")) {
+ Ok(origins) => origins,
+ Err(e) => usage!("invalid --allow-origins {}", e),
+ };
+
+ let address = server_address(webdriver_host, webdriver_port)?;
+
+ let settings = MarionetteSettings {
+ binary,
+ connect_existing: args.is_present("connect_existing"),
+ host: marionette_host.into(),
+ port: marionette_port,
+ websocket_port,
+ allow_hosts: allow_hosts.clone(),
+ allow_origins: allow_origins.clone(),
+ jsdebugger: args.is_present("jsdebugger"),
+ android_storage,
+ };
+ Ok(Operation::Server {
+ log_level,
+ allow_hosts,
+ allow_origins,
+ address,
+ settings,
+ deprecated_storage_arg: args.is_present("android_storage"),
+ })
}
fn inner_main(app: &mut App) -> ProgramResult<()> {
@@ -222,8 +331,9 @@ fn inner_main(app: &mut App) -> ProgramResult<()> {
Operation::Server {
log_level,
- host,
address,
+ allow_hosts,
+ allow_origins,
settings,
deprecated_storage_arg,
} => {
@@ -238,7 +348,13 @@ fn inner_main(app: &mut App) -> ProgramResult<()> {
};
let handler = MarionetteHandler::new(settings);
- let listening = webdriver::server::start(host, address, handler, extension_routes())?;
+ let listening = webdriver::server::start(
+ address,
+ allow_hosts,
+ allow_origins,
+ handler,
+ extension_routes(),
+ )?;
info!("Listening on {}", listening.socket);
}
}
@@ -266,11 +382,13 @@ fn main() {
});
}
-fn make_app<'a, 'b>() -> App<'a, 'b> {
+fn make_app<'a>() -> App<'a> {
App::new(format!("geckodriver {}", build::build_info()))
+ .setting(AppSettings::NoAutoHelp)
+ .setting(AppSettings::NoAutoVersion)
.about("WebDriver implementation for Firefox")
.arg(
- Arg::with_name("webdriver_host")
+ Arg::new("webdriver_host")
.long("host")
.takes_value(true)
.value_name("HOST")
@@ -278,8 +396,8 @@ fn make_app<'a, 'b>() -> App<'a, 'b> {
.help("Host IP to use for WebDriver server"),
)
.arg(
- Arg::with_name("webdriver_port")
- .short("p")
+ Arg::new("webdriver_port")
+ .short('p')
.long("port")
.takes_value(true)
.value_name("PORT")
@@ -287,15 +405,15 @@ fn make_app<'a, 'b>() -> App<'a, 'b> {
.help("Port to use for WebDriver server"),
)
.arg(
- Arg::with_name("binary")
- .short("b")
+ Arg::new("binary")
+ .short('b')
.long("binary")
.takes_value(true)
.value_name("BINARY")
.help("Path to the Firefox binary"),
)
.arg(
- Arg::with_name("marionette_host")
+ Arg::new("marionette_host")
.long("marionette-host")
.takes_value(true)
.value_name("HOST")
@@ -303,14 +421,14 @@ fn make_app<'a, 'b>() -> App<'a, 'b> {
.help("Host to use to connect to Gecko"),
)
.arg(
- Arg::with_name("marionette_port")
+ Arg::new("marionette_port")
.long("marionette-port")
.takes_value(true)
.value_name("PORT")
.help("Port to use to connect to Gecko [default: system-allocated port]"),
)
.arg(
- Arg::with_name("websocket_port")
+ Arg::new("websocket_port")
.long("websocket-port")
.takes_value(true)
.value_name("PORT")
@@ -318,25 +436,25 @@ fn make_app<'a, 'b>() -> App<'a, 'b> {
.help("Port to use to connect to WebDriver BiDi [default: 9222]"),
)
.arg(
- Arg::with_name("connect_existing")
+ Arg::new("connect_existing")
.long("connect-existing")
.requires("marionette_port")
.help("Connect to an existing Firefox instance"),
)
.arg(
- Arg::with_name("jsdebugger")
+ Arg::new("jsdebugger")
.long("jsdebugger")
.help("Attach browser toolbox debugger for Firefox"),
)
.arg(
- Arg::with_name("verbosity")
- .multiple(true)
+ Arg::new("verbosity")
+ .multiple_occurrences(true)
.conflicts_with("log_level")
- .short("v")
+ .short('v')
.help("Log level verbosity (-v for debug and -vv for trace level)"),
)
.arg(
- Arg::with_name("log_level")
+ Arg::new("log_level")
.long("log")
.takes_value(true)
.value_name("LEVEL")
@@ -344,24 +462,40 @@ fn make_app<'a, 'b>() -> App<'a, 'b> {
.help("Set Gecko log level"),
)
.arg(
- Arg::with_name("help")
- .short("h")
+ Arg::new("help")
+ .short('h')
.long("help")
.help("Prints this message"),
)
.arg(
- Arg::with_name("version")
- .short("V")
+ Arg::new("version")
+ .short('V')
.long("version")
.help("Prints version and copying information"),
)
.arg(
- Arg::with_name("android_storage")
+ Arg::new("android_storage")
.long("android-storage")
.possible_values(&["auto", "app", "internal", "sdcard"])
.value_name("ANDROID_STORAGE")
.help("Selects storage location to be used for test data (deprecated)."),
)
+ .arg(
+ Arg::new("allow_hosts")
+ .long("allow-hosts")
+ .takes_value(true)
+ .multiple_values(true)
+ .value_name("ALLOW_HOSTS")
+ .help("List of hostnames to allow. By default the value of --host is allowed, and in addition if that's a well known local address, other variations on well known local addresses are allowed. If --allow-hosts is provided only exactly those hosts are allowed."),
+ )
+ .arg(
+ Arg::new("allow_origins")
+ .long("allow-origins")
+ .takes_value(true)
+ .multiple_values(true)
+ .value_name("ALLOW_ORIGINS")
+ .help("List of request origins to allow. These must be formatted as scheme://host:port. By default any request with an origin header is rejected. If --allow-origins is provided then only exactly those origins are allowed."),
+ )
}
fn get_program_name() -> String {
diff --git a/src/marionette.rs b/src/marionette.rs
index cf3f40e..325cc9a 100644
--- a/src/marionette.rs
+++ b/src/marionette.rs
@@ -4,7 +4,7 @@
use crate::browser::{Browser, LocalBrowser, RemoteBrowser};
use crate::build;
-use crate::capabilities::{FirefoxCapabilities, FirefoxOptions};
+use crate::capabilities::{FirefoxCapabilities, FirefoxOptions, ProfileType};
use crate::command::{
AddonInstallParameters, AddonUninstallParameters, GeckoContextParameters,
GeckoExtensionCommand, GeckoExtensionRoute, CHROME_ELEMENT_KEY,
@@ -37,17 +37,19 @@ use std::path::PathBuf;
use std::sync::Mutex;
use std::thread;
use std::time;
+use url::{Host, Url};
+use webdriver::capabilities::BrowserCapabilities;
use webdriver::command::WebDriverCommand::{
AcceptAlert, AddCookie, CloseWindow, DeleteCookie, DeleteCookies, DeleteSession, DismissAlert,
ElementClear, ElementClick, ElementSendKeys, ExecuteAsyncScript, ExecuteScript, Extension,
FindElement, FindElementElement, FindElementElements, FindElements, FullscreenWindow, Get,
GetActiveElement, GetAlertText, GetCSSValue, GetCookies, GetCurrentUrl, GetElementAttribute,
GetElementProperty, GetElementRect, GetElementTagName, GetElementText, GetNamedCookie,
- GetPageSource, GetTimeouts, GetTitle, GetWindowHandle, GetWindowHandles, GetWindowRect, GoBack,
- GoForward, IsDisplayed, IsEnabled, IsSelected, MaximizeWindow, MinimizeWindow, NewSession,
- NewWindow, PerformActions, Print, Refresh, ReleaseActions, SendAlertText, SetTimeouts,
- SetWindowRect, Status, SwitchToFrame, SwitchToParentFrame, SwitchToWindow,
- TakeElementScreenshot, TakeScreenshot,
+ GetPageSource, GetShadowRoot, GetTimeouts, GetTitle, GetWindowHandle, GetWindowHandles,
+ GetWindowRect, GoBack, GoForward, IsDisplayed, IsEnabled, IsSelected, MaximizeWindow,
+ MinimizeWindow, NewSession, NewWindow, PerformActions, Print, Refresh, ReleaseActions,
+ SendAlertText, SetTimeouts, SetWindowRect, Status, SwitchToFrame, SwitchToParentFrame,
+ SwitchToWindow, TakeElementScreenshot, TakeScreenshot,
};
use webdriver::command::{
ActionsParameters, AddCookieParameters, GetNamedCookieParameters, GetParameters,
@@ -57,7 +59,8 @@ use webdriver::command::{
};
use webdriver::command::{WebDriverCommand, WebDriverMessage};
use webdriver::common::{
- Cookie, Date, FrameId, LocatorStrategy, WebElement, ELEMENT_KEY, FRAME_KEY, WINDOW_KEY,
+ Cookie, Date, FrameId, LocatorStrategy, ShadowRoot, WebElement, ELEMENT_KEY, FRAME_KEY,
+ SHADOW_KEY, WINDOW_KEY,
};
use webdriver::error::{ErrorStatus, WebDriverError, WebDriverResult};
use webdriver::response::{
@@ -82,6 +85,8 @@ pub(crate) struct MarionetteSettings {
pub(crate) host: String,
pub(crate) port: Option<u16>,
pub(crate) websocket_port: u16,
+ pub(crate) allow_hosts: Vec<Host>,
+ pub(crate) allow_origins: Vec<Url>,
/// Brings up the Browser Toolbox when starting Firefox,
/// letting you debug internals.
@@ -109,8 +114,8 @@ impl MarionetteHandler {
session_id: Option<String>,
new_session_parameters: &NewSessionParameters,
) -> WebDriverResult<MarionetteConnection> {
+ let mut fx_capabilities = FirefoxCapabilities::new(self.settings.binary.as_ref());
let (capabilities, options) = {
- let mut fx_capabilities = FirefoxCapabilities::new(self.settings.binary.as_ref());
let mut capabilities = new_session_parameters
.match_browser(&mut fx_capabilities)?
.ok_or_else(|| {
@@ -121,7 +126,7 @@ impl MarionetteHandler {
})?;
let options = FirefoxOptions::from_capabilities(
- fx_capabilities.chosen_binary,
+ fx_capabilities.chosen_binary.clone(),
&self.settings,
&mut capabilities,
)?;
@@ -133,14 +138,39 @@ impl MarionetteHandler {
}
let marionette_host = self.settings.host.to_owned();
- let marionette_port = self
- .settings
- .port
- .unwrap_or(get_free_port(&marionette_host)?);
-
- let websocket_port = match options.use_websocket {
- true => Some(self.settings.websocket_port),
- false => None,
+ let marionette_port = match self.settings.port {
+ Some(port) => port,
+ None => {
+ // If we're launching Firefox Desktop version 95 or later, and there's no port
+ // specified, we can pass 0 as the port and later read it back from
+ // the profile.
+ let can_use_profile: bool = options.android.is_none()
+ && options.profile != ProfileType::Named
+ && !self.settings.connect_existing
+ && fx_capabilities
+ .browser_version(&capabilities)
+ .map(|opt_v| {
+ opt_v
+ .map(|v| {
+ fx_capabilities
+ .compare_browser_version(&v, ">=95")
+ .unwrap_or(false)
+ })
+ .unwrap_or(false)
+ })
+ .unwrap_or(false);
+ if can_use_profile {
+ 0
+ } else {
+ get_free_port(&marionette_host)?
+ }
+ }
+ };
+
+ let websocket_port = if options.use_websocket {
+ Some(self.settings.websocket_port)
+ } else {
+ None
};
let browser = if options.android.is_some() {
@@ -166,10 +196,10 @@ impl MarionetteHandler {
self.settings.jsdebugger,
)?)
} else {
- Browser::Existing
+ Browser::Existing(marionette_port)
};
let session = MarionetteSession::new(session_id, capabilities);
- MarionetteConnection::new(marionette_host, marionette_port, browser, session)
+ MarionetteConnection::new(marionette_host, browser, session)
}
fn close_connection(&mut self, wait_for_shutdown: bool) {
@@ -214,7 +244,7 @@ impl WebDriverHandler<GeckoExtensionRoute> for MarionetteHandler {
Ok(mut connection) => {
if connection.is_none() {
if let NewSession(ref capabilities) = msg.command {
- let conn = self.create_connection(msg.session_id.clone(), &capabilities)?;
+ let conn = self.create_connection(msg.session_id.clone(), capabilities)?;
*connection = Some(conn);
} else {
return Err(WebDriverError::new(
@@ -322,6 +352,30 @@ impl MarionetteSession {
Ok(WebElement(id))
}
+ /// Converts a Marionette JSON response into a `ShadowRoot`.
+ fn to_shadow_root(&self, json_data: &Value) -> WebDriverResult<ShadowRoot> {
+ let data = try_opt!(
+ json_data.as_object(),
+ ErrorStatus::UnknownError,
+ "Failed to convert data to an object"
+ );
+
+ let shadow_root = data.get(SHADOW_KEY);
+
+ let value = try_opt!(
+ shadow_root,
+ ErrorStatus::UnknownError,
+ "Failed to extract shadow root from Marionette response"
+ );
+ let id = try_opt!(
+ value.as_str(),
+ ErrorStatus::UnknownError,
+ "Failed to convert shadow root reference value to string"
+ )
+ .to_string();
+ Ok(ShadowRoot(id))
+ }
+
fn next_command_id(&mut self) -> MessageId {
self.command_id += 1;
self.command_id
@@ -405,13 +459,9 @@ impl MarionetteSession {
"Failed to interpret script timeout duration as u64"
),
};
- // Check for the spec-compliant "pageLoad", but also for "page load",
- // which was sent by Firefox 52 and earlier.
let page_load = try_opt!(
try_opt!(
- resp.result
- .get("pageLoad")
- .or_else(|| resp.result.get("page load")),
+ resp.result.get("pageLoad"),
ErrorStatus::UnknownError,
"Missing field: pageLoad"
)
@@ -631,6 +681,14 @@ impl MarionetteSession {
.collect(),
)))
}
+ GetShadowRoot(_) => {
+ let shadow_root = self.to_shadow_root(try_opt!(
+ resp.result.get("value"),
+ ErrorStatus::UnknownError,
+ "Failed to find value field"
+ ))?;
+ WebDriverResponse::Generic(ValueResponse(serde_json::to_value(shadow_root)?))
+ }
GetActiveElement => {
let element = self.to_web_element(try_opt!(
resp.result.get("value"),
@@ -707,7 +765,7 @@ fn try_convert_to_marionette_message(
flags: vec![AppStatus::eForceQuit],
},
)),
- Browser::Existing => Some(Command::WebDriver(
+ Browser::Existing(_) => Some(Command::WebDriver(
MarionetteWebDriverCommand::DeleteSession,
)),
},
@@ -806,6 +864,11 @@ fn try_convert_to_marionette_message(
GetPageSource => Some(Command::WebDriver(
MarionetteWebDriverCommand::GetPageSource,
)),
+ GetShadowRoot(ref e) => Some(Command::WebDriver(
+ MarionetteWebDriverCommand::GetShadowRoot {
+ id: e.clone().to_string(),
+ },
+ )),
GetTitle => Some(Command::WebDriver(MarionetteWebDriverCommand::GetTitle)),
GetWindowHandle => Some(Command::WebDriver(
MarionetteWebDriverCommand::GetWindowHandle,
@@ -1063,11 +1126,10 @@ struct MarionetteConnection {
impl MarionetteConnection {
fn new(
host: String,
- port: u16,
mut browser: Browser,
session: MarionetteSession,
) -> WebDriverResult<MarionetteConnection> {
- let stream = match MarionetteConnection::connect(&host, port, &mut browser) {
+ let stream = match MarionetteConnection::connect(&host, &mut browser) {
Ok(stream) => stream,
Err(e) => {
if let Err(e) = browser.close(true) {
@@ -1083,16 +1145,15 @@ impl MarionetteConnection {
})
}
- fn connect(host: &str, port: u16, browser: &mut Browser) -> WebDriverResult<TcpStream> {
+ fn connect(host: &str, browser: &mut Browser) -> WebDriverResult<TcpStream> {
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 {}:{}",
+ "Waiting {}s to connect to browser on {}",
timeout.as_secs(),
host,
- port
);
loop {
@@ -1106,19 +1167,31 @@ impl MarionetteConnection {
}
}
- match MarionetteConnection::try_connect(host, port) {
- Ok(stream) => {
- debug!("Connection to Marionette established on {}:{}.", host, port);
- return Ok(stream);
- }
- Err(e) => {
- if now.elapsed() < timeout {
- trace!("{}. Retrying in {:?}", e.to_string(), poll_interval);
- thread::sleep(poll_interval);
- } else {
- return Err(WebDriverError::new(ErrorStatus::Timeout, e.to_string()));
+ let last_err;
+
+ if let Some(port) = browser.marionette_port()? {
+ match MarionetteConnection::try_connect(host, port) {
+ Ok(stream) => {
+ debug!("Connection to Marionette established on {}:{}.", host, port);
+ browser.update_marionette_port(port);
+ return Ok(stream);
+ }
+ Err(e) => {
+ let err_str = e.to_string();
+ last_err = Some(err_str);
}
}
+ } else {
+ last_err = Some("Failed to read marionette port".into());
+ }
+ if now.elapsed() < timeout {
+ trace!("Retrying in {:?}", poll_interval);
+ thread::sleep(poll_interval);
+ } else {
+ return Err(WebDriverError::new(
+ ErrorStatus::Timeout,
+ last_err.unwrap_or_else(|| "Unknown error".into()),
+ ));
}
}
}
@@ -1469,7 +1542,6 @@ impl ToMarionette<MarionetteFrame> for SwitchToFrameParameters {
impl ToMarionette<Window> for SwitchToWindowParameters {
fn to_marionette(&self) -> WebDriverResult<Window> {
Ok(Window {
- name: self.handle.clone(),
handle: self.handle.clone(),
})
}
diff --git a/src/prefs.rs b/src/prefs.rs
index e9a4032..b9b1785 100644
--- a/src/prefs.rs
+++ b/src/prefs.rs
@@ -29,9 +29,6 @@ lazy_static! {
// Note: Possible update tests could reset or flip the value to allow
// updates to be downloaded and applied.
("app.update.disabledForTesting", Pref::new(true)),
- // !!! For backward compatibility up to Firefox 64. Only remove
- // when this Firefox version is no longer supported by geckodriver !!!
- ("app.update.auto", Pref::new(false)),
// Enable the dump function, which sends messages to the system
// console
@@ -58,14 +55,6 @@ lazy_static! {
// Start with a blank page (about:blank)
("browser.startup.page", Pref::new(0)),
- // Do not close the window when the last tab gets closed
- // TODO: Remove once minimum supported Firefox release is 61.
- ("browser.tabs.closeWindowWithLastTab", Pref::new(false)),
-
- // Do not warn when closing all open tabs
- // TODO: Remove once minimum supported Firefox release is 61.
- ("browser.tabs.warnOnClose", Pref::new(false)),
-
// Disable the UI tour
("browser.uitour.enabled", Pref::new(false)),
@@ -126,10 +115,6 @@ lazy_static! {
// Disable the GFX sanity window
("media.sanity-test.disabled", Pref::new(true)),
- // Do not prompt with long usernames or passwords in URLs
- // TODO: Remove once minimum supported Firefox release is 61.
- ("network.http.phishy-userpass-length", Pref::new(255)),
-
// Do not automatically switch between offline and online
("network.manage-offline-status", Pref::new(false)),