From 9b5f85c4b61b8add460f56eff9b26071fb7f6c4f Mon Sep 17 00:00:00 2001 From: Henrik Skupin Date: Mon, 11 Apr 2022 11:10:43 +0200 Subject: [PATCH] Import of v0.31.0 (#2003) --- CHANGES.md | 228 +++--- Cargo.lock | 1462 +++++++++++++++++++++++++++++++++++ Cargo.toml | 22 +- README.md | 10 +- doc/Bugs.md | 2 +- doc/Building.md | 6 +- doc/Capabilities.md | 2 +- doc/Flags.md | 29 +- doc/Profiles.md | 6 +- doc/Support.md | 33 +- doc/Testing.md | 4 +- doc/TraceLogs.md | 2 +- mach_commands.py | 133 ---- marionette/Cargo.toml | 2 +- marionette/src/common.rs | 12 +- marionette/src/webdriver.rs | 2 + moz.build | 27 - src/android.rs | 6 +- src/browser.rs | 186 ++++- src/capabilities.rs | 229 +++++- src/logging.rs | 13 - src/main.rs | 278 +++++-- src/marionette.rs | 158 ++-- src/prefs.rs | 15 - 24 files changed, 2324 insertions(+), 543 deletions(-) create mode 100644 Cargo.lock delete mode 100644 mach_commands.py delete mode 100644 moz.build diff --git a/CHANGES.md b/CHANGES.md index c6e03998..8d768570 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,10 +1,63 @@ -Change log -========== + +# Change log All notable changes to this program are documented in this file. -0.30.0 (2021-09-16, `d372710b98a6`) ------------------------------------- +## 0.31.0 (2022-04-11, `b617178ef491`) + +### Known problems + +- _macOS 10.15 (Catalina) and later:_ + + 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 + +- Users with the [Rust] toolchain installed can now build and install + geckodriver from [crates.io] using Cargo: + + % cargo install geckodriver + +- Support for [Get Element Shadow Root] + + Implemented by [David Burns]. + + The standardised WebDriver [Get Element Shadow Root] endpoint provides a way + to retrieve the Shadow Root of a given web element. This endpoint is + supported by geckodriver when using Firefox version ≥96. + +- Support for additional hosts and origins + + Users can now specify a list of allowed `Host` and `Origin` headers for + incoming requests using the [`--allow-hosts`] and [`--allow-origins`] command + line options, respectively. When such a flag is provided, exactly the given + values will be permitted. + + By default any request with an `Origin` header is rejected, and only requests + containing the bound hostname (specified via `--host`), or an IP address, + in the Host header are allowed. These configuration options are + designed to support scenarios where geckodriver is running on a different + network node to the host e.g. some container based setups. + +### Fixed + +- Geckodriver lets Marionette itself select a system allocated port, so that + it's no longer required to specify a fixed port when using a custom Firefox + profile. This is done by reading the `MarionetteActivePort` file of the + Firefox profile in-use. This helps to avoid port collisions when multiple + Firefox instances are run in parallel. + +- It's no longer possible to specify both the `androidPackage` and `binary` + capabilities togther within [`moz:firefoxOptions`] because these capabilites + are mutually exclusive. + +## 0.30.0 (2021-09-16, `d372710b98a6`) ### Security Fixes @@ -85,9 +138,7 @@ All notable changes to this program are documented in this file. - The test root folder is now removed when geckodriver exists. - -0.29.1 (2021-04-09, `970ef713fe58`) -------------------------------------- +## 0.29.1 (2021-04-09, `970ef713fe58`) ### Known problems @@ -135,9 +186,7 @@ All notable changes to this program are documented in this file. anymore unless there is a strong reason. It will be removed in a future release. - -0.29.0 (2021-01-14, `cf6956a5ec8e`) ------------------------------------- +## 0.29.0 (2021-01-14, `cf6956a5ec8e`) ### Known problems @@ -176,8 +225,7 @@ All notable changes to this program are documented in this file. Note: For this experimental feature the site-isolation support of Firefox aka [Fission] will be not available. -0.28.0 (2020-11-03, `c00d2b6acd3f`) ------------------------------------- +## 0.28.0 (2020-11-03, `c00d2b6acd3f`) ### Known problems @@ -226,8 +274,7 @@ All notable changes to this program are documented in this file. - 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`) ------------------------------------- +## 0.27.0 (2020-07-27, `7b8c4f32cdde`) ### Security Fixes @@ -291,18 +338,17 @@ All notable changes to this program are 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. + - Setups with multiple connected Android devices are now supported. - * Improved cleanup of configuration files. This prevents crashes if + - Improved cleanup of configuration files. This prevents crashes if the application is started manually after launching it through geckodriver. - Windows and Linux binaries are again statically linked. -0.26.0 (2019-10-12, `e9783a644016'`) -------------------------------------- +## 0.26.0 (2019-10-12, `e9783a644016'`) Note that with this release the minimum recommended Firefox version has changed to Firefox ≥60. @@ -384,9 +430,7 @@ has changed to Firefox ≥60. to start a session with a malformed capabilities configuration will now return the [`invalid argument`] error consistently. - -0.25.0 (2019-09-09, `bdb64cf16b68`) ------------------------------------ +## 0.25.0 (2019-09-09, `bdb64cf16b68`) __Note to Windows users!__ With this release you must have the [Microsoft Visual Studio redistributable runtime] @@ -478,9 +522,7 @@ with this particular release that we intend to release a fix for soon. `firefox` binary on Linux. Now it supports different BSD flavours as well. - -0.24.0 (2019-01-28, `917474f3473e`) ------------------------------------ +## 0.24.0 (2019-01-28, `917474f3473e`) ### Added @@ -520,7 +562,7 @@ with this particular release that we intend to release a fix for soon. To cross-compile from another host system, you can use this command: - % cargo build --target armv7-unknown-linux-gnueabihf + % cargo build --target armv7-unknown-linux-gnueabihf ### Changed @@ -548,9 +590,7 @@ with this particular release that we intend to release a fix for soon. - Fixed a regression in the [Take Element Screenshot] to not screenshot the viewport, but the requested element. - -0.23.0 (2018-10-03) -------------------- +## 0.23.0 (2018-10-03) This release contains a number of fixes for regressions introduced in 0.22.0, where we shipped a significant refactoring to the way @@ -604,9 +644,7 @@ geckodriver internally dealt with JSON serialisation. git repository is now limited to 12 characters, as it is when building from an hg checkout. This ensures reproducible builds. - -0.22.0 (2018-09-15) -------------------- +## 0.22.0 (2018-09-15) This release marks an important milestone on the path towards a stable release of geckodriver. Large portions of geckodriver @@ -681,17 +719,15 @@ to the standard. [Jeremy Lempereur]. - Many documentation improvements, now published on - https://firefox-source-docs.mozilla.org/testing/geckodriver/. - + . -0.21.0 (2018-06-15) -------------------- +## 0.21.0 (2018-06-15) Note that with this release of geckodriver the minimum recommended Firefox and Selenium versions have changed: - - Firefox 57 (and greater) - - Selenium 3.11 (and greater) +- Firefox 57 (and greater) +- Selenium 3.11 (and greater) ### Added @@ -753,9 +789,7 @@ Firefox and Selenium versions have changed: - When stdout and stderr is redirected by geckodriver, a bug prevented the redirections from taking effect. - -0.20.1 (2018-04-06) -------------------- +## 0.20.1 (2018-04-06) ### Fixed @@ -769,9 +803,7 @@ Firefox and Selenium versions have changed: The regression should not have caused any functional problems, but the termination cause and the exit status are now reported correctly. - -0.20.0 (2018-03-08) -------------------- +## 0.20.0 (2018-03-08) ### Added @@ -815,9 +847,7 @@ Firefox and Selenium versions have changed: - Improved error messages for malformed capability values. - -0.19.1 (2017-10-30) -------------------- +## 0.19.1 (2017-10-30) ### Changed @@ -837,11 +867,10 @@ Firefox and Selenium versions have changed: - Removed obsolete `socksUsername` and `socksPassword` proxy configuration keys because neither were picked up or recognised - -0.19.0 (2017-09-16) -------------------- +## 0.19.0 (2017-09-16) Note that with geckodriver 0.19.0 the following versions are recommended: + - Firefox 55.0 (and greater) - Selenium 3.5 (and greater) @@ -903,9 +932,7 @@ Note that with geckodriver 0.19.0 the following versions are recommended: - `marionette.defaultPrefs.port` - `marionette.logging` - -0.18.0 (2017-07-10) -------------------- +## 0.18.0 (2017-07-10) ### Changed @@ -937,9 +964,7 @@ Note that with geckodriver 0.19.0 the following versions are recommended: - Linux x86 (i686-unknown-linux-musl) builds are fixed - -0.17.0 (2017-06-09) -------------------- +## 0.17.0 (2017-06-09) ### Added @@ -977,9 +1002,7 @@ Note that with geckodriver 0.19.0 the following versions are recommended: - Use [`SessionNotCreated`] error instead of [`UnknownError`] if there is no current session - -0.16.1 (2017-04-26) -------------------- +## 0.16.1 (2017-04-26) ### Fixed @@ -990,9 +1013,7 @@ Note that with geckodriver 0.19.0 the following versions are recommended: - Session is now ended when closing the last Firefox window (fixes [#613](https://github.com/mozilla/geckodriver/issues/613)) - -0.16.0 (2017-04-21) -------------------- +## 0.16.0 (2017-04-21) Note that geckodriver v0.16.0 is only compatible with Selenium 3.4 and greater. @@ -1078,9 +1099,7 @@ and greater. - Improved log messages to the HTTPD - -0.15.0 (2017-03-08) -------------------- +## 0.15.0 (2017-03-08) ### Added @@ -1106,9 +1125,7 @@ and greater. - Aligned the data structure accepted by the [Set Timeouts] command with the WebDriver specification - -0.14.0 (2017-01-31) -------------------- +## 0.14.0 (2017-01-31) ### Changed @@ -1125,9 +1142,7 @@ and greater. - HTTPD now returns correct response headers for `Content-Type` and `Cache-Control` thanks to [Mike Pennisi] - -0.13.0 (2017-01-06) -------------------- +## 0.13.0 (2017-01-06) ### Changed @@ -1148,9 +1163,7 @@ and greater. - Check for single-character key codes in action sequences now counts characters instead of bytes - -0.12.0 (2017-01-03) -------------------- +## 0.12.0 (2017-01-03) ### Added @@ -1194,17 +1207,13 @@ and greater. - Included capabilities example in the [README] - -0.11.1 (2016-10-10) -------------------- +## 0.11.1 (2016-10-10) ### Fixed - Version number in binary now reflects the release version - -0.11.0 (2016-10-10) -------------------- +## 0.11.0 (2016-10-10) ### Added @@ -1273,9 +1282,7 @@ and greater. which means a tab with an upgrade notice is not displayed when launching a new Firefox version - -0.10.0 (2016-08-02) -------------------- +## 0.10.0 (2016-08-02) ### Changed @@ -1292,9 +1299,7 @@ and greater. - Grammar fix in [README] - -0.9.0 (2016-06-30) ------------------- +## 0.9.0 (2016-06-30) ### Added @@ -1326,9 +1331,7 @@ and greater. - Introduced a changelog (this) - -0.8.0 (2016-06-07) ------------------- +## 0.8.0 (2016-06-07) ### Added @@ -1355,9 +1358,7 @@ and greater. - FIx typo in error message for parsing errors - -0.7.1 (2016-04-27) ------------------- +## 0.7.1 (2016-04-27) ### Added @@ -1380,9 +1381,7 @@ and greater. - Squash rustc 1.6 warnings by using `std::thread::sleep(dur: Duration)` - -0.6.2 (2016-01-20) ------------------- +## 0.6.2 (2016-01-20) ### Added @@ -1394,9 +1393,7 @@ and greater. - Enable CPOWs in Marionette - -0.6.0 (2016-01-12) ------------------- +## 0.6.0 (2016-01-12) ### Added @@ -1412,9 +1409,7 @@ and greater. - Update dependencies - -0.5.0 (2015-12-10) ------------------- +## 0.5.0 (2015-12-10) ### Changed @@ -1426,17 +1421,13 @@ and greater. - Update dependencies - -0.4.2 (2015-10-02) ------------------- +## 0.4.2 (2015-10-02) ### Changed - Skip compiling optional items in hyper - -0.4.1 (2015-10-02) ------------------- +## 0.4.1 (2015-10-02) ### Changed @@ -1444,9 +1435,7 @@ and greater. - Update dependencies - -0.4.0 (2015-09-28) ------------------- +## 0.4.0 (2015-09-28) ### Added @@ -1476,17 +1465,13 @@ and greater. - Fix example in documentation from @vladikoff - -0.3.0 (2015-08-17) ------------------- +## 0.3.0 (2015-08-17) ### Added - Add support for finding elements in subtrees - -0.2.0 (2015-05-20) ------------------- +## 0.2.0 (2015-05-20) ### Added @@ -1522,9 +1507,7 @@ and greater. - Handle null id for switching to frame more correctly - -0.1.0 (2015-04-09) ------------------- +## 0.1.0 (2015-04-09) ### Added @@ -1573,8 +1556,6 @@ and greater. - Squash compile warnings - - [README]: https://github.com/mozilla/geckodriver/blob/master/README.md [Browser Toolbox]: https://developer.mozilla.org/en-US/docs/Tools/Browser_Toolbox [WebDriver conformance]: https://wpt.fyi/results/webdriver/tests?label=experimental @@ -1586,8 +1567,11 @@ and greater. [Fission]: https://wiki.mozilla.org/Project_Fission [Capabilities]: https://firefox-source-docs.mozilla.org/testing/geckodriver/Capabilities.html [Flags]: https://firefox-source-docs.mozilla.org/testing/geckodriver/Flags.html +[`--allow-hosts`]: https://firefox-source-docs.mozilla.org/testing/geckodriver/Flags.html#code-allow-hosts-var-allow-hosts-var-code +[`--allow-origins`]: https://firefox-source-docs.mozilla.org/testing/geckodriver/Flags.html#code-allow-origins-var-allow-origins-var-code [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 +[Rust]: https://rustup.rs/ [`CloseWindowResponse`]: https://docs.rs/webdriver/newest/webdriver/response/struct.CloseWindowResponse.html [`CookieResponse`]: https://docs.rs/webdriver/newest/webdriver/response/struct.CookieResponse.html @@ -1629,6 +1613,7 @@ and greater. [Actions]: https://w3c.github.io/webdriver/webdriver-spec.html#actions [Delete Session]: https://w3c.github.io/webdriver/webdriver-spec.html#delete-session [Element Click]: https://w3c.github.io/webdriver/webdriver-spec.html#element-click +[Get Element Shadow Root]: https://w3c.github.io/webdriver/#get-element-shadow-root [Get Timeouts]: https://w3c.github.io/webdriver/webdriver-spec.html#get-timeouts [Get Window Rect]: https://w3c.github.io/webdriver/webdriver-spec.html#get-window-rect [insecure certificate]: https://w3c.github.io/webdriver/webdriver-spec.html#dfn-insecure-certificate @@ -1644,6 +1629,7 @@ and greater. [WebDriver errors]: https://w3c.github.io/webdriver/webdriver-spec.html#handling-errors [Bastien Orivel]: https://github.com/Eijebong +[David Burns]: https://github.com/AutomatedTester [Jason Juang]: https://github.com/juangj [Jeremy Lempereur]: https://github.com/o0Ignition0o [Joshua Bruning]: https://github.com/joshbruning diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 00000000..bfc81a59 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,1462 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[package]] +name = "aho-corasick" +version = "0.7.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" +dependencies = [ + "memchr", +] + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "base64" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b25d992356d2eb0ed82172f5248873db5560c4721f564b13cb5193bda5e668e" +dependencies = [ + "byteorder", +] + +[[package]] +name = "base64" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3441f0f7b02788e948e47f457ca01f1d7e6d92c693bc132c22b087d3141c03ff" + +[[package]] +name = "base64" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "block-buffer" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf7fe51849ea569fd452f37822f606a5cabb684dc918707a0193fd4664ff324" +dependencies = [ + "generic-array", +] + +[[package]] +name = "byteorder" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" + +[[package]] +name = "bytes" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e4cec68f03f32e44924783795810fa50a7035d8c8ebe78580ad7e6c703fba38" + +[[package]] +name = "bytes" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8" + +[[package]] +name = "cfg-if" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "chrono" +version = "0.4.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73" +dependencies = [ + "libc", + "num-integer", + "num-traits", + "time", + "winapi 0.3.9", +] + +[[package]] +name = "clap" +version = "3.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71c47df61d9e16dc010b55dba1952a57d8c215dbb533fd13cdd13369aac73b1c" +dependencies = [ + "bitflags", + "indexmap", + "lazy_static", + "os_str_bytes", + "strsim", + "terminal_size", + "textwrap", +] + +[[package]] +name = "cookie" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "888604f00b3db336d2af898ec3c1d5d0ddf5e6d462220f2ededc33a87ac4bbd5" +dependencies = [ + "time", +] + +[[package]] +name = "cpufeatures" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59a6001667ab124aebae2a495118e11d30984c3a653e99d86d58971708cf5e4b" +dependencies = [ + "libc", +] + +[[package]] +name = "crc32fast" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" +dependencies = [ + "cfg-if 1.0.0", +] + +[[package]] +name = "crypto-common" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57952ca27b5e3606ff4dd79b0020231aaf9d6aa76dc05fd30137538c50bd3ce8" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "digest" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2fb860ca6fafa5552fb6d0e816a69c8e49f0908bf524e30a90d97c85892d506" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[package]] +name = "dirs" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13aea89a5c93364a98e9b37b2fa237effbb694d5cfe01c5b70941f7eb087d5e3" +dependencies = [ + "cfg-if 0.1.10", + "dirs-sys", +] + +[[package]] +name = "dirs-sys" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b1d1d91c932ef41c0f2663aa8b0ca0342d444d842c06914aa0a7e352d0bada6" +dependencies = [ + "libc", + "redox_users", + "winapi 0.3.9", +] + +[[package]] +name = "dtoa" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56899898ce76aaf4a0f24d914c97ea6ed976d42fec6ad33fcbb0a1103e07b2b0" + +[[package]] +name = "fastrand" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3fcf0cee53519c866c09b5de1f6c56ff9d647101f81c1964fa632e148896cdf" +dependencies = [ + "instant", +] + +[[package]] +name = "flate2" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e6988e897c1c9c485f43b47a529cef42fde0547f9d8d41a7062518f1d8fc53f" +dependencies = [ + "cfg-if 1.0.0", + "crc32fast", + "libc", + "miniz_oxide", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "form_urlencoded" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fc25a87fa4fd2094bffb06925852034d90a17f0d1e05197d4956d3555752191" +dependencies = [ + "matches", + "percent-encoding", +] + +[[package]] +name = "fuchsia-zircon" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82" +dependencies = [ + "bitflags", + "fuchsia-zircon-sys", +] + +[[package]] +name = "fuchsia-zircon-sys" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7" + +[[package]] +name = "futures" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f73fe65f54d1e12b726f517d3e2135ca3125a437b6d998caf1962961f7172d9e" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3083ce4b914124575708913bca19bfe887522d6e2e6d0952943f5eac4a74010" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c09fd04b7e4073ac7156a9539b57a484a8ea920f79c7c675d05d289ab6110d3" + +[[package]] +name = "futures-io" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc4045962a5a5e935ee2fdedaa4e08284547402885ab326734432bed5d12966b" + +[[package]] +name = "futures-sink" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21163e139fa306126e6eedaf49ecdb4588f939600f0b1e770f4205ee4b7fa868" + +[[package]] +name = "futures-task" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c66a976bf5909d801bbef33416c41372779507e7a6b3a5e25e4749c58f776a" + +[[package]] +name = "futures-util" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8b7abd5d659d9b90c8cba917f6ec750a74e2dc23902ef9cd4cc8c8b22e6036a" +dependencies = [ + "futures-core", + "futures-sink", + "futures-task", + "pin-project-lite 0.2.8", + "pin-utils", +] + +[[package]] +name = "geckodriver" +version = "0.31.0" +dependencies = [ + "base64 0.12.3", + "chrono", + "clap", + "hyper", + "lazy_static", + "log", + "marionette", + "mozdevice", + "mozprofile", + "mozrunner", + "mozversion", + "regex", + "serde", + "serde_derive", + "serde_json", + "serde_yaml", + "tempfile", + "url", + "uuid", + "webdriver", + "zip", +] + +[[package]] +name = "generic-array" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd48d33ec7f05fbfa152300fdad764757cbded343c1aa1cff2fbaf4134851803" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9be70c98951c83b8d2f8f60d7065fa6d5146873094452a1008da8c2f1e4205ad" +dependencies = [ + "cfg-if 1.0.0", + "libc", + "wasi", +] + +[[package]] +name = "h2" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e4728fd124914ad25e99e3d15a9361a879f6620f63cb56bbb08f95abb97a535" +dependencies = [ + "bytes 0.5.6", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", + "tracing-futures", +] + +[[package]] +name = "hashbrown" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" + +[[package]] +name = "headers" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cff78e5788be1e0ab65b04d306b2ed5092c815ec97ec70f4ebd5aee158aa55d" +dependencies = [ + "base64 0.13.0", + "bitflags", + "bytes 1.1.0", + "headers-core", + "http", + "httpdate 1.0.2", + "mime", + "sha-1", +] + +[[package]] +name = "headers-core" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7f66481bfee273957b1f20485a4ff3362987f85b2c236580d81b4eb7a326429" +dependencies = [ + "http", +] + +[[package]] +name = "http" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31f4c6746584866f0feabcc69893c5b51beef3831656a968ed7ae254cdc4fd03" +dependencies = [ + "bytes 1.1.0", + "fnv", + "itoa 1.0.1", +] + +[[package]] +name = "http-body" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13d5ff830006f7646652e057693569bfe0d51760c0085a071769d142a205111b" +dependencies = [ + "bytes 0.5.6", + "http", +] + +[[package]] +name = "httparse" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9100414882e15fb7feccb4897e5f0ff0ff1ca7d1a86a23208ada4d7a18e6c6c4" + +[[package]] +name = "httpdate" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "494b4d60369511e7dea41cf646832512a94e542f68bb9c49e54518e0f468eb47" + +[[package]] +name = "httpdate" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" + +[[package]] +name = "hyper" +version = "0.13.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a6f157065790a3ed2f88679250419b5cdd96e714a0d65f7797fd337186e96bb" +dependencies = [ + "bytes 0.5.6", + "futures-channel", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "httparse", + "httpdate 0.3.2", + "itoa 0.4.8", + "pin-project 1.0.10", + "socket2", + "tokio", + "tower-service", + "tracing", + "want", +] + +[[package]] +name = "idna" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8" +dependencies = [ + "matches", + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "indexmap" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f647032dfaa1f8b6dc29bd3edb7bbef4861b8b8007ebb118d6db284fd59f6ee" +dependencies = [ + "autocfg", + "hashbrown", +] + +[[package]] +name = "instant" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" +dependencies = [ + "cfg-if 1.0.0", +] + +[[package]] +name = "iovec" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2b3ea6ff95e175473f8ffe6a7eb7c00d054240321b84c57051175fe3c1e075e" +dependencies = [ + "libc", +] + +[[package]] +name = "itoa" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4" + +[[package]] +name = "itoa" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1aab8fc367588b89dcee83ab0fd66b72b50b72fa1904d7095045ace2b0c81c35" + +[[package]] +name = "kernel32-sys" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" +dependencies = [ + "winapi 0.2.8", + "winapi-build", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "libc" +version = "0.2.122" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec647867e2bf0772e28c8bcde4f0d19a9216916e890543b5a03ed8ef27b8f259" + +[[package]] +name = "line-wrap" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f30344350a2a51da54c1d53be93fade8a237e545dbcc4bdbe635413f2117cab9" +dependencies = [ + "safemem", +] + +[[package]] +name = "linked-hash-map" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fb9b38af92608140b86b693604b9ffcc5824240a484d1ecd4795bacb2fe88f3" + +[[package]] +name = "log" +version = "0.4.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6389c490849ff5bc16be905ae24bc913a9c8892e19b2341dbc175e14c341c2b8" +dependencies = [ + "cfg-if 1.0.0", +] + +[[package]] +name = "marionette" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9da77b04880acc694aafbb3c9efc20880bb4884403b1c92b85ffda8b1820ec3" +dependencies = [ + "serde", + "serde_json", + "serde_repr", +] + +[[package]] +name = "matches" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f" + +[[package]] +name = "memchr" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a" + +[[package]] +name = "mime" +version = "0.3.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d" + +[[package]] +name = "mime_guess" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4192263c238a5f0d0c6bfd21f336a313a4ce1c450542449ca191bb657b4642ef" +dependencies = [ + "mime", + "unicase", +] + +[[package]] +name = "miniz_oxide" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a92518e98c078586bc6c934028adcca4c92a53d6a958196de835170a01d84e4b" +dependencies = [ + "adler", + "autocfg", +] + +[[package]] +name = "mio" +version = "0.6.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4afd66f5b91bf2a3bc13fad0e21caedac168ca4c707504e75585648ae80e4cc4" +dependencies = [ + "cfg-if 0.1.10", + "fuchsia-zircon", + "fuchsia-zircon-sys", + "iovec", + "kernel32-sys", + "libc", + "log", + "miow", + "net2", + "slab", + "winapi 0.2.8", +] + +[[package]] +name = "miow" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebd808424166322d4a38da87083bfddd3ac4c131334ed55856112eb06d46944d" +dependencies = [ + "kernel32-sys", + "net2", + "winapi 0.2.8", + "ws2_32-sys", +] + +[[package]] +name = "mozdevice" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "853947b2e889c7ce59735b00833e75f27beecca6b2b03116cf1decd57b335ca0" +dependencies = [ + "log", + "once_cell", + "regex", + "tempfile", + "thiserror", + "unix_path", + "uuid", + "walkdir", +] + +[[package]] +name = "mozprofile" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5ac2d1cdb74a02db9ded3ca4d6fbd047a26db9ea67ac0343d0576c00d517652" +dependencies = [ + "tempfile", +] + +[[package]] +name = "mozrunner" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5619df94a42cf28fcfa03570d6c5eee07ca1296debde2502e642d7359e5fd099" +dependencies = [ + "dirs", + "log", + "mozprofile", + "plist", + "winreg", +] + +[[package]] +name = "mozversion" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "236ff9cf42e1474c8ff97b84c623d92c90f516e723d5a0dff5d248379e3d03cc" +dependencies = [ + "regex", + "rust-ini", + "semver", +] + +[[package]] +name = "msdos_time" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aad9dfe950c057b1bfe9c1f2aa51583a8468ef2a5baba2ebbe06d775efeb7729" +dependencies = [ + "time", + "winapi 0.3.9", +] + +[[package]] +name = "net2" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "391630d12b68002ae1e25e8f974306474966550ad82dac6886fb8910c19568ae" +dependencies = [ + "cfg-if 0.1.10", + "libc", + "winapi 0.3.9", +] + +[[package]] +name = "num-integer" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db" +dependencies = [ + "autocfg", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" +dependencies = [ + "autocfg", +] + +[[package]] +name = "once_cell" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87f3e037eac156d1775da914196f0f37741a274155e34a0b7e427c35d2a2ecb9" + +[[package]] +name = "os_str_bytes" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e22443d1643a904602595ba1cd8f7d896afe56d26712531c5ff73a15b2fbf64" +dependencies = [ + "memchr", +] + +[[package]] +name = "percent-encoding" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" + +[[package]] +name = "pin-project" +version = "0.4.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9615c18d31137579e9ff063499264ddc1278e7b1982757ebc111028c4d1dc909" +dependencies = [ + "pin-project-internal 0.4.29", +] + +[[package]] +name = "pin-project" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58ad3879ad3baf4e44784bc6a718a8698867bb991f8ce24d1bcbe2cfb4c3a75e" +dependencies = [ + "pin-project-internal 1.0.10", +] + +[[package]] +name = "pin-project-internal" +version = "0.4.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "044964427019eed9d49d9d5bbce6047ef18f37100ea400912a9fa4a3523ab12a" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "pin-project-internal" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "744b6f092ba29c3650faf274db506afd39944f48420f6c86b17cfe0ee1cb36bb" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "pin-project-lite" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "257b64915a082f7811703966789728173279bdebb956b143dbcd23f6f970a777" + +[[package]] +name = "pin-project-lite" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e280fbe77cc62c91527259e9442153f4688736748d24660126286329742b4c6c" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "plist" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b59eb8d91dfa89208ec74a920e3b55f840476cf46568026c18dbaa2999e0d48" +dependencies = [ + "base64 0.10.1", + "chrono", + "indexmap", + "line-wrap", + "serde", + "xml-rs", +] + +[[package]] +name = "podio" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b18befed8bc2b61abc79a457295e7e838417326da1586050b919414073977f19" + +[[package]] +name = "proc-macro2" +version = "1.0.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec757218438d5fda206afc041538b2f6d889286160d649a86a24d37e1235afd1" +dependencies = [ + "unicode-xid", +] + +[[package]] +name = "quote" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "632d02bff7f874a36f33ea8bb416cd484b90cc66c1194b1a1110d067a7013f58" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "redox_syscall" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62f25bc4c7e55e0b0b7a1d43fb893f4fa1361d0abe38b9ce4f323c2adfe6ef42" +dependencies = [ + "bitflags", +] + +[[package]] +name = "redox_users" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b" +dependencies = [ + "getrandom", + "redox_syscall", + "thiserror", +] + +[[package]] +name = "regex" +version = "1.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a11647b6b25ff05a515cb92c365cec08801e83423a235b51e231e1808747286" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.6.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b" + +[[package]] +name = "remove_dir_all" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" +dependencies = [ + "winapi 0.3.9", +] + +[[package]] +name = "rust-ini" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a654c5bda722c699be6b0fe4c0d90de218928da5b724c3e467fc48865c37263" + +[[package]] +name = "ryu" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73b4b750c782965c211b42f022f59af1fbceabdd026623714f104152f1ec149f" + +[[package]] +name = "safemem" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef703b7cb59335eae2eb93ceb664c0eb7ea6bf567079d843e09420219668e072" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "scoped-tls" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea6a9290e3c9cf0f18145ef7ffa62d68ee0bf5fcd651017e586dc7fd5da448c2" + +[[package]] +name = "semver" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" +dependencies = [ + "semver-parser", +] + +[[package]] +name = "semver-parser" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" + +[[package]] +name = "serde" +version = "1.0.136" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce31e24b01e1e524df96f1c2fdd054405f8d7376249a5110886fb4b658484789" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.136" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08597e7152fcd306f41838ed3e37be9eaeed2b61c42e2117266a554fab4662f9" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.79" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e8d9fa5c3b304765ce1fd9c4c8a3de2c8db365a5b91be52f186efc675681d95" +dependencies = [ + "itoa 1.0.1", + "ryu", + "serde", +] + +[[package]] +name = "serde_repr" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98d0516900518c29efa217c298fa1f4e6c6ffc85ae29fd7f4ee48f176e1a9ed5" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_urlencoded" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ec5d77e2d4c73717816afac02670d5c4f534ea95ed430442cad02e7a6e32c97" +dependencies = [ + "dtoa", + "itoa 0.4.8", + "serde", + "url", +] + +[[package]] +name = "serde_yaml" +version = "0.8.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4a521f2940385c165a24ee286aa8599633d162077a54bdcae2a6fd5a7bfa7a0" +dependencies = [ + "indexmap", + "ryu", + "serde", + "yaml-rust", +] + +[[package]] +name = "sha-1" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "028f48d513f9678cda28f6e4064755b3fbb2af6acd672f2c209b62323f7aea0f" +dependencies = [ + "cfg-if 1.0.0", + "cpufeatures", + "digest", +] + +[[package]] +name = "slab" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb703cfe953bccee95685111adeedb76fabe4e97549a58d16f03ea7b9367bb32" + +[[package]] +name = "socket2" +version = "0.3.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "122e570113d28d773067fab24266b66753f6ea915758651696b6e35e49f88d6e" +dependencies = [ + "cfg-if 1.0.0", + "libc", + "winapi 0.3.9", +] + +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + +[[package]] +name = "syn" +version = "1.0.91" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b683b2b825c8eef438b77c36a06dc262294da3d5a5813fac20da149241dcd44d" +dependencies = [ + "proc-macro2", + "quote", + "unicode-xid", +] + +[[package]] +name = "tempfile" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4" +dependencies = [ + "cfg-if 1.0.0", + "fastrand", + "libc", + "redox_syscall", + "remove_dir_all", + "winapi 0.3.9", +] + +[[package]] +name = "terminal_size" +version = "0.1.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "633c1a546cee861a1a6d0dc69ebeca693bf4296661ba7852b9d21d159e0506df" +dependencies = [ + "libc", + "winapi 0.3.9", +] + +[[package]] +name = "textwrap" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1141d4d61095b28419e22cb0bbf02755f5e54e0526f97f1e3d1d160e60885fb" +dependencies = [ + "terminal_size", +] + +[[package]] +name = "thiserror" +version = "1.0.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "854babe52e4df1653706b98fcfc05843010039b406875930a70e4d9644e5c417" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa32fd3f627f367fe16f893e2597ae3c05020f8bba2666a4e6ea73d377e5714b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "time" +version = "0.1.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca8a50ef2360fbd1eeb0ecd46795a87a19024eb4b53c5dc916ca1fd95fe62438" +dependencies = [ + "libc", + "winapi 0.3.9", +] + +[[package]] +name = "tinyvec" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c1c1d5a42b6245520c249549ec267180beaffcc0615401ac8e31853d4b6d8d2" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" + +[[package]] +name = "tokio" +version = "0.2.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6703a273949a90131b290be1fe7b039d0fc884aa1935860dfcbe056f28cd8092" +dependencies = [ + "bytes 0.5.6", + "fnv", + "futures-core", + "iovec", + "lazy_static", + "memchr", + "mio", + "pin-project-lite 0.1.12", + "slab", +] + +[[package]] +name = "tokio-util" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be8242891f2b6cbef26a2d7e8605133c2c554cd35b3e4948ea892d6d68436499" +dependencies = [ + "bytes 0.5.6", + "futures-core", + "futures-sink", + "log", + "pin-project-lite 0.1.12", + "tokio", +] + +[[package]] +name = "tower-service" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "360dfd1d6d30e05fda32ace2c8c70e9c0a9da713275777f5a4dbb8a1893930c6" + +[[package]] +name = "tracing" +version = "0.1.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a1bdf54a7c28a2bbf701e1d2233f6c77f473486b94bee4f9678da5a148dca7f" +dependencies = [ + "cfg-if 1.0.0", + "log", + "pin-project-lite 0.2.8", + "tracing-core", +] + +[[package]] +name = "tracing-core" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90442985ee2f57c9e1b548ee72ae842f4a9a20e3f417cc38dbc5dc684d9bb4ee" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "tracing-futures" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97d095ae15e245a057c8e8451bab9b3ee1e1f68e9ba2b4fbc18d0ac5237835f2" +dependencies = [ + "pin-project 1.0.10", + "tracing", +] + +[[package]] +name = "try-lock" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642" + +[[package]] +name = "typenum" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987" + +[[package]] +name = "unicase" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50f37be617794602aabbeee0be4f259dc1778fabe05e2d67ee8f79326d5cb4f6" +dependencies = [ + "version_check", +] + +[[package]] +name = "unicode-bidi" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a01404663e3db436ed2746d9fefef640d868edae3cceb81c3b8d5732fda678f" + +[[package]] +name = "unicode-normalization" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d54590932941a9e9266f0832deed84ebe1bf2e4c9e4a3554d393d18f5e854bf9" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "unicode-segmentation" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e8820f5d777f6224dc4be3632222971ac30164d4a258d595640799554ebfd99" + +[[package]] +name = "unicode-xid" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" + +[[package]] +name = "unix_path" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af8e291873ae77c4c8d9c9b34d0bee68a35b048fb39c263a5155e0e353783eaf" +dependencies = [ + "unix_str", +] + +[[package]] +name = "unix_str" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ace0b4755d0a2959962769239d56267f8a024fef2d9b32666b3dcd0946b0906" + +[[package]] +name = "url" +version = "2.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a507c383b2d33b5fc35d1861e77e6b383d158b2da5e14fe51b83dfedf6fd578c" +dependencies = [ + "form_urlencoded", + "idna", + "matches", + "percent-encoding", +] + +[[package]] +name = "urlencoding" +version = "1.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a1f0175e03a0973cf4afd476bef05c26e228520400eb1fd473ad417b1c00ffb" + +[[package]] +name = "uuid" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7" +dependencies = [ + "getrandom", + "serde", +] + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "walkdir" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "808cf2735cd4b6866113f648b791c6adc5714537bc222d9347bb203386ffda56" +dependencies = [ + "same-file", + "winapi 0.3.9", + "winapi-util", +] + +[[package]] +name = "want" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0" +dependencies = [ + "log", + "try-lock", +] + +[[package]] +name = "warp" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f41be6df54c97904af01aa23e613d4521eed7ab23537cede692d4058f6449407" +dependencies = [ + "bytes 0.5.6", + "futures", + "headers", + "http", + "hyper", + "log", + "mime", + "mime_guess", + "pin-project 0.4.29", + "scoped-tls", + "serde", + "serde_json", + "serde_urlencoded", + "tokio", + "tower-service", + "tracing", + "tracing-futures", + "urlencoding", +] + +[[package]] +name = "wasi" +version = "0.10.2+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" + +[[package]] +name = "webdriver" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35ac57589eec8879f709240c532f6d238d492e204ec6828aeffaf311c20ff580" +dependencies = [ + "base64 0.12.3", + "bytes 0.5.6", + "cookie", + "http", + "log", + "serde", + "serde_derive", + "serde_json", + "time", + "tokio", + "unicode-segmentation", + "url", + "warp", +] + +[[package]] +name = "winapi" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-build" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +dependencies = [ + "winapi 0.3.9", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "winreg" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a27a759395c1195c4cc5cda607ef6f8f6498f64e78f7900f5de0a127a424704a" +dependencies = [ + "winapi 0.3.9", +] + +[[package]] +name = "ws2_32-sys" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d59cefebd0c892fa2dd6de581e937301d8552cb44489cdff035c6187cb63fa5e" +dependencies = [ + "winapi 0.2.8", + "winapi-build", +] + +[[package]] +name = "xml-rs" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2d7d3948613f75c98fd9328cfdcc45acc4d360655289d0a7d4ec931392200a3" + +[[package]] +name = "yaml-rust" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85" +dependencies = [ + "linked-hash-map", +] + +[[package]] +name = "zip" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36b9e08fb518a65cf7e08a1e482573eb87a2f4f8c6619316612a3c1f162fe822" +dependencies = [ + "flate2", + "msdos_time", + "podio", + "time", +] diff --git a/Cargo.toml b/Cargo.toml index 42030986..014f0165 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,34 +1,38 @@ [package] name = "geckodriver" -version = "0.30.0" +version = "0.31.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" readme = "README.md" license = "MPL-2.0" -publish = false +authors = ["Mozilla"] edition = "2018" [dependencies] base64 = "0.12" chrono = "0.4.6" -clap = { version = "^2.19", default-features = false, features = ["suggestions", "wrap_help"] } +clap = { version = "3", default-features = false, features = ["cargo", "std", "suggestions", "wrap_help"] } hyper = "0.13" lazy_static = "1.0" log = { version = "0.4", features = ["std"] } -marionette = { path = "./marionette" } -mozdevice = "0.4.0" -mozprofile = "0.7.3" -mozrunner = "0.13.0" -mozversion = "0.4.2" +marionette = "0.2.0" +mozdevice = "0.5.0" +mozprofile = "0.8.0" +mozrunner = "0.14.0" +mozversion = "0.4.3" 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" +url = "2.0" uuid = { version = "0.8", features = ["v4"] } -webdriver = "0.44.0" +webdriver = "0.45.0" zip = { version = "0.4", default-features = false, features = ["deflate"] } +[dev-dependencies] +tempfile = "3" + [[bin]] name = "geckodriver" diff --git a/README.md b/README.md index a1d65e8a..923c9147 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,6 @@ Downloads * [Releases](https://github.com/mozilla/geckodriver/releases/latest) * [Change log](https://searchfox.org/mozilla-central/source/testing/geckodriver/CHANGES.md) - Documentation ------------- @@ -65,6 +64,15 @@ This GitHub repository is only used for issue tracking and making releases. [Mozilla Public License]: https://www.mozilla.org/en-US/MPL/2.0/ [mozilla-central]: https://hg.mozilla.org/mozilla-central/file/tip/testing/geckodriver +Custom release builds +--------------------- + +If a binary is not available for your platform, it's possibe to create a custom +build using the [Rust] toolchain. To do this, checkout the release tag for the +version of interest and run `cargo build`. Alternatively the latest version may +be built and installed from `crates.io` using `cargo install geckodriver`. + +[Rust]: https://rustup.rs/ Contact ------- diff --git a/doc/Bugs.md b/doc/Bugs.md index 0a4272d0..c4117ea2 100644 --- a/doc/Bugs.md +++ b/doc/Bugs.md @@ -40,7 +40,7 @@ is appropriate. Bugs specific to geckodriver will be filed in the [`Testing :: geckodriver`] component in Bugzilla. [mailing list]: ./#communication -[trace-level log]: TraceLogs.html +[trace-level log]: TraceLogs.md [GitHub issue tracker]: https://github.com/mozilla/geckodriver/issues [ISSUE_TEMPLATE.md]: https://raw.githubusercontent.com/mozilla/geckodriver/master/ISSUE_TEMPLATE.md [`Testing :: geckodriver`]: https://bugzilla.mozilla.org/buglist.cgi?component=geckodriver diff --git a/doc/Building.md b/doc/Building.md index 50e2f969..18fab16c 100644 --- a/doc/Building.md +++ b/doc/Building.md @@ -29,13 +29,13 @@ You can run your freshly built geckodriver this way: % ./mach geckodriver -- --other --flags -See [Testing](Testing.html) for how to run tests. +See [Testing](Testing.md) for how to run tests. [Rust]: https://www.rust-lang.org/ [webdriver crate]: https://crates.io/crates/webdriver [commands]: https://docs.rs/webdriver/newest/webdriver/command/ [responses]: https://docs.rs/webdriver/newest/webdriver/response/ [errors]: https://docs.rs/webdriver/newest/webdriver/error/enum.ErrorStatus.html -[Marionette protocol]: /testing/marionette/doc/marionette/Protocol.html +[Marionette protocol]: /testing/marionette/Protocol.md [WebDriver]: https://w3c.github.io/webdriver/ -[Marionette]: /testing/marionette/doc/marionette +[Marionette]: /testing/marionette/index.rst diff --git a/doc/Capabilities.md b/doc/Capabilities.md index feec2cbe..abeda224 100644 --- a/doc/Capabilities.md +++ b/doc/Capabilities.md @@ -50,7 +50,7 @@ A list of all available websocket targets: The contained `webSocketDebuggerUrl` entries can be used to connect to the websocket and interact with the browser by using the CDP protocol. -[Remote Protocol]: /testing/remote/doc/ +[Remote Protocol]: /remote/index.rst [Chrome DevTools Protocol]: https://chromedevtools.github.io/devtools-protocol/ diff --git a/doc/Flags.md b/doc/Flags.md index e0eda547..2299e9eb 100644 --- a/doc/Flags.md +++ b/doc/Flags.md @@ -1,6 +1,32 @@ Flags ===== +#### --allow-hosts ALLOW_HOSTS... + +Values of the `Host` header to allow for incoming requests. + +By default the value of HOST is allowed. If `--allow-hosts` +is provided, exactly the given values will be permitted. For example +`--allow-host geckodriver.test webdriver.local` will allow requests +with `Host` set to `geckodriver.test` or `webdriver.local`. + +Requests with `Host` set to an IP address are always allowed. + +#### --allow-origins ALLOW_ORIGINS... + +Values of the `Origin` header to allow for incoming requests. + +`Origin` is set by web browsers for all `POST` requests, and most +other cross-origin requests. By default any request with an `Origin` +header is rejected to protect against malicious websites trying to +access geckodriver running on the local machine. + +If `--allow-origins` is provided, web services running on the given +origin will be able to make requests to geckodriver. For example +`--allow-origins https://webdriver.test:8080` will allow a web-based +service on the origin with scheme `https`, hostname `webdriver.test`, +and port `8080` to access the geckodriver instance. + #### --android-storage ANDROID_STORAGE **Deprecation warning**: This argument is deprecated and planned to be removed @@ -133,7 +159,6 @@ Port to use for the WebDriver server. Defaults to 4444. A helpful trick is that it is possible to bind to 0 to get the system to atomically assign a free port. - #### --jsdebugger Attach [browser toolbox] debugger when Firefox starts. This is @@ -174,3 +199,5 @@ argument is passed to geckodriver. Increases the logging verbosity by to debug level when passing a single `-v`, or to trace level if `-vv` is passed. This is analogous to passing `--log debug` and `--log trace`, respectively. + +[Marionette]: /testing/marionette/index.rst diff --git a/doc/Profiles.md b/doc/Profiles.md index d9a7c9b8..d4665f45 100644 --- a/doc/Profiles.md +++ b/doc/Profiles.md @@ -32,8 +32,8 @@ two distinct systems. [profiles]: https://support.mozilla.org/en-US/kb/profiles-where-firefox-stores-user-data [_Automation preferences_]: #automation-preferences -[`args` capability]: ./Capabilities.html#capability-args -[`profile` capability]: ./Capabilities.html#capability-profile +[`args` capability]: https://developer.mozilla.org/en-US/docs/Web/WebDriver/Capabilities/firefoxOptions#args_array_of_strings +[`profile` capability]: https://developer.mozilla.org/en-US/docs/Web/WebDriver/Capabilities/firefoxOptions#profile_string [known bug concerning `--profile`]: https://github.com/mozilla/geckodriver/issues/1058 @@ -96,7 +96,7 @@ the `--marionette-port ` flag is used specifically to instruct the Marionette server in Firefox which port to use. [user.js file]: http://kb.mozillazine.org/User.js_file -[`prefs` capability]: ./Capabilities.html#capability-prefs +[`prefs` capability]: https://developer.mozilla.org/en-US/docs/Web/WebDriver/Capabilities/firefoxOptions#prefs_preferences_object Temporary profiles not being removed diff --git a/doc/Support.md b/doc/Support.md index 689522e8..8dd42be2 100644 --- a/doc/Support.md +++ b/doc/Support.md @@ -1,5 +1,5 @@ -Supported platforms -=================== + +# Supported platforms The following table shows a mapping between [geckodriver releases], and required versions of Selenium and Firefox: @@ -22,41 +22,47 @@ and required versions of Selenium and Firefox: max + + 0.31.0 + ≥ 3.11 (3.14 Python) + 91 ESR + n/a + 0.30.0 ≥ 3.11 (3.14 Python) 78 ESR - n/a + 90 0.29.1 ≥ 3.11 (3.14 Python) 60 - n/a + 90 0.29.0 ≥ 3.11 (3.14 Python) 60 - n/a + 90 0.28.0 ≥ 3.11 (3.14 Python) 60 - n/a + 90 0.27.0 ≥ 3.11 (3.14 Python) 60 - n/a + 90 0.26.0 ≥ 3.11 (3.14 Python) 60 - n/a + 90 0.25.0 ≥ 3.11 (3.14 Python) 57 - n/a + 90 0.24.0 ≥ 3.11 (3.14 Python) @@ -109,15 +115,13 @@ and required versions of Selenium and Firefox: 62 -Clients -------- +## Clients [Selenium] users must update to version 3.11 or later to use geckodriver. Other clients that follow the [W3C WebDriver specification][WebDriver] are also supported. -Firefoxen ---------- +## Firefoxen geckodriver is not yet feature complete. This means that it does not yet offer full conformance with the [WebDriver] standard @@ -133,8 +137,7 @@ in the most recent Firefox versions, and we strongly advise using the latest [Firefox Nightly] with geckodriver. Since Windows XP support in Firefox was dropped with Firefox 53, we do not support this platform. -Android -------- +## Android Starting with the 0.26.0 release geckodriver is able to connect to Android devices, and to control packages which are based on [GeckoView] diff --git a/doc/Testing.md b/doc/Testing.md index f14aff6e..d8c97ce8 100644 --- a/doc/Testing.md +++ b/doc/Testing.md @@ -55,5 +55,5 @@ flag to geckodriver through WPT: [cargo]: http://doc.crates.io/guide.html [headless mode]: https://developer.mozilla.org/en-US/Firefox/Headless_mode [mozconfig]: https://developer.mozilla.org/en-US/docs/Mozilla/Developer_guide/Build_Instructions/Configuring_Build_Options -[trace-level logs]: TraceLogs.html -[Marionette protocol]: https://firefox-source-docs.mozilla.org/testing/marionette/Protocol.html +[trace-level logs]: TraceLogs.md +[Marionette protocol]: /testing/marionette/Protocol.md diff --git a/doc/TraceLogs.md b/doc/TraceLogs.md index ef472e47..c16fdd70 100644 --- a/doc/TraceLogs.md +++ b/doc/TraceLogs.md @@ -102,7 +102,7 @@ documentation to cover all the best known clients people use with geckodriver. If you find your language missing, please consider [submitting a patch]. -[submitting a patch]: ../CONTRIBUTING.md +[submitting a patch]: Patches.md C# diff --git a/mach_commands.py b/mach_commands.py deleted file mode 100644 index e557caea..00000000 --- a/mach_commands.py +++ /dev/null @@ -1,133 +0,0 @@ -# This Source Code Form is subject to the terms of the Mozilla Public -# License, v. 2.0. If a copy of the MPL was not distributed with this -# file, You can obtain one at http://mozilla.org/MPL/2.0/. - -from __future__ import absolute_import, print_function, unicode_literals - -import os -import logging - -from mach.decorators import ( - Command, - CommandArgument, - CommandArgumentGroup, - CommandProvider, -) - -from mozbuild.base import MachCommandBase, BinaryNotFoundException - - -@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." - ) - @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.", - ) - def run(self, command_context, binary, params, debug, debugger, debugger_args): - try: - binpath = command_context.get_binary_path("geckodriver") - except BinaryNotFoundException as e: - command_context.log( - logging.ERROR, "geckodriver", {"error": str(e)}, "ERROR: {error}" - ) - command_context.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] - - if params: - args.extend(params) - - if binary is None: - try: - binary = command_context.get_binary_path("app") - except BinaryNotFoundException as e: - command_context.log( - logging.ERROR, "geckodriver", {"error": str(e)}, "ERROR: {error}" - ) - command_context.log( - logging.INFO, "geckodriver", {"help": e.help()}, "{help}" - ) - return 1 - - args.extend(["--binary", binary]) - - if debug or debugger or debugger_args: - if "INSIDE_EMACS" in os.environ: - command_context.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 - ) - - if debugger: - debuggerInfo = mozdebug.get_debugger_info(debugger, debugger_args) - if not debuggerInfo: - print("Could not find a suitable debugger in your PATH.") - return 1 - - # Parameters come from the CLI. We need to convert them before - # 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("(We can't handle the %r character.)" % e.char) - return 1 - - # Prepend the debugger args. - args = [debuggerInfo.path] + debuggerInfo.args + args - - return command_context.run_process( - args=args, ensure_exit_code=False, pass_thru=True - ) diff --git a/marionette/Cargo.toml b/marionette/Cargo.toml index 1d18558a..034560c2 100644 --- a/marionette/Cargo.toml +++ b/marionette/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "marionette" -version = "0.1.0" +version = "0.2.0" authors = ["Mozilla"] edition = "2018" diff --git a/marionette/src/common.rs b/marionette/src/common.rs index 78bf3afd..e819757e 100644 --- a/marionette/src/common.rs +++ b/marionette/src/common.rs @@ -121,12 +121,7 @@ pub struct WebElement { pub struct Timeouts { #[serde(default, skip_serializing_if = "Option::is_none")] pub implicit: Option, - #[serde( - default, - rename = "pageLoad", - alias = "page load", - skip_serializing_if = "Option::is_none" - )] + #[serde(default, rename = "pageLoad", skip_serializing_if = "Option::is_none")] pub page_load: Option, #[serde(default, skip_serializing_if = "Option::is_none")] #[allow(clippy::option_option)] @@ -135,7 +130,6 @@ pub struct Timeouts { #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct Window { - pub name: String, pub handle: String, } @@ -222,10 +216,6 @@ mod tests { &data, json!({"implicit":1000,"pageLoad":200000,"script":60000}), ); - assert_de( - &data, - json!({"implicit":1000,"page load":200000,"script":60000}), - ); } #[test] diff --git a/marionette/src/webdriver.rs b/marionette/src/webdriver.rs index 68773130..a7957011 100644 --- a/marionette/src/webdriver.rs +++ b/marionette/src/webdriver.rs @@ -231,6 +231,8 @@ pub enum Command { GetElementText(LegacyWebElement), #[serde(rename = "WebDriver:GetPageSource")] GetPageSource, + #[serde(rename = "WebDriver:GetShadowRoot")] + GetShadowRoot { id: String }, #[serde(rename = "WebDriver:GetTimeouts")] GetTimeouts, #[serde(rename = "WebDriver:GetTitle")] diff --git a/moz.build b/moz.build deleted file mode 100644 index 9cda32b4..00000000 --- a/moz.build +++ /dev/null @@ -1,27 +0,0 @@ -# This Source Code Form is subject to the terms of the Mozilla Public -# License, v. 2.0. If a copy of the MPL was not distributed with this -# file, You can obtain one at http://mozilla.org/MPL/2.0/. - -RUST_PROGRAMS += ["geckodriver"] -# Some Rust build scripts compile C/C++ sources, don't error on warnings for them. -AllowCompilerWarnings() - -RUST_TESTS = [ - "geckodriver", - "webdriver", - "marionette", - # TODO: Move to mozbase/rust/moz.build once those crates can be - # tested separately. - "mozdevice", - "mozprofile", - "mozrunner", - "mozversion", -] - -with Files("**"): - BUG_COMPONENT = ("Testing", "geckodriver") - -SPHINX_TREES["/testing/geckodriver"] = "doc" - -with Files("doc/**"): - SCHEDULES.exclusive = ["docs"] diff --git a/src/android.rs b/src/android.rs index 8969fb14..3cc87b0f 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 b777a0a9..306bfe1c 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> { + 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, + process: FirefoxProcess, + profile_path: Option, } 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> { + 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 { match self.process.try_wait() { Ok(Some(status)) => Some( @@ -146,10 +200,33 @@ impl LocalBrowser { } } +fn read_marionette_port(profile_path: &Path) -> Option { + 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::().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> { + 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 278530ea..97e1e287 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 { - self.browser_version(caps)? - .map(|v| self.compare_browser_version(&v, ">=90")) - .unwrap_or(Ok(false)) + fn web_socket_url(&mut self, _: &Capabilities) -> WebDriverResult { + 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, - pub profile: Option, + pub profile: ProfileType, pub args: Option>, pub env: Option>, 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::>().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 `). \ + Support for named profiles will be removed in a future geckodriver release. \ + Please instead use the `--profile ` 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::>() + .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::>() + .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>> { 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 { @@ -862,6 +934,23 @@ mod tests { assert_eq!(opts.prefs, vec![]); } + #[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 @@ -975,6 +1106,16 @@ mod tests { .expect_err("Firefox options need to be of type object"); } + #[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(); @@ -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 de477dca..73ce0621 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! { @@ -238,17 +236,6 @@ mod tests { assert_eq!(Level::Trace as usize, 10); } - #[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); diff --git a/src/main.rs b/src/main.rs index c6c55036..115d6505 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 = result::Result; +#[allow(clippy::large_enum_variant)] enum Operation { Help, Version, Server { log_level: Option, - host: String, address: SocketAddr, + allow_hosts: Vec, + allow_origins: Vec, 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 { + let mut socket_addrs = match format!("{}:{}", webdriver_host, webdriver_port).to_socket_addrs() + { + Ok(addrs) => addrs.collect::>(), + 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 { + 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> { + 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, +) -> Result, url::ParseError> { + allow_hosts + .map(|hosts| hosts.map(Host::parse).collect::>()) + .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::, url::ParseError>>() +} + +fn get_allowed_origins(allow_origins: Option) -> Result, url::ParseError> { + allow_origins + .map(|origins| { + origins + .map(Url::parse) + .collect::, url::ParseError>>() + }) + .unwrap_or_else(|| Ok(vec![])) +} + fn parse_args(app: &mut App) -> ProgramResult { - 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::("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 { // 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 { 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 cf3f40e8..325cc9a1 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, pub(crate) websocket_port: u16, + pub(crate) allow_hosts: Vec, + pub(crate) allow_origins: Vec, /// Brings up the Browser Toolbox when starting Firefox, /// letting you debug internals. @@ -109,8 +114,8 @@ impl MarionetteHandler { session_id: Option, new_session_parameters: &NewSessionParameters, ) -> WebDriverResult { + 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 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 { + 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 { - 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 { + fn connect(host: &str, browser: &mut Browser) -> WebDriverResult { 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 for SwitchToFrameParameters { impl ToMarionette for SwitchToWindowParameters { fn to_marionette(&self) -> WebDriverResult { Ok(Window { - name: self.handle.clone(), handle: self.handle.clone(), }) } diff --git a/src/prefs.rs b/src/prefs.rs index e9a4032f..b9b1785b 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)),