Explorar el Código

Add back WebDriver support (#2324)

chip hace 4 años
padre
commit
4b2aa35684

+ 5 - 0
.changes/config.json

@@ -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 - 0
.changes/tauri-driver.md

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

+ 5 - 0
.changes/webdriver.md

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

+ 42 - 7
core/tauri-runtime-wry/src/lib.rs

@@ -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 - 0
tooling/webdriver/.gitignore

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

+ 3 - 0
tooling/webdriver/.license_template

@@ -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 - 0
tooling/webdriver/Cargo.lock

@@ -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"

+ 28 - 0
tooling/webdriver/Cargo.toml

@@ -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" ] }

+ 1 - 0
tooling/webdriver/LICENSE.spdx

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

+ 1 - 0
tooling/webdriver/LICENSE_APACHE-2.0

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

+ 1 - 0
tooling/webdriver/LICENSE_MIT

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

+ 42 - 0
tooling/webdriver/README.md

@@ -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

+ 58 - 0
tooling/webdriver/src/cli.rs

@@ -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
+  }
+}

+ 23 - 0
tooling/webdriver/src/main.rs

@@ -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);
+  }
+}

+ 196 - 0
tooling/webdriver/src/server.rs

@@ -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(())
+}

+ 51 - 0
tooling/webdriver/src/webdriver.rs

@@ -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
+}