Add back WebDriver support (#2324)

This commit is contained in:
chip
2021-08-01 19:54:10 -07:00
committed by GitHub
parent af3cc7d7f7
commit 4b2aa35684
16 changed files with 1000 additions and 7 deletions

View File

@@ -274,6 +274,11 @@
"create-tauri-app": {
"path": "./tooling/create-tauri-app",
"manager": "javascript"
},
"tauri-driver": {
"path": "./tooling/webdriver",
"manager": "rust",
"postversion": "node ../../.scripts/sync-prerelease.js ${ pkg.pkg } ${ release.type }"
}
}
}

5
.changes/tauri-driver.md Normal file
View File

@@ -0,0 +1,5 @@
---
"tauri-driver": minor
---
Initial release including Linux and Windows support.

5
.changes/webdriver.md Normal file
View File

@@ -0,0 +1,5 @@
---
"tauri-runtime-wry": patch
---
Add webdriver support to Tauri.

View File

@@ -64,9 +64,13 @@ pub use wry::application::window::{Window, WindowBuilder as WryWindowBuilder};
use wry::webview::WebviewExtWindows;
use std::{
collections::HashMap,
collections::{
hash_map::Entry::{Occupied, Vacant},
HashMap,
},
convert::TryFrom,
fs::read,
path::PathBuf,
sync::{
atomic::{AtomicBool, Ordering},
mpsc::{channel, Sender},
@@ -83,6 +87,7 @@ use menu::*;
mod mime_type;
use mime_type::MimeType;
type WebContextStore = Mutex<HashMap<Option<PathBuf>, WebContext>>;
type WindowEventHandler = Box<dyn Fn(&WindowEvent) + Send>;
type WindowEventListenersMap = Arc<Mutex<HashMap<Uuid, WindowEventHandler>>>;
type WindowEventListeners = Arc<Mutex<HashMap<WindowId, WindowEventListenersMap>>>;
@@ -752,7 +757,9 @@ pub(crate) enum Message {
#[cfg(feature = "system-tray")]
Tray(TrayMessage),
CreateWebview(
Box<dyn FnOnce(&EventLoopWindowTarget<Message>) -> Result<WindowWrapper> + Send>,
Box<
dyn FnOnce(&EventLoopWindowTarget<Message>, &WebContextStore) -> Result<WindowWrapper> + Send,
>,
Sender<WindowId>,
),
CreateWindow(
@@ -956,7 +963,9 @@ impl Dispatch for WryDispatcher {
.context
.proxy
.send_event(Message::CreateWebview(
Box::new(move |event_loop| create_webview(event_loop, context, pending)),
Box::new(move |event_loop, web_context| {
create_webview(event_loop, web_context, context, pending)
}),
tx,
))
.map_err(|_| Error::FailedToSendMessage)?;
@@ -1243,6 +1252,7 @@ pub struct Wry {
is_event_loop_running: Arc<AtomicBool>,
event_loop: EventLoop<Message>,
windows: Arc<Mutex<HashMap<WindowId, WindowWrapper>>>,
web_context: WebContextStore,
window_event_listeners: WindowEventListeners,
#[cfg(feature = "menu")]
menu_event_listeners: MenuEventListeners,
@@ -1288,7 +1298,9 @@ impl RuntimeHandle for WryHandle {
.dispatcher_context
.proxy
.send_event(Message::CreateWebview(
Box::new(move |event_loop| create_webview(event_loop, dispatcher_context, pending)),
Box::new(move |event_loop, web_context| {
create_webview(event_loop, web_context, dispatcher_context, pending)
}),
tx,
))
.map_err(|_| Error::FailedToSendMessage)?;
@@ -1348,6 +1360,7 @@ impl Runtime for Wry {
is_event_loop_running,
event_loop,
windows: Default::default(),
web_context: Default::default(),
window_event_listeners: Default::default(),
#[cfg(feature = "menu")]
menu_event_listeners: Default::default(),
@@ -1382,6 +1395,7 @@ impl Runtime for Wry {
let proxy = self.event_loop.create_proxy();
let webview = create_webview(
&self.event_loop,
&self.web_context,
DispatcherContext {
main_thread_id: self.main_thread_id,
is_event_loop_running: self.is_event_loop_running.clone(),
@@ -1497,6 +1511,7 @@ impl Runtime for Wry {
fn run_iteration<F: Fn(RunEvent) + 'static>(&mut self, callback: F) -> RunIteration {
use wry::application::platform::run_return::EventLoopExtRunReturn;
let windows = self.windows.clone();
let web_context = &self.web_context;
let window_event_listeners = self.window_event_listeners.clone();
#[cfg(feature = "menu")]
let menu_event_listeners = self.menu_event_listeners.clone();
@@ -1531,6 +1546,7 @@ impl Runtime for Wry {
#[cfg(feature = "system-tray")]
tray_context: &tray_context,
},
web_context,
);
});
self.is_event_loop_running.store(false, Ordering::Relaxed);
@@ -1541,6 +1557,7 @@ impl Runtime for Wry {
fn run<F: Fn(RunEvent) + 'static>(self, callback: F) {
self.is_event_loop_running.store(true, Ordering::Relaxed);
let windows = self.windows.clone();
let web_context = self.web_context;
let window_event_listeners = self.window_event_listeners.clone();
#[cfg(feature = "menu")]
let menu_event_listeners = self.menu_event_listeners.clone();
@@ -1567,6 +1584,7 @@ impl Runtime for Wry {
#[cfg(feature = "system-tray")]
tray_context: &tray_context,
},
&web_context,
);
})
}
@@ -1590,6 +1608,7 @@ fn handle_event_loop(
event_loop: &EventLoopWindowTarget<Message>,
control_flow: &mut ControlFlow,
context: EventLoopIterationContext<'_>,
web_context: &WebContextStore,
) -> RunIteration {
let EventLoopIterationContext {
callback,
@@ -1892,7 +1911,7 @@ fn handle_event_loop(
}
}
}
Message::CreateWebview(handler, sender) => match handler(event_loop) {
Message::CreateWebview(handler, sender) => match handler(event_loop, web_context) {
Ok(webview) => {
let window_id = webview.inner.window().id();
windows.insert(window_id, webview);
@@ -1936,6 +1955,7 @@ fn handle_event_loop(
sender.send(Err(Error::CreateWindow)).unwrap();
}
}
#[cfg(feature = "system-tray")]
Message::Tray(tray_message) => match tray_message {
TrayMessage::UpdateItem(menu_id, update) => {
@@ -2068,6 +2088,7 @@ fn center_window(window: &Window) -> Result<()> {
fn create_webview(
event_loop: &EventLoopWindowTarget<Message>,
web_context: &WebContextStore,
context: DispatcherContext,
pending: PendingWindow<Wry>,
) -> Result<WindowWrapper> {
@@ -2134,13 +2155,27 @@ fn create_webview(
.map_err(|_| wry::Error::InitScriptError)
});
}
let mut context = WebContext::new(webview_attributes.data_directory);
webview_builder = webview_builder.with_web_context(&mut context);
for script in webview_attributes.initialization_scripts {
webview_builder = webview_builder.with_initialization_script(&script);
}
let mut web_context = web_context.lock().expect("poisoned WebContext store");
let is_first_context = web_context.is_empty();
let web_context = match web_context.entry(webview_attributes.data_directory) {
Occupied(occupied) => occupied.into_mut(),
Vacant(vacant) => {
let mut web_context = WebContext::new(vacant.key().clone());
web_context.set_allows_automation(match std::env::var("TAURI_AUTOMATION").as_deref() {
Ok("true") => is_first_context,
_ => false,
});
vacant.insert(web_context)
}
};
let webview = webview_builder
.with_web_context(web_context)
.build()
.map_err(|e| Error::CreateWebview(Box::new(e)))?;

4
tooling/webdriver/.gitignore vendored Normal file
View File

@@ -0,0 +1,4 @@
/target
.DS_Store
*.rs.bk
*~

View File

@@ -0,0 +1,3 @@
// Copyright {20\d{2}(-20\d{2})?} Tauri Programme within The Commons Conservancy
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT

535
tooling/webdriver/Cargo.lock generated Normal file
View File

@@ -0,0 +1,535 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
[[package]]
name = "anyhow"
version = "1.0.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "28b2cd92db5cbd74e8e5028f7e27dd7aa3090e89e4f2a197cc7c8dfb69c7063b"
[[package]]
name = "autocfg"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
[[package]]
name = "bytes"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b700ce4376041dcd0a327fd0097c41095743c4c8af8887265942faf1100bd040"
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "either"
version = "1.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457"
[[package]]
name = "fnv"
version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
[[package]]
name = "futures"
version = "0.3.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0e7e43a803dae2fa37c1f6a8fe121e1f7bf9548b4dfc0522a42f34145dadfc27"
dependencies = [
"futures-channel",
"futures-core",
"futures-executor",
"futures-io",
"futures-sink",
"futures-task",
"futures-util",
]
[[package]]
name = "futures-channel"
version = "0.3.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e682a68b29a882df0545c143dc3646daefe80ba479bcdede94d5a703de2871e2"
dependencies = [
"futures-core",
"futures-sink",
]
[[package]]
name = "futures-core"
version = "0.3.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0402f765d8a89a26043b889b26ce3c4679d268fa6bb22cd7c6aad98340e179d1"
[[package]]
name = "futures-executor"
version = "0.3.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "badaa6a909fac9e7236d0620a2f57f7664640c56575b71a7552fbd68deafab79"
dependencies = [
"futures-core",
"futures-task",
"futures-util",
]
[[package]]
name = "futures-io"
version = "0.3.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "acc499defb3b348f8d8f3f66415835a9131856ff7714bf10dadfc4ec4bdb29a1"
[[package]]
name = "futures-macro"
version = "0.3.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4c40298486cdf52cc00cd6d6987892ba502c7656a16a4192a9992b1ccedd121"
dependencies = [
"autocfg",
"proc-macro-hack",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "futures-sink"
version = "0.3.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a57bead0ceff0d6dde8f465ecd96c9338121bb7717d3e7b108059531870c4282"
[[package]]
name = "futures-task"
version = "0.3.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a16bef9fc1a4dddb5bee51c989e3fbba26569cbb0e31f5b303c184e3dd33dae"
[[package]]
name = "futures-util"
version = "0.3.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "feb5c238d27e2bf94ffdfd27b2c29e3df4a68c4193bb6427384259e2bf191967"
dependencies = [
"autocfg",
"futures-channel",
"futures-core",
"futures-io",
"futures-macro",
"futures-sink",
"futures-task",
"memchr",
"pin-project-lite",
"pin-utils",
"proc-macro-hack",
"proc-macro-nested",
"slab",
]
[[package]]
name = "http"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "527e8c9ac747e28542699a951517aa9a6945af506cd1f2e1b53a576c17b6cc11"
dependencies = [
"bytes",
"fnv",
"itoa",
]
[[package]]
name = "http-body"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "60daa14be0e0786db0f03a9e57cb404c9d756eed2b6c62b9ea98ec5743ec75a9"
dependencies = [
"bytes",
"http",
"pin-project-lite",
]
[[package]]
name = "httparse"
version = "1.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f3a87b616e37e93c22fb19bcd386f02f3af5ea98a25670ad0fce773de23c5e68"
[[package]]
name = "httpdate"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6456b8a6c8f33fee7d958fcd1b60d55b11940a79e63ae87013e6d22e26034440"
[[package]]
name = "hyper"
version = "0.14.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3f71a7eea53a3f8257a7b4795373ff886397178cd634430ea94e12d7fe4fe34"
dependencies = [
"bytes",
"futures-channel",
"futures-core",
"futures-util",
"http",
"http-body",
"httparse",
"httpdate",
"itoa",
"pin-project",
"socket2",
"tokio",
"tower-service",
"tracing",
"want",
]
[[package]]
name = "itoa"
version = "0.4.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd25036021b0de88a0aff6b850051563c6516d0bf53f8638938edbb9de732736"
[[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.95"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "789da6d93f1b866ffe175afc5322a4d76c038605a1c3319bb57b06967ca98a36"
[[package]]
name = "log"
version = "0.4.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710"
dependencies = [
"cfg-if",
]
[[package]]
name = "memchr"
version = "2.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b16bd47d9e329435e309c58469fe0791c2d0d1ba96ec0954152a5ae2b04387dc"
[[package]]
name = "mio"
version = "0.7.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf80d3e903b34e0bd7282b218398aec54e082c840d9baf8339e0080a0c542956"
dependencies = [
"libc",
"log",
"miow",
"ntapi",
"winapi",
]
[[package]]
name = "miow"
version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9f1c5b025cda876f66ef43a113f91ebc9f4ccef34843000e0adf6ebbab84e21"
dependencies = [
"winapi",
]
[[package]]
name = "ntapi"
version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f6bb902e437b6d86e03cce10a7e2af662292c5dfef23b65899ea3ac9354ad44"
dependencies = [
"winapi",
]
[[package]]
name = "pico-args"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7d7afeb98c5a10e0bffcc7fc16e105b04d06729fac5fd6384aebf7ff5cb5a67d"
[[package]]
name = "pin-project"
version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c7509cc106041c40a4518d2af7a61530e1eed0e6285296a3d8c5472806ccc4a4"
dependencies = [
"pin-project-internal",
]
[[package]]
name = "pin-project-internal"
version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "48c950132583b500556b1efd71d45b319029f2b71518d979fcc208e16b42426f"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "pin-project-lite"
version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc0e1f259c92177c30a4c9d177246edd0a3568b25756a977d0632cf8fa37e905"
[[package]]
name = "pin-utils"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
[[package]]
name = "proc-macro-hack"
version = "0.5.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5"
[[package]]
name = "proc-macro-nested"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bc881b2c22681370c6a780e47af9840ef841837bc98118431d4e1868bd0c1086"
[[package]]
name = "proc-macro2"
version = "1.0.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0d8caf72986c1a598726adc988bb5984792ef84f5ee5aa50209145ee8077038"
dependencies = [
"unicode-xid",
]
[[package]]
name = "quote"
version = "1.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7"
dependencies = [
"proc-macro2",
]
[[package]]
name = "ryu"
version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e"
[[package]]
name = "serde"
version = "1.0.126"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec7505abeacaec74ae4778d9d9328fe5a5d04253220a85c4ee022239fc996d03"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.126"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "963a7dbc9895aeac7ac90e74f34a5d5261828f79df35cbed41e10189d3804d43"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "serde_json"
version = "1.0.64"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "799e97dc9fdae36a5c8b8f2cae9ce2ee9fdce2058c57a93e6099d919fd982f79"
dependencies = [
"itoa",
"ryu",
"serde",
]
[[package]]
name = "signal-hook"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "470c5a6397076fae0094aaf06a08e6ba6f37acb77d3b1b91ea92b4d6c8650c39"
dependencies = [
"libc",
"signal-hook-registry",
]
[[package]]
name = "signal-hook-registry"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e51e73328dc4ac0c7ccbda3a494dfa03df1de2f46018127f60c693f2648455b0"
dependencies = [
"libc",
]
[[package]]
name = "signal-hook-tokio"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f6c5d32165ff8b94e68e7b3bdecb1b082e958c22434b363482cfb89dcd6f3ff8"
dependencies = [
"futures-core",
"libc",
"signal-hook",
"tokio",
]
[[package]]
name = "slab"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f173ac3d1a7e3b28003f40de0b5ce7fe2710f9b9dc3fc38664cebee46b3b6527"
[[package]]
name = "socket2"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9e3dfc207c526015c632472a77be09cf1b6e46866581aecae5cc38fb4235dea2"
dependencies = [
"libc",
"winapi",
]
[[package]]
name = "syn"
version = "1.0.72"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1e8cdbefb79a9a5a65e0db8b47b723ee907b7c7f8496c76a1770b5c310bab82"
dependencies = [
"proc-macro2",
"quote",
"unicode-xid",
]
[[package]]
name = "tauri-driver"
version = "0.0.0"
dependencies = [
"anyhow",
"futures",
"hyper",
"pico-args",
"serde",
"serde_json",
"signal-hook",
"signal-hook-tokio",
"tokio",
"which",
]
[[package]]
name = "tokio"
version = "1.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0a38d31d7831c6ed7aad00aa4c12d9375fd225a6dd77da1d25b707346319a975"
dependencies = [
"autocfg",
"libc",
"mio",
"pin-project-lite",
"tokio-macros",
]
[[package]]
name = "tokio-macros"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c49e3df43841dafb86046472506755d8501c5615673955f6aa17181125d13c37"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[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.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09adeb8c97449311ccd28a427f96fb563e7fd31aabf994189879d9da2394b89d"
dependencies = [
"cfg-if",
"pin-project-lite",
"tracing-core",
]
[[package]]
name = "tracing-core"
version = "0.1.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a9ff14f98b1a4b289c6248a023c1c2fa1491062964e9fed67ab29c4e4da4a052"
dependencies = [
"lazy_static",
]
[[package]]
name = "try-lock"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642"
[[package]]
name = "unicode-xid"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3"
[[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 = "which"
version = "4.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b55551e42cbdf2ce2bedd2203d0cc08dba002c27510f86dab6d0ce304cba3dfe"
dependencies = [
"either",
"libc",
]
[[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-i686-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"

View File

@@ -0,0 +1,28 @@
workspace = { }
[package]
name = "tauri-driver"
version = "0.0.0"
authors = ["Tauri Programme within The Commons Conservancy"]
categories = ["gui", "web-programming"]
license = "Apache-2.0 OR MIT"
homepage = "https://tauri.studio"
repository = "https://github.com/tauri-apps/tauri"
description = "Webdriver server for Tauri applications"
readme = "README.md"
exclude = [".license_template", "/target"]
edition = "2018"
[dependencies]
anyhow = "1"
hyper = { version = "0.14", features = [ "client", "http1", "runtime", "server", "stream", "tcp" ] }
futures = "0.3"
pico-args = "0.4"
serde = { version = "1", features = [ "derive" ] }
serde_json = "1"
tokio = { version = "1", features = [ "macros" ] } # other required features enabled by hyper
which = "4"
[target."cfg(unix)".dependencies]
signal-hook = "0.3"
signal-hook-tokio = { version = "0.3", features = [ "futures-v0_3" ] }

View File

@@ -0,0 +1 @@
../../LICENSE.spdx

View File

@@ -0,0 +1 @@
../../LICENSE_APACHE-2.0

View File

@@ -0,0 +1 @@
../../LICENSE_MIT

View File

@@ -0,0 +1,42 @@
# `tauri-driver` _(pre-alpha)_
Cross-platform WebDriver server for Tauri applications.
This is a [WebDriver Intermediary Node] that wraps the native WebDriver server
for platforms that [Tauri] supports. Your WebDriver client will connect to the
running `tauri-driver` server, and `tauri-driver` will handle starting the
native WebDriver server for you behind the scenes. It requires two separate
ports to be used since two distinct [WebDriver Remote Ends] run.
You can configure the ports used with arguments when starting the binary:
* `--port` (default: `4444`)
* `--native-port` (default: `4445`)
Supported platforms:
* **[In Progress]** Linux w/ `WebKitWebDriver`
* **[In Progress]** Windows w/ [Microsoft Edge Driver]
* **[Todo]** macOS w/ [Appium Mac2 Driver] (probably)
_note: the (probably) items haven't been proof-of-concept'd yet, and if it is
not possible to use the listed native webdriver, then a custom implementation
will be used that wraps around [wry]._
## Trying it out
**Until this branch is merged into Tauri `dev`, this code works for pure [wry]
applications only.**
Currently, this uses a branch on [wry] `feat/webdriver`. The support for
automated actions goes all the way down to wry with no real layer for just
Tauri yet. For Windows, the [wry] branch only supports the `win32` backend
and not `winrt`, unless you are okay with the webview not being closable by
the webdriver.
[WebDriver Intermediary Node]: https://www.w3.org/TR/webdriver/#dfn-intermediary-nodes
[WebDriver Remote Ends]: https://www.w3.org/TR/webdriver/#dfn-remote-ends
[Microsoft Edge Driver]: https://developer.microsoft.com/en-us/microsoft-edge/tools/webdriver/
[Appium Mac2 Driver]: https://github.com/appium/appium-mac2-driver
[wry]: https://github.com/tauri-apps/wry
[Tauri]: https://github.com/tauri-apps/tauri

View File

@@ -0,0 +1,58 @@
// Copyright 2019-2021 Tauri Programme within The Commons Conservancy
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
use std::path::PathBuf;
const HELP: &str = "\
USAGE: tauri-driver [FLAGS] [OPTIONS]
FLAGS:
-h, --help Prints help information
OPTIONS:
--port NUMBER Sets the tauri-driver intermediary port
--native-port NUMBER Sets the port of the underlying WebDriver
--native-driver PATH Sets the path to the native WebDriver binary
";
#[derive(Debug, Clone)]
pub struct Args {
pub port: u16,
pub native_port: u16,
pub native_driver: Option<PathBuf>,
}
impl From<pico_args::Arguments> for Args {
fn from(mut args: pico_args::Arguments) -> Self {
// if the user wanted help, we don't care about parsing the rest of the args
if args.contains(["-h", "--help"]) {
println!("{}", HELP);
std::process::exit(0);
}
let native_driver = match args.opt_value_from_str("--native-driver") {
Ok(native_driver) => native_driver,
Err(e) => {
eprintln!("Error while parsing option --native-driver: {}", e);
std::process::exit(1);
}
};
let parsed = Args {
port: args.value_from_str("--port").unwrap_or(4444),
native_port: args.value_from_str("--native-port").unwrap_or(4445),
native_driver,
};
// be strict about accepting args, error for anything extraneous
let rest = args.finish();
if !rest.is_empty() {
eprintln!("Error: unused arguments left: {:?}", rest);
eprintln!("{}", HELP);
std::process::exit(1);
}
parsed
}
}

View File

@@ -0,0 +1,23 @@
// Copyright 2019-2021 Tauri Programme within The Commons Conservancy
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
mod cli;
mod server;
mod webdriver;
fn main() {
let args = pico_args::Arguments::from_env().into();
// start the native webdriver on the port specified in args
let mut driver = webdriver::native(&args);
let driver = driver
.spawn()
.expect("error while running native webdriver");
// start our webdriver intermediary node
if let Err(e) = server::run(args, driver) {
eprintln!("error while running server: {}", e);
std::process::exit(1);
}
}

View File

@@ -0,0 +1,196 @@
// Copyright 2019-2021 Tauri Programme within The Commons Conservancy
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
use crate::cli::Args;
use anyhow::Error;
use futures::TryFutureExt;
use hyper::header::CONTENT_LENGTH;
use hyper::http::uri::Authority;
use hyper::service::{make_service_fn, service_fn};
use hyper::{Body, Client, Method, Request, Response, Server};
use serde::Deserialize;
use serde_json::{json, Map, Value};
use std::convert::Infallible;
use std::path::PathBuf;
use std::process::Child;
type HttpClient = Client<hyper::client::HttpConnector>;
const TAURI_OPTIONS: &str = "tauri:options";
#[derive(Debug, Deserialize)]
struct TauriOptions {
application: PathBuf,
}
impl TauriOptions {
#[cfg(target_os = "linux")]
fn into_native_object(self) -> Map<String, Value> {
let mut map = Map::new();
map.insert(
"webkitgtk:browserOptions".into(),
json!({"binary": self.application}),
);
map
}
#[cfg(target_os = "windows")]
fn into_native_object(self) -> Map<String, Value> {
let mut map = Map::new();
map.insert("ms:edgeChromium".into(), json!(true));
map.insert("browserName".into(), json!("webview2"));
map.insert("ms:edgeOptions".into(), json!({"binary": self.application}));
map
}
}
async fn handle(
client: HttpClient,
mut req: Request<Body>,
args: Args,
) -> Result<Response<Body>, Error> {
// manipulate a new session to convert options to the native driver format
if let (&Method::POST, "/session") = (req.method(), req.uri().path()) {
let (mut parts, body) = req.into_parts();
// get the body from the future stream and parse it as json
let body = hyper::body::to_bytes(body).await?;
let json: Value = serde_json::from_slice(&body)?;
// manipulate the json to convert from tauri option to native driver options
let json = map_capabilities(json);
// serialize json and update the content-length header to be accurate
let bytes = serde_json::to_vec(&json)?;
parts.headers.insert(CONTENT_LENGTH, bytes.len().into());
req = Request::from_parts(parts, bytes.into());
}
client
.request(forward_to_native_driver(req, args)?)
.err_into()
.await
}
/// Transform the request to a request for the native webdriver server.
fn forward_to_native_driver(mut req: Request<Body>, args: Args) -> Result<Request<Body>, Error> {
let host: Authority = {
let headers = req.headers_mut();
headers.remove("host").expect("hyper request has host")
}
.to_str()?
.parse()?;
let path = req
.uri()
.path_and_query()
.expect("hyper request has uri")
.clone();
let uri = format!(
"http://{}:{}{}",
host.host(),
args.native_port,
path.as_str()
);
let (mut parts, body) = req.into_parts();
parts.uri = uri.parse()?;
Ok(Request::from_parts(parts, body))
}
/// only happy path for now, no errors
fn map_capabilities(mut json: Value) -> Value {
let mut native = None;
if let Some(capabilities) = json.get_mut("capabilities") {
if let Some(always_match) = capabilities.get_mut("alwaysMatch") {
if let Some(always_match) = always_match.as_object_mut() {
if let Some(tauri_options) = always_match.remove(TAURI_OPTIONS) {
if let Ok(options) = serde_json::from_value::<TauriOptions>(tauri_options) {
native = Some(options.into_native_object());
}
}
if let Some(native) = native.clone() {
always_match.extend(native);
}
}
}
}
if let Some(native) = native {
if let Some(desired) = json.get_mut("desiredCapabilities") {
if let Some(desired) = desired.as_object_mut() {
desired.remove(TAURI_OPTIONS);
desired.extend(native);
}
}
}
json
}
#[tokio::main(flavor = "current_thread")]
pub async fn run(args: Args, mut _driver: Child) -> Result<(), Error> {
#[cfg(unix)]
let (signals_handle, signals_task) = {
use futures::StreamExt;
use signal_hook::consts::signal::*;
let signals = signal_hook_tokio::Signals::new(&[SIGTERM, SIGINT, SIGQUIT])?;
let signals_handle = signals.handle();
let signals_task = tokio::spawn(async move {
let mut signals = signals.fuse();
while let Some(signal) = signals.next().await {
match signal {
SIGTERM | SIGINT | SIGQUIT => {
_driver
.kill()
.expect("unable to kill native webdriver server");
std::process::exit(0);
}
_ => unreachable!(),
}
}
});
(signals_handle, signals_task)
};
let address = std::net::SocketAddr::from(([127, 0, 0, 1], args.port));
// the client we use to proxy requests to the native webdriver
let client = Client::builder()
.http1_preserve_header_case(true)
.http1_title_case_headers(true)
.retry_canceled_requests(false)
.build_http();
// pass a copy of the client to the http request handler
let service = make_service_fn(move |_| {
let client = client.clone();
let args = args.clone();
async move {
Ok::<_, Infallible>(service_fn(move |request| {
handle(client.clone(), request, args.clone())
}))
}
});
// set up a http1 server that uses the service we just created
Server::bind(&address)
.http1_title_case_headers(true)
.http1_preserve_header_case(true)
.http1_only(true)
.serve(service)
.await?;
#[cfg(unix)]
{
signals_handle.close();
signals_task.await?;
}
Ok(())
}

View File

@@ -0,0 +1,51 @@
// Copyright 2019-2021 Tauri Programme within The Commons Conservancy
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
use crate::cli::Args;
use std::{env::current_dir, process::Command};
// the name of the binary to find in $PATH
#[cfg(target_os = "linux")]
const DRIVER_BINARY: &str = "WebKitWebDriver";
#[cfg(target_os = "windows")]
const DRIVER_BINARY: &str = "msedgedriver.exe";
/// Find the native driver binary in the PATH, or exits the process with an error.
pub fn native(args: &Args) -> Command {
let native_binary = match args.native_driver.as_deref() {
Some(custom) => {
if custom.exists() {
custom.to_owned()
} else {
eprintln!(
"can not find the supplied binary path {}. This is currently required.",
custom.display()
);
match current_dir() {
Ok(cwd) => eprintln!("current working directory: {}", cwd.display()),
Err(error) => eprintln!("can not find current working directory: {}", error),
}
std::process::exit(1);
}
}
None => match which::which(DRIVER_BINARY) {
Ok(binary) => binary,
Err(error) => {
eprintln!(
"can not find binary {} in the PATH. This is currently required.\
You can also pass a custom path with --native-driver",
DRIVER_BINARY
);
eprintln!("{:?}", error);
std::process::exit(1);
}
},
};
let mut cmd = Command::new(native_binary);
cmd.env("TAURI_AUTOMATION", "true");
cmd.arg(format!("--port={}", args.native_port));
cmd
}