From 2acb733de5bda81ed30b6f4e90be27fee6f62ff7 Mon Sep 17 00:00:00 2001 From: Henrik Skupin Date: Tue, 3 Nov 2020 17:47:06 +0100 Subject: [PATCH] Import of v0.28.0 (#1803) --- CHANGES.md | 61 ++++++- Cargo.toml | 10 +- doc/Flags.md | 58 ++++++- doc/Releasing.md | 8 +- doc/Support.md | 10 ++ doc/Usage.md | 6 +- mach_commands.py | 115 ++++++++----- moz.build | 3 +- src/android.rs | 386 +++++++++++++++++++++++++++----------------- src/capabilities.rs | 154 ++++++++++-------- src/command.rs | 61 +------ src/main.rs | 13 +- src/marionette.rs | 61 +------ src/prefs.rs | 18 +-- 14 files changed, 561 insertions(+), 403 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index b1f76ce1..eb7902c4 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,12 +1,59 @@ Change log ========== -All notable changes to this program is documented in this file. +All notable changes to this program are documented in this file. +0.28.0 (2020-11-03, `c00d2b6acd3f`) +-------------------- + +### Known problems + +- _macOS 10.15 (Catalina):_ + + Due to the requirement from Apple that all programs must be + notarized, geckodriver will not work on Catalina if you manually + download it through another notarized program, such as Firefox. + + Whilst we are working on a repackaging fix for this problem, you can + find more details on how to work around this issue in the [macOS + notarization] section of the documentation. + +### Added + +- The command line flag `--android-storage` has been added, to allow geckodriver +to also control Firefox on root-less Android devices. See the [documentation][Flags] +for available values. + +### Fixed -0.27.0 (2020-07-27, `90ec81285ff6`) +- Firefox can be started again via a shell script that is located outside of the + Firefox directory on Linux. + +- If Firefox cannot be started by geckodriver the real underlying error message is + now being reported. + +- Version numbers for minor and extended support releases of Firefox are now parsed correctly. + +### Removed + +- Since Firefox 72 extension commands for finding an element’s anonymous children + and querying its attributes are no longer needed, and have been removed. + +0.27.0 (2020-07-27, `7b8c4f32cdde`) -------------------- +### Security Fixes + +- CVE-2020-15660 + + - Added additional checks on the `Content-Type` header for `POST` + requests to disallow `application/x-www-form-urlencoded`, + `multipart/form-data` and `text/plain`. + + - Added checking of the `Origin` header for `POST` requests. + + - The version number of Firefox is now checked when establishing a session. + ### Known problems - _macOS 10.15 (Catalina):_ @@ -15,9 +62,9 @@ All notable changes to this program is documented in this file. notarized, geckodriver will not work on Catalina if you manually download it through another notarized program, such as Firefox. - Whilst we are working on a repackaging fix for this problem, you - can find more details on how to work around this issue in the - [macOS notarization] section of the documentation. + Whilst we are working on a repackaging fix for this problem, you can + find more details on how to work around this issue in the [macOS + notarization] section of the documentation. ### Added @@ -42,8 +89,7 @@ All notable changes to this program is documented in this file. - _Android:_ - * Firefox running on Android devices can now be controlled from a - Windows host. + * Firefox running on Android devices can now be controlled from a Windows host. * Setups with multiple connected Android devices are now supported. @@ -1327,6 +1373,7 @@ and greater. [Firefox Preview]: https://play.google.com/store/apps/details?id=org.mozilla.fenix [Firefox Reality]: https://play.google.com/store/apps/details?id=org.mozilla.vrbrowser [Capabilities]: https://firefox-source-docs.mozilla.org/testing/geckodriver/Capabilities.html +[Flags]: https://firefox-source-docs.mozilla.org/testing/geckodriver/Flags.html [enable remote debugging on the Android device]: https://developers.google.com/web/tools/chrome-devtools/remote-debugging [macOS notarization]: https://firefox-source-docs.mozilla.org/testing/geckodriver/Notarization.html diff --git a/Cargo.toml b/Cargo.toml index 7a1ebdb5..a5d4a274 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "geckodriver" -version = "0.27.0" +version = "0.28.0" description = "Proxy for using WebDriver clients to interact with Gecko-based browsers." keywords = ["webdriver", "w3c", "httpd", "mozilla", "firefox"] repository = "https://hg.mozilla.org/mozilla-central/file/tip/testing/geckodriver" @@ -17,17 +17,17 @@ hyper = "0.13" lazy_static = "1.0" log = { version = "0.4", features = ["std"] } marionette = { path = "./marionette" } -mozdevice = "0.2.0" +mozdevice = "0.3.0" mozprofile = "0.7.0" -mozrunner = "0.11" -mozversion = "0.3" +mozrunner = "0.12.0" +mozversion = "0.4.0" regex = { version="1.0", default-features = false, features = ["perf", "std"] } serde = "1.0" serde_derive = "1.0" serde_json = "1.0" serde_yaml = "0.8" uuid = { version = "0.8", features = ["v4"] } -webdriver = "0.41" +webdriver = "0.42.0" zip = { version = "0.4", default-features = false, features = ["deflate"] } [[bin]] diff --git a/doc/Flags.md b/doc/Flags.md index 209acf19..8889e8b5 100644 --- a/doc/Flags.md +++ b/doc/Flags.md @@ -1,7 +1,53 @@ Flags ===== -#### -b BINARY/--binary BINARY +#### --android-storage ANDROID_STORAGE + +Selects the test data location on the Android device, eg. the Firefox profile. +By default `auto` is used. + + + + + + + + + + + + + +
Value + Description +
auto + Best suitable location based on whether the device is rooted.
+ If the device is rooted internal is used, otherwise app. +
app +

Location: /data/data/%androidPackage%/test_root

+ Based on the androidPackage capability that is passed as part of + moz:firefoxOptions when creating a new session. Commands that + change data in the app's directory are executed using run-as. This requires + that the installed app is debuggable. +
internal +

Location: /data/local/tmp/test_root

+ The device must be rooted since when the app runs, files that are created + in the profile, which is owned by the app user, cannot be changed by the + shell user. Commands will be executed via su. +
sdcard +

Location: /mnt/sdcard/test_root

+ This location is not supported on Android 11+ due to the + + changes related to scoped storage. +
+ + +#### -b BINARY / --binary BINARY Path to the Firefox binary to use. By default geckodriver tries to find and use the system installation of Firefox, but that behaviour @@ -28,7 +74,7 @@ scanning the Windows registry. [whereis(1)]: http://www.manpagez.com/man/1/whereis/ -#### `--connect-existing` +#### --connect-existing Connect geckodriver to an existing Firefox instance. This means geckodriver will abstain from the default of starting a new Firefox @@ -55,6 +101,12 @@ Set the Gecko and geckodriver log level. Possible values are `fatal`, `error`, `warn`, `info`, `config`, `debug`, and `trace`. +#### --marionette-host HOST + +Selects the host for geckodriver’s connection to the [Marionette] +remote protocol. Defaults to 127.0.0.1. + + #### --marionette-port PORT Selects the port for geckodriver’s connection to the [Marionette] @@ -70,7 +122,7 @@ under geckodriver’s control, it will simply connect to PORT. [`--connect-existing`]: #connect-existing -#### -p PORT/--port PORT +#### -p PORT / --port PORT Port to use for the WebDriver server. Defaults to 4444. diff --git a/doc/Releasing.md b/doc/Releasing.md index 9618045d..39185809 100644 --- a/doc/Releasing.md +++ b/doc/Releasing.md @@ -124,10 +124,10 @@ repository, the changeset id for the release has to be added to the change log. Therefore add a final place-holder commit to the patch series, to already get review for. -Once all previous revisions of the patch series have been reviewed and -landed, it's known which commit id the version bump commit has, finalize the -change log, and land that remaining revision. - +Once all previous revisions of the patch series have been landed, and got merged +to `mozilla-central`, the changeset id from the merge commit has to picked for +finalizing the change log. This specific id is needed because Taskcluster creates +the final signed builds based on that merge. Release new in-tree dependency crates ------------------------------------- diff --git a/doc/Support.md b/doc/Support.md index c3dcfe7f..a7478d4c 100644 --- a/doc/Support.md +++ b/doc/Support.md @@ -23,6 +23,16 @@ and required versions of Selenium and Firefox: + + 0.28.0 + ≥ 3.11 (3.14 Python) + 60 + n/a + + 0.27.0 + ≥ 3.11 (3.14 Python) + 60 + n/a 0.26.0 ≥ 3.11 (3.14 Python) diff --git a/doc/Usage.md b/doc/Usage.md index a5d915e9..d3ce68a4 100644 --- a/doc/Usage.md +++ b/doc/Usage.md @@ -68,9 +68,9 @@ Using [curl(1)]: % geckodriver & [1] 16010 % 1491834109194 geckodriver INFO Listening on 127.0.0.1:4444 - % curl -d '{"capabilities": {"alwaysMatch": {"acceptInsecureCerts": true}}}' http://localhost:4444/session - {"sessionId":"d4605710-5a4e-4d64-a52a-778bb0c31e00","value":{"XULappId":"{ec8030f7-c20a-464f-9b0e-13a3a9e97384}","acceptSslCerts":false,"appBuildId":"20160913030425","browserName":"firefox","browserVersion":"51.0a1","command_id":1,"platform":"LINUX","platformName":"linux","platformVersion":"4.9.0-1-amd64","processId":17474,"proxy":{},"raisesAccessibilityExceptions":false,"rotatable":false,"specificationLevel":0,"takesElementScreenshot":true,"takesScreenshot":true,"version":"51.0a1"}} - % curl -d '{"url": "https://mozilla.org"}' http://localhost:4444/session/d4605710-5a4e-4d64-a52a-778bb0c31e00/url + % curl -H 'Content-Type: application/json' -d '{"capabilities": {"alwaysMatch": {"acceptInsecureCerts": true}}}' http://localhost:4444/session + {"value":{"sessionId":"d4605710-5a4e-4d64-a52a-778bb0c31e00","capabilities":{"acceptInsecureCerts":true,[...]}}} + % curl -H 'Content-Type: application/json' -d '{"url": "https://mozilla.org"}' http://localhost:4444/session/d4605710-5a4e-4d64-a52a-778bb0c31e00/url {} % curl http://localhost:4444/session/d4605710-5a4e-4d64-a52a-778bb0c31e00/url {"value":"https://www.mozilla.org/en-US/" diff --git a/mach_commands.py b/mach_commands.py index efcf7591..beb4b93b 100644 --- a/mach_commands.py +++ b/mach_commands.py @@ -19,37 +19,56 @@ @CommandProvider class GeckoDriver(MachCommandBase): - - @Command("geckodriver", - category="post-build", - description="Run the WebDriver implementation for Gecko.") - @CommandArgument("--binary", type=str, - help="Firefox binary (defaults to the local build).") - @CommandArgument("params", nargs="...", - help="Flags to be passed through to geckodriver.") + @Command( + "geckodriver", + category="post-build", + description="Run the WebDriver implementation for Gecko.", + ) + @CommandArgument( + "--binary", type=str, help="Firefox binary (defaults to the local build)." + ) + @CommandArgument( + "params", nargs="...", help="Flags to be passed through to geckodriver." + ) @CommandArgumentGroup("debugging") - @CommandArgument("--debug", action="store_true", group="debugging", - help="Enable the debugger. Not specifying a --debugger " - "option will result in the default debugger " - "being used.") - @CommandArgument("--debugger", default=None, type=str, group="debugging", - help="Name of debugger to use.") - @CommandArgument("--debugger-args", default=None, metavar="params", - type=str, group="debugging", - help="Flags to pass to the debugger itself; " - "split as the Bourne shell would.") + @CommandArgument( + "--debug", + action="store_true", + group="debugging", + help="Enable the debugger. Not specifying a --debugger " + "option will result in the default debugger " + "being used.", + ) + @CommandArgument( + "--debugger", + default=None, + type=str, + group="debugging", + help="Name of debugger to use.", + ) + @CommandArgument( + "--debugger-args", + default=None, + metavar="params", + type=str, + group="debugging", + help="Flags to pass to the debugger itself; " + "split as the Bourne shell would.", + ) def run(self, binary, params, debug, debugger, debugger_args): try: binpath = self.get_binary_path("geckodriver") except BinaryNotFoundException as e: - self.log(logging.ERROR, 'geckodriver', - {'error': str(e)}, - 'ERROR: {error}') - self.log(logging.INFO, 'geckodriver', {}, - "It looks like geckodriver isn't built. " - "Add ac_add_options --enable-geckodriver to your " - "mozconfig " - "and run |./mach build| to build it.") + self.log(logging.ERROR, "geckodriver", {"error": str(e)}, "ERROR: {error}") + self.log( + logging.INFO, + "geckodriver", + {}, + "It looks like geckodriver isn't built. " + "Add ac_add_options --enable-geckodriver to your " + "mozconfig " + "and run |./mach build| to build it.", + ) return 1 args = [binpath] @@ -61,12 +80,10 @@ def run(self, binary, params, debug, debugger, debugger_args): try: binary = self.get_binary_path("app") except BinaryNotFoundException as e: - self.log(logging.ERROR, 'geckodriver', - {'error': str(e)}, - 'ERROR: {error}') - self.log(logging.INFO, 'geckodriver', - {'help': e.help()}, - '{help}') + self.log( + logging.ERROR, "geckodriver", {"error": str(e)}, "ERROR: {error}" + ) + self.log(logging.INFO, "geckodriver", {"help": e.help()}, "{help}") return 1 args.extend(["--binary", binary]) @@ -76,10 +93,13 @@ def run(self, binary, params, debug, debugger, debugger_args): self.log_manager.terminal_handler.setLevel(logging.WARNING) import mozdebug + if not debugger: # No debugger name was provided. Look for the default ones on # current OS. - debugger = mozdebug.get_default_debugger_name(mozdebug.DebuggerSearch.KeepLooking) + debugger = mozdebug.get_default_debugger_name( + mozdebug.DebuggerSearch.KeepLooking + ) if debugger: self.debuggerInfo = mozdebug.get_debugger_info(debugger, debugger_args) @@ -91,29 +111,35 @@ def run(self, binary, params, debug, debugger, debugger_args): # their use. if debugger_args: from mozbuild import shellutil + try: debugger_args = shellutil.split(debugger_args) except shellutil.MetaCharacterException as e: - print("The --debugger-args you passed require a real shell to parse them.") + print( + "The --debugger-args you passed require a real shell to parse them." + ) print("(We can't handle the %r character.)" % e.char) return 1 # Prepend the debugger args. args = [self.debuggerInfo.path] + self.debuggerInfo.args + args - return self.run_process(args=args, ensure_exit_code=False, - pass_thru=True) + return self.run_process(args=args, ensure_exit_code=False, pass_thru=True) @CommandProvider class GeckoDriverTest(MachCommandBase): - - @Command("geckodriver-test", - category="post-build", - description="Run geckodriver unit tests.") - @CommandArgument("-v", "--verbose", action="store_true", - help="Verbose output for what" - " commands the build is running.") + @Command( + "geckodriver-test", + category="post-build", + description="Run geckodriver unit tests.", + ) + @CommandArgument( + "-v", + "--verbose", + action="store_true", + help="Verbose output for what" " commands the build is running.", + ) def test(self, verbose=False, **kwargs): from mozbuild.controller.building import BuildDriver @@ -123,4 +149,5 @@ def test(self, verbose=False, **kwargs): return driver.build( what=["testing/geckodriver/check"], verbose=verbose, - mach_context=self._mach_context) + mach_context=self._mach_context, + ) diff --git a/moz.build b/moz.build index 9f191598..9cda32b4 100644 --- a/moz.build +++ b/moz.build @@ -10,10 +10,9 @@ RUST_TESTS = [ "geckodriver", "webdriver", "marionette", - # TODO: Move to mozbase/rust/moz.build once those crates can be # tested separately. - # "mozdevice", // Tests require adb, and cannot be run in CI + "mozdevice", "mozprofile", "mozrunner", "mozversion", diff --git a/src/android.rs b/src/android.rs index 87cce8bd..6aaf58ec 100644 --- a/src/android.rs +++ b/src/android.rs @@ -1,5 +1,5 @@ use crate::capabilities::AndroidOptions; -use mozdevice::{Device, Host}; +use mozdevice::{AndroidStorage, Device, Host}; use mozprofile::profile::Profile; use serde::Serialize; use serde_yaml::{Mapping, Value}; @@ -7,6 +7,7 @@ use std::fmt; use std::io; use std::path::PathBuf; use std::time; +use webdriver::error::{ErrorStatus, WebDriverError}; // TODO: avoid port clashes across GeckoView-vehicles. // For now, we always use target port 2829, leading to issues like bug 1533704. @@ -25,7 +26,6 @@ pub enum AndroidError { ActivityNotFound(String), Device(mozdevice::DeviceError), IO(io::Error), - NotConnected, PackageNotFound(String), Serde(serde_yaml::Error), } @@ -38,7 +38,6 @@ impl fmt::Display for AndroidError { } AndroidError::Device(ref message) => message.fmt(f), AndroidError::IO(ref message) => message.fmt(f), - AndroidError::NotConnected => write!(f, "Not connected to any Android device"), AndroidError::PackageNotFound(ref package) => { write!(f, "Package '{}' not found", package) } @@ -65,6 +64,12 @@ impl From for AndroidError { } } +impl From for WebDriverError { + fn from(value: AndroidError) -> WebDriverError { + WebDriverError::new(ErrorStatus::UnknownError, value.to_string()) + } +} + /// A remote Gecko instance. /// /// Host refers to the device running `geckodriver`. Target refers to the @@ -90,12 +95,13 @@ impl AndroidProcess { } } -#[derive(Debug, Default)] +#[derive(Debug)] pub struct AndroidHandler { pub config: PathBuf, pub options: AndroidOptions, - pub process: Option, + pub process: AndroidProcess, pub profile: PathBuf, + pub test_root: PathBuf, // For port forwarding host => target pub host_port: u16, @@ -105,59 +111,41 @@ pub struct AndroidHandler { impl Drop for AndroidHandler { fn drop(&mut self) { // Try to clean up various settings - if let Some(ref process) = self.process { - let clear_command = format!("am clear-debug-app {}", process.package); - match process.device.execute_host_shell_command(&clear_command) { - Ok(_) => debug!("Disabled reading from configuration file"), - Err(e) => error!("Failed disabling from configuration file: {}", e), - } + let clear_command = format!("am clear-debug-app {}", self.process.package); + match self + .process + .device + .execute_host_shell_command(&clear_command) + { + Ok(_) => debug!("Disabled reading from configuration file"), + Err(e) => error!("Failed disabling from configuration file: {}", e), + } - let remove_command = format!("rm -rf {}", self.config.display()); - match process.device.execute_host_shell_command(&remove_command) { - Ok(_) => debug!("Deleted GeckoView configuration file"), - Err(e) => error!("Failed deleting GeckoView configuration file: {}", e), - } + match self.process.device.remove(&self.config) { + Ok(_) => debug!("Deleted GeckoView configuration file"), + Err(e) => error!("Failed deleting GeckoView configuration file: {}", e), + } - match process.device.kill_forward_port(self.host_port) { - Ok(_) => debug!( - "Android port forward ({} -> {}) stopped", - &self.host_port, &self.target_port - ), - Err(e) => error!( - "Android port forward ({} -> {}) failed to stop: {}", - &self.host_port, &self.target_port, e - ), - } + match self.process.device.kill_forward_port(self.host_port) { + Ok(_) => debug!( + "Android port forward ({} -> {}) stopped", + &self.host_port, &self.target_port + ), + Err(e) => error!( + "Android port forward ({} -> {}) failed to stop: {}", + &self.host_port, &self.target_port, e + ), } } } impl AndroidHandler { - pub fn new(options: &AndroidOptions) -> AndroidHandler { + pub fn new(options: &AndroidOptions, host_port: u16) -> Result { // We need to push profile.pathbuf to a safe space on the device. // Make it per-Android package to avoid clashes and confusion. // This naming scheme follows GeckoView's configuration file naming scheme, // see bug 1533385. - let profile = PathBuf::from(format!( - "/mnt/sdcard/{}-geckodriver-profile", - &options.package - )); - let config = PathBuf::from(format!( - "/data/local/tmp/{}-geckoview-config.yaml", - &options.package - )); - - AndroidHandler { - options: options.clone(), - profile, - config, - process: None, - ..Default::default() - } - } - - pub fn connect(&mut self, host_port: u16) -> Result<()> { let host = Host { host: None, port: None, @@ -165,58 +153,88 @@ impl AndroidHandler { write_timeout: Some(time::Duration::from_millis(5000)), }; - let device = host.device_or_default(self.options.device_serial.as_ref())?; - - self.host_port = host_port; - self.target_port = TARGET_PORT; + let mut device = host.device_or_default(options.device_serial.as_ref(), options.storage)?; // Set up port forward. Port forwarding will be torn down, if possible, - device.forward_port(self.host_port, self.target_port)?; + device.forward_port(host_port, TARGET_PORT)?; debug!( "Android port forward ({} -> {}) started", - &self.host_port, &self.target_port + host_port, TARGET_PORT + ); + + let test_root = match device.storage { + AndroidStorage::App => { + device.run_as_package = Some(options.package.to_owned()); + let mut buf = PathBuf::from("/data/data"); + buf.push(&options.package); + buf.push("test_root"); + buf + } + AndroidStorage::Internal => PathBuf::from("/data/local/tmp/test_root"), + AndroidStorage::Sdcard => PathBuf::from("/mnt/sdcard/test_root"), + }; + + debug!( + "Connecting: options={:?}, storage={:?}) test_root={}, run_as_package={:?}", + options, + device.storage, + test_root.display(), + device.run_as_package ); + let mut profile = test_root.clone(); + profile.push(format!("{}-geckodriver-profile", &options.package)); + // Check if the specified package is installed - let response = device - .execute_host_shell_command(&format!("pm list packages {}", &self.options.package))?; + let response = + device.execute_host_shell_command(&format!("pm list packages {}", &options.package))?; let packages = response + .trim() .split_terminator('\n') .filter(|line| line.starts_with("package:")) .map(|line| line.rsplit(':').next().expect("Package name found")) .collect::>(); - if !packages.contains(&self.options.package.as_str()) { - return Err(AndroidError::PackageNotFound(self.options.package.clone())); + if !packages.contains(&options.package.as_str()) { + return Err(AndroidError::PackageNotFound(options.package.clone())); } + let config = PathBuf::from(format!( + "/data/local/tmp/{}-geckoview-config.yaml", + &options.package + )); + // If activity hasn't been specified default to the main activity of the package - let activity = match self.options.activity { + let activity = match options.activity { Some(ref activity) => activity.clone(), None => { let response = device.execute_host_shell_command(&format!( "cmd package resolve-activity --brief {}", - &self.options.package + &options.package ))?; let activities = response .split_terminator('\n') - .filter(|line| line.starts_with(&self.options.package)) + .filter(|line| line.starts_with(&options.package)) .map(|line| line.rsplit('/').next().unwrap()) .collect::>(); if activities.is_empty() { - return Err(AndroidError::ActivityNotFound(self.options.package.clone())); + return Err(AndroidError::ActivityNotFound(options.package.clone())); } activities[0].to_owned() } }; - self.process = Some(AndroidProcess::new( - device, - self.options.package.clone(), - activity, - )?); + let process = AndroidProcess::new(device, options.package.clone(), activity)?; - Ok(()) + Ok(AndroidHandler { + options: options.clone(), + config, + process, + profile, + test_root, + host_port, + target_port: TARGET_PORT, + }) } pub fn generate_config_file(&self, envs: I) -> Result @@ -275,102 +293,178 @@ impl AndroidHandler { K: ToString, V: ToString, { - match self.process { - Some(ref process) => { - process.device.clear_app_data(&process.package)?; - - // These permissions, at least, are required to read profiles in /mnt/sdcard. - for perm in &["READ_EXTERNAL_STORAGE", "WRITE_EXTERNAL_STORAGE"] { - process.device.execute_host_shell_command(&format!( - "pm grant {} android.permission.{}", - &process.package, perm - ))?; - } - - debug!("Deleting {}", self.profile.display()); - process - .device - .execute_host_shell_command(&format!("rm -rf {}", self.profile.display()))?; + self.process.device.clear_app_data(&self.process.package)?; + + // These permissions, at least, are required to read profiles in /mnt/sdcard. + for perm in &["READ_EXTERNAL_STORAGE", "WRITE_EXTERNAL_STORAGE"] { + self.process.device.execute_host_shell_command(&format!( + "pm grant {} android.permission.{}", + &self.process.package, perm + ))?; + } - debug!( - "Pushing {} to {}", - profile.path.display(), - self.profile.display() - ); - process - .device - .push_dir(&profile.path, &self.profile, 0o777)?; + // Make sure to create the test root. + self.process.device.create_dir(&self.test_root)?; + self.process.device.chmod(&self.test_root, "777", true)?; - let contents = self.generate_config_file(env)?; - debug!("Content of generated GeckoView config file:\n{}", contents); - let reader = &mut io::BufReader::new(contents.as_bytes()); + // Replace the profile + self.process.device.remove(&self.profile)?; + self.process + .device + .push_dir(&profile.path, &self.profile, 0o777)?; - debug!( - "Pushing GeckoView configuration file to {}", - self.config.display() - ); - process.device.push(reader, &self.config, 0o777)?; + let contents = self.generate_config_file(env)?; + debug!("Content of generated GeckoView config file:\n{}", contents); + let reader = &mut io::BufReader::new(contents.as_bytes()); - // Bug 1584966: File permissions are not correctly set by push() - process - .device - .execute_host_shell_command(&format!("chmod a+rw {}", self.config.display()))?; + debug!( + "Pushing GeckoView configuration file to {}", + self.config.display() + ); + self.process.device.push(reader, &self.config, 0o777)?; - // Tell GeckoView to read configuration even when `android:debuggable="false"`. - process.device.execute_host_shell_command(&format!( - "am set-debug-app --persistent {}", - process.package - ))?; - } - None => return Err(AndroidError::NotConnected), - } + // Tell GeckoView to read configuration even when `android:debuggable="false"`. + self.process.device.execute_host_shell_command(&format!( + "am set-debug-app --persistent {}", + self.process.package + ))?; Ok(()) } pub fn launch(&self) -> Result<()> { - match self.process { - Some(ref process) => { - // TODO: Remove the usage of intent arguments once Fennec is no longer - // supported. Packages which are using GeckoView always read the arguments - // via the YAML configuration file. - let mut intent_arguments = self - .options - .intent_arguments - .clone() - .unwrap_or_else(|| Vec::with_capacity(3)); - intent_arguments.push("--es".to_owned()); - intent_arguments.push("args".to_owned()); - intent_arguments - .push(format!("--marionette --profile {}", self.profile.display()).to_owned()); - - debug!("Launching {}/{}", process.package, process.activity); - process - .device - .launch(&process.package, &process.activity, &intent_arguments) - .map_err(|e| { - let message = format!( - "Could not launch Android {}/{}: {}", - process.package, process.activity, e - ); - mozdevice::DeviceError::Adb(message) - })?; - } - None => return Err(AndroidError::NotConnected), - } + // TODO: Remove the usage of intent arguments once Fennec is no longer + // supported. Packages which are using GeckoView always read the arguments + // via the YAML configuration file. + let mut intent_arguments = self + .options + .intent_arguments + .clone() + .unwrap_or_else(|| Vec::with_capacity(3)); + intent_arguments.push("--es".to_owned()); + intent_arguments.push("args".to_owned()); + intent_arguments.push(format!("--marionette --profile {}", self.profile.display())); + + debug!( + "Launching {}/{}", + self.process.package, self.process.activity + ); + self.process + .device + .launch( + &self.process.package, + &self.process.activity, + &intent_arguments, + ) + .map_err(|e| { + let message = format!( + "Could not launch Android {}/{}: {}", + self.process.package, self.process.activity, e + ); + mozdevice::DeviceError::Adb(message) + })?; Ok(()) } pub fn force_stop(&self) -> Result<()> { - match &self.process { - Some(process) => { - debug!("Force stopping the Android package: {}", &process.package); - process.device.force_stop(&process.package)?; - } - None => return Err(AndroidError::NotConnected), - } + debug!( + "Force stopping the Android package: {}", + &self.process.package + ); + self.process.device.force_stop(&self.process.package)?; Ok(()) } } + +#[cfg(test)] +mod test { + // To successfully run those tests the geckoview_example package needs to + // be installed on the device or emulator. After setting up the build + // environment (https://mzl.la/3muLv5M), the following mach commands have to + // be executed: + // + // $ ./mach build && ./mach install + // + // Currently the mozdevice API is not safe for multiple requests at the same + // time. It is recommended to run each of the unit tests on its own. Also adb + // specific tests cannot be run in CI yet. To check those locally, also run + // the ignored tests. + // + // Use the following command to accomplish that: + // + // $ cargo test -- --ignored --test-threads=1 + + use crate::android::AndroidHandler; + use crate::capabilities::AndroidOptions; + use mozdevice::{AndroidStorage, AndroidStorageInput}; + use std::path::PathBuf; + + fn run_handler_storage_test(package: &str, storage: AndroidStorageInput) { + let options = AndroidOptions::new(package.to_owned(), storage); + let handler = AndroidHandler::new(&options, 4242).expect("has valid Android handler"); + + assert_eq!(handler.options, options); + assert_eq!(handler.process.package, package); + + let expected_config_path = PathBuf::from(format!( + "/data/local/tmp/{}-geckoview-config.yaml", + &package + )); + assert_eq!(handler.config, expected_config_path); + + if handler.process.device.storage == AndroidStorage::App { + assert_eq!( + handler.process.device.run_as_package, + Some(package.to_owned()) + ); + } else { + assert_eq!(handler.process.device.run_as_package, None); + } + + let test_root = match handler.process.device.storage { + AndroidStorage::App => { + let mut buf = PathBuf::from("/data/data"); + buf.push(&package); + buf.push("test_root"); + buf + } + AndroidStorage::Internal => PathBuf::from("/data/local/tmp/test_root"), + AndroidStorage::Sdcard => PathBuf::from("/mnt/sdcard/test_root"), + }; + assert_eq!(handler.test_root, test_root); + + let mut profile = test_root.clone(); + profile.push(format!("{}-geckodriver-profile", &package)); + assert_eq!(handler.profile, profile); + } + + #[test] + #[ignore] + fn android_handler_storage_as_app() { + let package = "org.mozilla.geckoview_example"; + run_handler_storage_test(&package, AndroidStorageInput::App); + } + + #[test] + #[ignore] + fn android_handler_storage_as_auto() { + let package = "org.mozilla.geckoview_example"; + run_handler_storage_test(package, AndroidStorageInput::Auto); + } + + #[test] + #[ignore] + fn android_handler_storage_as_internal() { + let package = "org.mozilla.geckoview_example"; + run_handler_storage_test(package, AndroidStorageInput::Internal); + } + + #[test] + #[ignore] + fn android_handler_storage_as_sdcard() { + let package = "org.mozilla.geckoview_example"; + run_handler_storage_test(package, AndroidStorageInput::Sdcard); + } +} diff --git a/src/capabilities.rs b/src/capabilities.rs index ef1b0d74..ab4ed687 100644 --- a/src/capabilities.rs +++ b/src/capabilities.rs @@ -1,26 +1,54 @@ use crate::command::LogOptions; use crate::logging::Level; use base64; +use mozdevice::AndroidStorageInput; use mozprofile::preferences::Pref; use mozprofile::profile::Profile; use mozrunner::runner::platform::firefox_default_path; -use mozversion::{self, firefox_version, Version}; +use mozversion::{self, 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::fmt::{self, Display}; use std::fs; use std::io; use std::io::BufWriter; use std::io::Cursor; use std::path::{Path, PathBuf}; -use std::process::{Command, Stdio}; use std::str::{self, FromStr}; use webdriver::capabilities::{BrowserCapabilities, Capabilities}; use webdriver::error::{ErrorStatus, WebDriverError, WebDriverResult}; use zip; -/// Provides matching of `moz:firefoxOptions` and resolution of which Firefox +#[derive(Clone, Debug)] +enum VersionError { + VersionError(mozversion::Error), + MissingBinary, +} + +impl From for VersionError { + fn from(err: mozversion::Error) -> VersionError { + VersionError::VersionError(err) + } +} + +impl Display for VersionError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + VersionError::VersionError(ref x) => x.fmt(f), + VersionError::MissingBinary => "No binary provided".fmt(f), + } + } +} + +impl From for WebDriverError { + fn from(err: VersionError) -> WebDriverError { + WebDriverError::new(ErrorStatus::SessionNotCreated, err.to_string()) + } +} + +/// Provides matching of `moz:firefoxOptions` and resolutionnized of which Firefox /// binary to use. /// /// `FirefoxCapabilities` is constructed with the fallback binary, should @@ -30,7 +58,7 @@ use zip; pub struct FirefoxCapabilities<'a> { pub chosen_binary: Option, fallback_binary: Option<&'a PathBuf>, - version_cache: BTreeMap, + version_cache: BTreeMap>, } impl<'a> FirefoxCapabilities<'a> { @@ -52,57 +80,42 @@ impl<'a> FirefoxCapabilities<'a> { .or_else(firefox_default_path); } - fn version(&mut self, binary: Option<&Path>) -> Option { + fn version(&mut self, binary: Option<&Path>) -> Result { if let Some(binary) = binary { - if let Some(value) = self.version_cache.get(binary) { - return Some((*value).clone()); + if let Some(cache_value) = self.version_cache.get(binary) { + return cache_value.clone(); } - debug!("Trying to read firefox version from ini files"); - let rv = firefox_version(&*binary) - .ok() - .and_then(|x| x.version_string) - .or_else(|| { - debug!("Trying to read firefox version from binary"); - self.version_from_binary(binary) - }); - if let Some(ref version) = rv { + let rv = self + .version_from_ini(binary) + .or_else(|_| self.version_from_binary(binary)); + if let Ok(ref version) = rv { debug!("Found version {}", version); - self.version_cache - .insert(binary.to_path_buf(), version.clone()); } else { debug!("Failed to get binary version"); } + self.version_cache.insert(binary.to_path_buf(), rv.clone()); rv } else { - None + Err(VersionError::MissingBinary) } } - fn version_from_binary(&self, binary: &Path) -> Option { - let version_regexp = Regex::new(r#"Mozilla Firefox [0-9]+\.[0-9]+(?:[a-z][0-9]+)?"#) - .expect("Error parsing version regexp"); - let output = Command::new(binary) - .args(&["--version"]) - .stdout(Stdio::piped()) - .spawn() - .and_then(|child| child.wait_with_output()) - .ok(); - - if let Some(x) = output { - version_regexp - .captures(&*x.stdout) - .and_then(|captures| captures.get(0)) - .and_then(|m| str::from_utf8(m.as_bytes()).ok()) - .map(|x| x.into()) + fn version_from_ini(&self, binary: &Path) -> Result { + debug!("Trying to read firefox version from ini files"); + let version = firefox_version(binary)?; + if let Some(version_string) = version.version_string { + Version::from_str(&version_string).map_err(|err| err.into()) } else { - None + Err(VersionError::VersionError( + mozversion::Error::MetadataError("Missing version string".into()), + )) } } -} -// TODO: put this in webdriver-rust -fn convert_version_error(err: mozversion::Error) -> WebDriverError { - WebDriverError::new(ErrorStatus::SessionNotCreated, err.to_string()) + fn version_from_binary(&self, binary: &Path) -> Result { + debug!("Trying to read firefox version from binary"); + Ok(firefox_binary_version(binary)?) + } } impl<'a> BrowserCapabilities for FirefoxCapabilities<'a> { @@ -116,7 +129,9 @@ impl<'a> BrowserCapabilities for FirefoxCapabilities<'a> { fn browser_version(&mut self, _: &Capabilities) -> WebDriverResult> { let binary = self.chosen_binary.clone(); - Ok(self.version(binary.as_ref().map(|x| x.as_ref()))) + self.version(binary.as_ref().map(|x| x.as_ref())) + .map_err(|err| err.into()) + .map(|x| Some(x.to_string())) } fn platform_name(&mut self, _: &Capabilities) -> WebDriverResult> { @@ -132,16 +147,7 @@ impl<'a> BrowserCapabilities for FirefoxCapabilities<'a> { } fn accept_insecure_certs(&mut self, _: &Capabilities) -> WebDriverResult { - let binary = self.chosen_binary.clone(); - let version_str = self.version(binary.as_ref().map(|x| x.as_ref())); - if let Some(x) = version_str { - Ok(Version::from_str(&*x) - .or_else(|x| Err(convert_version_error(x)))? - .major - >= 52) - } else { - Ok(false) - } + Ok(true) } fn set_window_rect(&mut self, _: &Capabilities) -> WebDriverResult { @@ -154,9 +160,9 @@ impl<'a> BrowserCapabilities for FirefoxCapabilities<'a> { comparison: &str, ) -> WebDriverResult { Version::from_str(version) - .or_else(|x| Err(convert_version_error(x)))? + .map_err(|err| VersionError::from(err))? .matches(comparison) - .or_else(|x| Err(convert_version_error(x))) + .map_err(|err| VersionError::from(err).into()) } fn strict_file_interactability(&mut self, _: &Capabilities) -> WebDriverResult { @@ -209,7 +215,7 @@ impl<'a> BrowserCapabilities for FirefoxCapabilities<'a> { "binary" => { if let Some(binary) = value.as_str() { if !data.contains_key("androidPackage") - && self.version(Some(Path::new(binary))).is_none() + && self.version(Some(Path::new(binary))).is_err() { return Err(WebDriverError::new( ErrorStatus::InvalidArgument, @@ -331,12 +337,14 @@ pub struct AndroidOptions { pub device_serial: Option, pub intent_arguments: Option>, pub package: String, + pub storage: AndroidStorageInput, } impl AndroidOptions { - fn new(package: String) -> AndroidOptions { + pub fn new(package: String, storage: AndroidStorageInput) -> AndroidOptions { AndroidOptions { package, + storage, ..Default::default() } } @@ -366,6 +374,7 @@ impl FirefoxOptions { pub fn from_capabilities( binary_path: Option, + android_storage: AndroidStorageInput, matched: &mut Capabilities, ) -> WebDriverResult { let mut rv = FirefoxOptions::new(); @@ -380,7 +389,7 @@ impl FirefoxOptions { ) })?; - rv.android = FirefoxOptions::load_android(&options)?; + rv.android = FirefoxOptions::load_android(android_storage, &options)?; rv.args = FirefoxOptions::load_args(&options)?; rv.env = FirefoxOptions::load_env(&options)?; rv.log = FirefoxOptions::load_log(&options)?; @@ -508,7 +517,10 @@ impl FirefoxOptions { } } - pub fn load_android(options: &Capabilities) -> WebDriverResult> { + pub fn load_android( + storage: AndroidStorageInput, + options: &Capabilities, + ) -> WebDriverResult> { if let Some(package_json) = options.get("androidPackage") { let package = package_json .as_str() @@ -530,7 +542,7 @@ impl FirefoxOptions { )); } - let mut android = AndroidOptions::new(package); + let mut android = AndroidOptions::new(package, storage); android.activity = match options.get("androidActivity") { Some(json) => { @@ -669,10 +681,10 @@ fn unzip_buffer(buf: &[u8], dest_dir: &Path) -> WebDriverResult<()> { mod tests { extern crate mozprofile; + use self::mozprofile::preferences::Pref; use super::*; use crate::marionette::MarionetteHandler; - - use self::mozprofile::preferences::Pref; + use mozdevice::AndroidStorageInput; use serde_json::json; use std::default::Default; use std::fs::File; @@ -691,7 +703,7 @@ mod tests { let mut caps = Capabilities::new(); caps.insert("moz:firefoxOptions".into(), Value::Object(firefox_opts)); - FirefoxOptions::from_capabilities(None, &mut caps) + FirefoxOptions::from_capabilities(None, AndroidStorageInput::Auto, &mut caps) } #[test] @@ -710,7 +722,8 @@ mod tests { fn fx_options_from_capabilities_no_binary_and_caps() { let mut caps = Capabilities::new(); - let opts = FirefoxOptions::from_capabilities(None, &mut caps).unwrap(); + let opts = + FirefoxOptions::from_capabilities(None, AndroidStorageInput::Auto, &mut caps).unwrap(); assert_eq!(opts.android, None); assert_eq!(opts.args, None); assert_eq!(opts.binary, None); @@ -728,7 +741,12 @@ mod tests { let binary = PathBuf::from("foo"); - let opts = FirefoxOptions::from_capabilities(Some(binary.clone()), &mut caps).unwrap(); + let opts = FirefoxOptions::from_capabilities( + Some(binary.clone()), + AndroidStorageInput::Auto, + &mut caps, + ) + .unwrap(); assert_eq!(opts.android, None); assert_eq!(opts.args, None); assert_eq!(opts.binary, Some(binary)); @@ -741,7 +759,7 @@ mod tests { let mut caps = Capabilities::new(); caps.insert("moz:firefoxOptions".into(), json!(42)); - FirefoxOptions::from_capabilities(None, &mut caps) + FirefoxOptions::from_capabilities(None, AndroidStorageInput::Auto, &mut caps) .expect_err("Firefox options need to be of type object"); } @@ -761,7 +779,13 @@ mod tests { firefox_opts.insert("androidPackage".into(), json!(value)); let opts = make_options(firefox_opts).expect("valid firefox options"); - assert_eq!(opts.android, Some(AndroidOptions::new(value.to_string()))); + assert_eq!( + opts.android, + Some(AndroidOptions::new( + value.to_string(), + AndroidStorageInput::Auto + )) + ); } } diff --git a/src/command.rs b/src/command.rs index 560598b7..5e3a6f3d 100644 --- a/src/command.rs +++ b/src/command.rs @@ -8,8 +8,7 @@ use std::fs::File; use std::io::prelude::*; use uuid::Uuid; use webdriver::command::{WebDriverCommand, WebDriverExtensionCommand}; -use webdriver::common::WebElement; -use webdriver::error::{ErrorStatus, WebDriverError, WebDriverResult}; +use webdriver::error::WebDriverResult; use webdriver::httpapi::WebDriverExtensionRoute; use webdriver::Parameters; @@ -27,16 +26,6 @@ pub fn extension_routes() -> Vec<(Method, &'static str, GeckoExtensionRoute)> { "/session/{sessionId}/moz/context", GeckoExtensionRoute::SetContext, ), - ( - Method::POST, - "/session/{sessionId}/moz/xbl/{elementId}/anonymous_children", - GeckoExtensionRoute::XblAnonymousChildren, - ), - ( - Method::POST, - "/session/{sessionId}/moz/xbl/{elementId}/anonymous_by_attribute", - GeckoExtensionRoute::XblAnonymousByAttribute, - ), ( Method::POST, "/session/{sessionId}/moz/addon/install", @@ -59,8 +48,6 @@ pub fn extension_routes() -> Vec<(Method, &'static str, GeckoExtensionRoute)> { pub enum GeckoExtensionRoute { GetContext, SetContext, - XblAnonymousChildren, - XblAnonymousByAttribute, InstallAddon, UninstallAddon, TakeFullScreenshot, @@ -71,7 +58,7 @@ impl WebDriverExtensionRoute for GeckoExtensionRoute { fn command( &self, - params: &Parameters, + _params: &Parameters, body_data: &Value, ) -> WebDriverResult> { use self::GeckoExtensionRoute::*; @@ -81,26 +68,6 @@ impl WebDriverExtensionRoute for GeckoExtensionRoute { SetContext => { GeckoExtensionCommand::SetContext(serde_json::from_value(body_data.clone())?) } - XblAnonymousChildren => { - let element_id = try_opt!( - params.get("elementId"), - ErrorStatus::InvalidArgument, - "Missing elementId parameter" - ); - let element = WebElement(element_id.as_str().to_string()); - GeckoExtensionCommand::XblAnonymousChildren(element) - } - XblAnonymousByAttribute => { - let element_id = try_opt!( - params.get("elementId"), - ErrorStatus::InvalidArgument, - "Missing elementId parameter" - ); - GeckoExtensionCommand::XblAnonymousByAttribute( - WebElement(element_id.as_str().into()), - serde_json::from_value(body_data.clone())?, - ) - } InstallAddon => { GeckoExtensionCommand::InstallAddon(serde_json::from_value(body_data.clone())?) } @@ -114,12 +81,10 @@ impl WebDriverExtensionRoute for GeckoExtensionRoute { } } -#[derive(Clone, PartialEq)] +#[derive(Clone)] pub enum GeckoExtensionCommand { GetContext, SetContext(GeckoContextParameters), - XblAnonymousChildren(WebElement), - XblAnonymousByAttribute(WebElement, XblLocatorParameters), InstallAddon(AddonInstallParameters), UninstallAddon(AddonUninstallParameters), TakeFullScreenshot, @@ -133,8 +98,6 @@ impl WebDriverExtensionCommand for GeckoExtensionCommand { InstallAddon(x) => Some(serde_json::to_value(x).unwrap()), SetContext(x) => Some(serde_json::to_value(x).unwrap()), UninstallAddon(x) => Some(serde_json::to_value(x).unwrap()), - XblAnonymousByAttribute(_, x) => Some(serde_json::to_value(x).unwrap()), - XblAnonymousChildren(_) => None, TakeFullScreenshot => None, } } @@ -372,22 +335,4 @@ mod tests { assert!(serde_json::from_value::

(json!({ "context": null })).is_err()); assert!(serde_json::from_value::

(json!({"context": "foo"})).is_err()); } - - #[test] - fn test_json_xbl_anonymous_by_attribute() { - let locator = XblLocatorParameters { - name: "foo".to_string(), - value: "bar".to_string(), - }; - assert_de(&locator, json!({"name": "foo", "value": "bar"})); - } - - #[test] - fn test_json_xbl_anonymous_by_attribute_with_name_invalid() { - type P = XblLocatorParameters; - assert!(serde_json::from_value::

(json!({"value": "bar"})).is_err()); - assert!(serde_json::from_value::

(json!({"name": null, "value": "bar"})).is_err()); - assert!(serde_json::from_value::

(json!({"name": "foo"})).is_err()); - assert!(serde_json::from_value::

(json!({"name": "foo", "value": null})).is_err()); - } } diff --git a/src/main.rs b/src/main.rs index 9ce17efa..4dd6c668 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,5 @@ #![forbid(unsafe_code)] -extern crate base64; extern crate chrono; #[macro_use] extern crate clap; @@ -58,6 +57,7 @@ pub mod test; use crate::command::extension_routes; use crate::logging::Level; use crate::marionette::{MarionetteHandler, MarionetteSettings}; +use mozdevice::AndroidStorageInput; const EXIT_SUCCESS: i32 = 0; const EXIT_USAGE: i32 = 64; @@ -159,6 +159,8 @@ fn parse_args(app: &mut App) -> ProgramResult { Err(e) => usage!("{}: {}:{}", e, host, port), }; + let android_storage = value_t!(matches, "android_storage", AndroidStorageInput)?; + let binary = matches.value_of("binary").map(PathBuf::from); let marionette_host = matches.value_of("marionette_host").unwrap(); @@ -181,6 +183,7 @@ fn parse_args(app: &mut App) -> ProgramResult { binary, connect_existing: matches.is_present("connect_existing"), jsdebugger: matches.is_present("jsdebugger"), + android_storage, }; Operation::Server { log_level, @@ -318,6 +321,14 @@ fn make_app<'a, 'b>() -> App<'a, 'b> { .long("version") .help("Prints version and copying information"), ) + .arg( + Arg::with_name("android_storage") + .long("android-storage") + .possible_values(&["auto", "app", "internal", "sdcard"]) + .default_value("auto") + .value_name("ANDROID_STORAGE") + .help("Selects storage location to be used for test data."), + ) } fn get_program_name() -> String { diff --git a/src/marionette.rs b/src/marionette.rs index 666d56d7..7074cf35 100644 --- a/src/marionette.rs +++ b/src/marionette.rs @@ -1,7 +1,7 @@ use crate::android::AndroidHandler; use crate::command::{ AddonInstallParameters, AddonUninstallParameters, GeckoContextParameters, - GeckoExtensionCommand, GeckoExtensionRoute, XblLocatorParameters, CHROME_ELEMENT_KEY, + GeckoExtensionCommand, GeckoExtensionRoute, CHROME_ELEMENT_KEY, }; use marionette_rs::common::{ Cookie as MarionetteCookie, Date as MarionetteDate, Frame as MarionetteFrame, @@ -17,6 +17,7 @@ use marionette_rs::webdriver::{ ScreenshotOptions, Script as MarionetteScript, Selector as MarionetteSelector, Url as MarionetteUrl, WindowRect as MarionetteWindowRect, }; +use mozdevice::AndroidStorageInput; use mozprofile::preferences::Pref; use mozprofile::profile::Profile; use mozrunner::runner::{FirefoxProcess, FirefoxRunner, Runner, RunnerProcess}; @@ -95,6 +96,8 @@ pub struct MarionetteSettings { /// Brings up the Browser Toolbox when starting Firefox, /// letting you debug internals. pub jsdebugger: bool, + + pub android_storage: AndroidStorageInput, } #[derive(Default)] @@ -131,6 +134,7 @@ impl MarionetteHandler { let options = FirefoxOptions::from_capabilities( fx_capabilities.chosen_binary, + self.settings.android_storage, &mut capabilities, )?; (options, capabilities) @@ -188,10 +192,7 @@ impl MarionetteHandler { fn start_android(&mut self, port: u16, options: FirefoxOptions) -> WebDriverResult<()> { let android_options = options.android.unwrap(); - let mut handler = AndroidHandler::new(&android_options); - handler - .connect(port) - .map_err(|e| WebDriverError::new(ErrorStatus::UnknownError, e.to_string()))?; + let handler = AndroidHandler::new(&android_options, port)?; // Profile management. let is_custom_profile = options.profile.is_some(); @@ -330,7 +331,7 @@ impl WebDriverHandler for MarionetteHandler { let mut capabilities_options = None; // First handle the status message which doesn't actually require a marionette // connection or message - if msg.command == Status { + if let Status = msg.command { let (ready, message) = self .connection .lock() @@ -862,27 +863,6 @@ impl MarionetteSession { Extension(ref extension) => match extension { GetContext => WebDriverResponse::Generic(resp.into_value_response(true)?), SetContext(_) => WebDriverResponse::Void, - XblAnonymousChildren(_) => { - let els_vec = try_opt!( - resp.result.as_array(), - ErrorStatus::UnknownError, - "Failed to interpret body as array" - ); - let els = els_vec - .iter() - .map(|x| self.to_web_element(x)) - .collect::, _>>()?; - - WebDriverResponse::Generic(ValueResponse(serde_json::to_value(els)?)) - } - XblAnonymousByAttribute(_, _) => { - let el = self.to_web_element(try_opt!( - resp.result.get("value"), - ErrorStatus::UnknownError, - "Failed to find value field" - ))?; - WebDriverResponse::Generic(ValueResponse(serde_json::to_value(el)?)) - } InstallAddon(_) => WebDriverResponse::Generic(resp.into_value_response(true)?), UninstallAddon(_) => WebDriverResponse::Void, TakeFullScreenshot => WebDriverResponse::Generic(resp.into_value_response(true)?), @@ -1165,18 +1145,6 @@ impl MarionetteCommand { InstallAddon(x) => (Some("Addon:Install"), Some(x.to_marionette())), SetContext(x) => (Some("Marionette:SetContext"), Some(x.to_marionette())), UninstallAddon(x) => (Some("Addon:Uninstall"), Some(x.to_marionette())), - XblAnonymousByAttribute(e, x) => { - let mut data = x.to_marionette()?; - data.insert("element".to_string(), Value::String(e.to_string())); - (Some("WebDriver:FindElement"), Some(Ok(data))) - } - XblAnonymousChildren(e) => { - let mut data = Map::new(); - data.insert("using".to_owned(), serde_json::to_value("anon")?); - data.insert("value".to_owned(), Value::Null); - data.insert("element".to_string(), serde_json::to_value(e.to_string())?); - (Some("WebDriver:FindElements"), Some(Ok(data))) - } _ => (None, None), }, _ => (None, None), @@ -1581,21 +1549,6 @@ impl ToMarionette for PrintMargins { } } -impl ToMarionette> for XblLocatorParameters { - fn to_marionette(&self) -> WebDriverResult> { - let mut value = Map::new(); - value.insert(self.name.to_owned(), Value::String(self.value.clone())); - - let mut data = Map::new(); - data.insert( - "using".to_owned(), - Value::String("anon attribute".to_string()), - ); - data.insert("value".to_owned(), Value::Object(value)); - Ok(data) - } -} - impl ToMarionette> for ActionsParameters { fn to_marionette(&self) -> WebDriverResult> { Ok(try_opt!( diff --git a/src/prefs.rs b/src/prefs.rs index dfcaac23..17e882c5 100644 --- a/src/prefs.rs +++ b/src/prefs.rs @@ -68,16 +68,16 @@ lazy_static! { // Do not warn on quitting Firefox ("browser.warnOnQuit", Pref::new(false)), - // Do not show datareporting policy notifications which can - // interfere with tests - ("datareporting.healthreport.about.reportUrl", Pref::new("http://%(server)s/dummy/abouthealthreport/")), // removed in Firefox 59 + // Defensively disable data reporting systems ("datareporting.healthreport.documentServerURI", Pref::new("http://%(server)s/dummy/healthreport/")), ("datareporting.healthreport.logging.consoleEnabled", Pref::new(false)), ("datareporting.healthreport.service.enabled", Pref::new(false)), ("datareporting.healthreport.service.firstRun", Pref::new(false)), ("datareporting.healthreport.uploadEnabled", Pref::new(false)), + + // Do not show datareporting policy notifications which can + // interfere with tests ("datareporting.policy.dataSubmissionEnabled", Pref::new(false)), - ("datareporting.policy.dataSubmissionPolicyAccepted", Pref::new(false)), ("datareporting.policy.dataSubmissionPolicyBypassNotification", Pref::new(true)), // Disable the ProcessHangMonitor @@ -91,10 +91,6 @@ lazy_static! { // Disable intalling any distribution extensions or add-ons ("extensions.installDistroAddons", Pref::new(false)), - // Disable extensions compatibility dialogue. - // TODO: Remove once minimum supported Firefox release is 61. - ("extensions.showMismatchUI", Pref::new(false)), - // Turn off extension updates so they do not bother tests ("extensions.update.enabled", Pref::new(false)), ("extensions.update.notifyUser", Pref::new(false)), @@ -126,6 +122,9 @@ lazy_static! { // Disable download and usage of OpenH264, and Widevine plugins ("media.gmp-manager.updateEnabled", Pref::new(false)), + // 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)), @@ -153,8 +152,5 @@ lazy_static! { // Prevent starting into safe mode after application crashes ("toolkit.startup.max_resumed_crashes", Pref::new(-1)), - - // We want to collect telemetry, but we don't want to send in the results - ("toolkit.telemetry.server", Pref::new("https://%(server)s/dummy/telemetry/")), ]; }