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

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