Przeglądaj źródła

feat(tauri): use WRY as webview engine (#1190)

Lucas Fernandes Nogueira 4 lat temu
rodzic
commit
99ecf7bb3e

+ 5 - 0
.changes/wry.md

@@ -0,0 +1,5 @@
+---
+"tauri": minor
+---
+
+Use [WRY](https://github.com/tauri-apps/wry) as Webview interface, thanks to @wusyong.

+ 1 - 1
api/src/tauri.ts

@@ -34,7 +34,7 @@ function uid(): string {
  * @param args
  */
 function invoke(args: any): void {
-  window.__TAURI_INVOKE_HANDLER__(args);
+  window.__TAURI_INVOKE_HANDLER__(JSON.stringify(args));
 }
 
 function transformCallback(

+ 8 - 8
cli/core/src/templates/tauri.js

@@ -118,24 +118,24 @@ if (!String.prototype.startsWith) {
 
       if (window.__TAURI_INVOKE_HANDLER__) {
         window.__TAURI_INVOKE_HANDLER__(
-          _objectSpread(
+          JSON.stringify(_objectSpread(
             {
               callback: callback,
               error: error
             },
             args
-          )
+          ))
         )
       } else {
         window.addEventListener('DOMContentLoaded', function () {
           window.__TAURI_INVOKE_HANDLER__(
-            _objectSpread(
+            JSON.stringify(_objectSpread(
               {
                 callback: callback,
                 error: error
               },
               args
-            )
+            ))
           )
         })
       }
@@ -182,10 +182,10 @@ if (!String.prototype.startsWith) {
               target.href.startsWith('http') &&
               target.target === '_blank'
             ) {
-              window.__TAURI_INVOKE_HANDLER__({
+              window.__TAURI_INVOKE_HANDLER__(JSON.stringify({
                 cmd: 'open',
                 uri: target.href
-              })
+              }))
               e.preventDefault()
             }
             break
@@ -294,10 +294,10 @@ if (!String.prototype.startsWith) {
   })
 
   window.alert = function (message) {
-    window.__TAURI_INVOKE_HANDLER__({
+    window.__TAURI_INVOKE_HANDLER__(JSON.stringify({
       cmd: 'messageDialog',
       message: message
-    })
+    }))
   }
 
   window.confirm = function (message) {

+ 1 - 1
cli/tauri.js/templates/src-tauri/src/main.rs

@@ -6,7 +6,7 @@
 mod cmd;
 
 fn main() {
-  tauri::AppBuilder::<tauri::flavors::Official>::new()
+  tauri::AppBuilder::<tauri::flavors::Wry>::new()
     .invoke_handler(|_webview, arg| async move {
       use cmd::Cmd::*;
       match serde_json::from_str(&arg) {

+ 5 - 3
cli/tauri.js/test/jest/fixtures/app/dist/index.html

@@ -136,9 +136,11 @@
       })
 
       setTimeout(function () {
-        window.__TAURI_INVOKE_HANDLER__({
-          cmd: 'exit'
-        })
+        window.__TAURI_INVOKE_HANDLER__(
+          JSON.stringify({
+            cmd: 'exit'
+          })
+        )
       }, 15000)
     </script>
   </body>

+ 6 - 10
tauri-api/src/rpc.rs

@@ -1,6 +1,5 @@
 use serde::Serialize;
 use serde_json::Value as JsonValue;
-use std::fmt::Display;
 
 /// Formats a function name and argument to be evaluated as callback.
 ///
@@ -25,10 +24,7 @@ use std::fmt::Display;
 /// }).expect("failed to serialize"));
 /// assert!(cb.contains(r#"window["callback-function-name"]({"value":"some value"})"#));
 /// ```
-pub fn format_callback<T: Into<JsonValue>, S: AsRef<str> + Display>(
-  function_name: S,
-  arg: T,
-) -> String {
+pub fn format_callback<T: Into<JsonValue>, S: AsRef<str>>(function_name: S, arg: T) -> String {
   format!(
     r#"
       if (window["{fn}"]) {{
@@ -37,7 +33,7 @@ pub fn format_callback<T: Into<JsonValue>, S: AsRef<str> + Display>(
         console.warn("[TAURI] Couldn't find callback id {fn} in window. This happens when the app is reloaded while Rust is running an asynchronous operation.")
       }}
     "#,
-    fn = function_name,
+    fn = function_name.as_ref(),
     arg = arg.into().to_string()
   )
 }
@@ -57,17 +53,17 @@ pub fn format_callback<T: Into<JsonValue>, S: AsRef<str> + Display>(
 /// ```
 /// use tauri_api::rpc::format_callback_result;
 /// let res: Result<u8, &str> = Ok(5);
-/// let cb = format_callback_result(res, "success_cb".to_string(), "error_cb".to_string()).expect("failed to format");
+/// let cb = format_callback_result(res, "success_cb", "error_cb").expect("failed to format");
 /// assert!(cb.contains(r#"window["success_cb"](5)"#));
 ///
 /// let res: Result<&str, &str> = Err("error message here");
-/// let cb = format_callback_result(res, "success_cb".to_string(), "error_cb".to_string()).expect("failed to format");
+/// let cb = format_callback_result(res, "success_cb", "error_cb").expect("failed to format");
 /// assert!(cb.contains(r#"window["error_cb"]("error message here")"#));
 /// ```
 pub fn format_callback_result<T: Serialize, E: Serialize>(
   result: Result<T, E>,
-  success_callback: String,
-  error_callback: String,
+  success_callback: impl AsRef<str>,
+  error_callback: impl AsRef<str>,
 ) -> crate::Result<String> {
   let rpc = match result {
     Ok(res) => format_callback(success_callback, serde_json::to_value(res)?),

+ 1 - 2
tauri/Cargo.toml

@@ -20,7 +20,6 @@ features = [ "all-api" ]
 [dependencies]
 serde_json = "1.0"
 serde = { version = "1.0", features = [ "derive" ] }
-webview_official = "0.2.0"
 tauri_includedir = "0.6.0"
 phf = "0.8.0"
 base64 = "0.13.0"
@@ -33,10 +32,10 @@ async-trait = "0.1"
 uuid = { version = "0.8.2", features = [ "v4" ] }
 anyhow = "1.0.38"
 thiserror = "1.0.23"
-envmnt = "0.8.4"
 once_cell = "1.5.2"
 tauri-api = { version = "0.7.5", path = "../tauri-api" }
 urlencoding = "1.1.1"
+wry = { git = "https://github.com/tauri-apps/wry", rev = "42f4f2133f7921ed5adc47908787094da8abeac5" }
 
 [target."cfg(target_os = \"windows\")".dependencies]
 runas = "0.2"

Plik diff jest za duży
+ 629 - 118
tauri/examples/api/src-tauri/Cargo.lock


+ 1 - 1
tauri/examples/api/src-tauri/src/main.rs

@@ -13,7 +13,7 @@ struct Reply {
 }
 
 fn main() {
-  tauri::AppBuilder::new()
+  tauri::AppBuilder::<tauri::flavors::Wry>::new()
     .setup(|webview, _source| async move {
       let mut webview = webview.clone();
       tauri::event::listen(String::from("js-event"), move |msg| {

+ 1 - 1
tauri/examples/api/src-tauri/tauri.conf.json

@@ -44,7 +44,7 @@
       }
     },
     "embeddedServer": {
-      "active": false
+      "active": true
     },
     "bundle": {
       "active": true,

Plik diff jest za duży
+ 0 - 0
tauri/examples/communication/dist/__tauri.js


Plik diff jest za duży
+ 720 - 39
tauri/examples/communication/src-tauri/Cargo.lock


+ 1 - 1
tauri/examples/communication/src-tauri/src/main.rs

@@ -13,7 +13,7 @@ struct Reply {
 }
 
 fn main() {
-  tauri::AppBuilder::<tauri::flavors::Official>::new()
+  tauri::AppBuilder::<tauri::flavors::Wry>::new()
     .setup(|webview, _source| async move {
       let mut webview = webview.clone();
       tauri::event::listen(String::from("js-event"), move |msg| {

+ 1 - 1
tauri/examples/communication/src-tauri/tauri.conf.json

@@ -43,7 +43,7 @@
       }
     },
     "embeddedServer": {
-      "active": false
+      "active": true
     },
     "bundle": {
       "active": true,

+ 22 - 22
tauri/src/app.rs

@@ -1,4 +1,4 @@
-use crate::Webview;
+use crate::ApplicationExt;
 use futures::future::BoxFuture;
 
 mod runner;
@@ -7,16 +7,16 @@ type InvokeHandler<W> = dyn Fn(W, String) -> BoxFuture<'static, Result<(), Strin
 type Setup<W> = dyn Fn(W, String) -> BoxFuture<'static, ()> + Send + Sync;
 
 /// The application runner.
-pub struct App<W: Webview> {
+pub struct App<A: ApplicationExt> {
   /// The JS message handler.
-  invoke_handler: Option<Box<InvokeHandler<W>>>,
+  invoke_handler: Option<Box<InvokeHandler<A::Dispatcher>>>,
   /// The setup callback, invoked when the webview is ready.
-  setup: Option<Box<Setup<W>>>,
+  setup: Option<Box<Setup<A::Dispatcher>>>,
   /// The HTML of the splashscreen to render.
   splashscreen_html: Option<String>,
 }
 
-impl<W: Webview + 'static> App<W> {
+impl<A: ApplicationExt + 'static> App<A> {
   /// Runs the app until it finishes.
   pub fn run(self) {
     runner::run(self).expect("Failed to build webview");
@@ -27,11 +27,11 @@ impl<W: Webview + 'static> App<W> {
   /// The message is considered consumed if the handler exists and returns an Ok Result.
   pub(crate) async fn run_invoke_handler(
     &self,
-    webview: &mut W,
+    dispatcher: &mut A::Dispatcher,
     arg: &str,
   ) -> Result<bool, String> {
     if let Some(ref invoke_handler) = self.invoke_handler {
-      let fut = invoke_handler(webview.clone(), arg.to_string());
+      let fut = invoke_handler(dispatcher.clone(), arg.to_string());
       fut.await.map(|_| true)
     } else {
       Ok(false)
@@ -39,9 +39,9 @@ impl<W: Webview + 'static> App<W> {
   }
 
   /// Runs the setup callback if defined.
-  pub(crate) async fn run_setup(&self, webview: &mut W, source: String) {
+  pub(crate) async fn run_setup(&self, dispatcher: &mut A::Dispatcher, source: String) {
     if let Some(ref setup) = self.setup {
-      let fut = setup(webview.clone(), source);
+      let fut = setup(dispatcher.clone(), source);
       fut.await;
     }
   }
@@ -54,16 +54,16 @@ impl<W: Webview + 'static> App<W> {
 
 /// The App builder.
 #[derive(Default)]
-pub struct AppBuilder<W: Webview> {
+pub struct AppBuilder<A: ApplicationExt> {
   /// The JS message handler.
-  invoke_handler: Option<Box<InvokeHandler<W>>>,
+  invoke_handler: Option<Box<InvokeHandler<A::Dispatcher>>>,
   /// The setup callback, invoked when the webview is ready.
-  setup: Option<Box<Setup<W>>>,
+  setup: Option<Box<Setup<A::Dispatcher>>>,
   /// The HTML of the splashscreen to render.
   splashscreen_html: Option<String>,
 }
 
-impl<W: Webview + 'static> AppBuilder<W> {
+impl<A: ApplicationExt + 'static> AppBuilder<A> {
   /// Creates a new App builder.
   pub fn new() -> Self {
     Self {
@@ -76,13 +76,13 @@ impl<W: Webview + 'static> AppBuilder<W> {
   /// Defines the JS message handler callback.
   pub fn invoke_handler<
     T: futures::Future<Output = Result<(), String>> + Send + Sync + 'static,
-    F: Fn(W, String) -> T + Send + Sync + 'static,
+    F: Fn(A::Dispatcher, String) -> T + Send + Sync + 'static,
   >(
     mut self,
     invoke_handler: F,
   ) -> Self {
-    self.invoke_handler = Some(Box::new(move |webview, arg| {
-      Box::pin(invoke_handler(webview, arg))
+    self.invoke_handler = Some(Box::new(move |dispatcher, arg| {
+      Box::pin(invoke_handler(dispatcher, arg))
     }));
     self
   }
@@ -90,13 +90,13 @@ impl<W: Webview + 'static> AppBuilder<W> {
   /// Defines the setup callback.
   pub fn setup<
     T: futures::Future<Output = ()> + Send + Sync + 'static,
-    F: Fn(W, String) -> T + Send + Sync + 'static,
+    F: Fn(A::Dispatcher, String) -> T + Send + Sync + 'static,
   >(
     mut self,
     setup: F,
   ) -> Self {
-    self.setup = Some(Box::new(move |webview, source| {
-      Box::pin(setup(webview, source))
+    self.setup = Some(Box::new(move |dispatcher, source| {
+      Box::pin(setup(dispatcher, source))
     }));
     self
   }
@@ -110,14 +110,14 @@ impl<W: Webview + 'static> AppBuilder<W> {
   /// Adds a plugin to the runtime.
   pub fn plugin(
     self,
-    plugin: impl crate::plugin::Plugin<W> + Send + Sync + Sync + 'static,
+    plugin: impl crate::plugin::Plugin<A::Dispatcher> + Send + Sync + Sync + 'static,
   ) -> Self {
-    crate::async_runtime::block_on(crate::plugin::register(W::plugin_store(), plugin));
+    crate::async_runtime::block_on(crate::plugin::register(A::plugin_store(), plugin));
     self
   }
 
   /// Builds the App.
-  pub fn build(self) -> App<W> {
+  pub fn build(self) -> App<A> {
     App {
       invoke_handler: self.invoke_handler,
       setup: self.setup,

+ 104 - 131
tauri/src/app/runner.rs

@@ -1,12 +1,9 @@
-use std::{
-  path::Path,
-  sync::{
-    atomic::{AtomicBool, Ordering},
-    Arc,
-  },
+use std::sync::{
+  atomic::{AtomicBool, Ordering},
+  Arc,
 };
 
-use crate::{SizeHint, Webview, WebviewBuilder};
+use crate::{ApplicationDispatcherExt, ApplicationExt, WebviewBuilderExt, WindowBuilderExt};
 
 use super::App;
 #[cfg(embedded_server)]
@@ -20,7 +17,7 @@ enum Content<T> {
 }
 
 /// Main entry point for running the Webview
-pub(crate) fn run<W: Webview + 'static>(application: App<W>) -> crate::Result<()> {
+pub(crate) fn run<A: ApplicationExt + 'static>(application: App<A>) -> crate::Result<()> {
   // setup the content using the config struct depending on the compile target
   let main_content = setup_content()?;
 
@@ -46,11 +43,11 @@ pub(crate) fn run<W: Webview + 'static>(application: App<W>) -> crate::Result<()
   };
 
   // build the webview
-  let mut webview = build_webview(application, main_content, splashscreen_content)?;
+  let (webview_application, mut dispatcher) =
+    build_webview(application, main_content, splashscreen_content)?;
 
-  let mut webview_ = webview.clone();
   crate::async_runtime::spawn(async move {
-    crate::plugin::created(W::plugin_store(), &mut webview_).await
+    crate::plugin::created(A::plugin_store(), &mut dispatcher).await
   });
 
   // spawn the embedded server on our server url
@@ -62,7 +59,7 @@ pub(crate) fn run<W: Webview + 'static>(application: App<W>) -> crate::Result<()
   spawn_updater();
 
   // run the webview
-  webview.run();
+  webview_application.run();
 
   Ok(())
 }
@@ -103,7 +100,7 @@ fn setup_content() -> crate::Result<Content<String>> {
     Ok(Content::Url(config.build.dev_path.clone()))
   } else {
     let dev_dir = &config.build.dev_path;
-    let dev_path = Path::new(dev_dir).join("index.tauri.html");
+    let dev_path = std::path::Path::new(dev_dir).join("index.tauri.html");
     if !dev_path.exists() {
       panic!(
         "Couldn't find 'index.tauri.html' inside {}; did you forget to run 'tauri dev'?",
@@ -240,21 +237,17 @@ pub fn init() -> String {
 }
 
 // build the webview struct
-fn build_webview<W: Webview + 'static>(
-  application: App<W>,
+fn build_webview<A: ApplicationExt + 'static>(
+  application: App<A>,
   content: Content<String>,
   splashscreen_content: Option<Content<String>>,
-) -> crate::Result<W> {
+) -> crate::Result<(A, A::Dispatcher)> {
   let config = get()?;
-  let debug = cfg!(debug_assertions);
+  // TODO let debug = cfg!(debug_assertions);
   // get properties from config struct
-  let width = config.tauri.window.width;
-  let height = config.tauri.window.height;
-  let resizable = if config.tauri.window.resizable {
-    SizeHint::NONE
-  } else {
-    SizeHint::FIXED
-  };
+  // TODO let width = config.tauri.window.width;
+  // TODO let height = config.tauri.window.height;
+  let resizable = config.tauri.window.resizable;
   // let fullscreen = config.tauri.window.fullscreen;
   let title = config.tauri.window.title.clone();
 
@@ -275,114 +268,112 @@ fn build_webview<W: Webview + 'static>(
       {tauri_init}
       {event_init}
       if (window.__TAURI_INVOKE_HANDLER__) {{
-        window.__TAURI_INVOKE_HANDLER__({{ cmd: "__initialized" }})
+        window.__TAURI_INVOKE_HANDLER__(JSON.stringify({{ cmd: "__initialized" }}))
       }} else {{
         window.addEventListener('DOMContentLoaded', function () {{
-          window.__TAURI_INVOKE_HANDLER__({{ cmd: "__initialized" }})
+          window.__TAURI_INVOKE_HANDLER__(JSON.stringify({{ cmd: "__initialized" }}))
         }})
       }}
       {plugin_init}
     "#,
     tauri_init = include_str!(concat!(env!("OUT_DIR"), "/__tauri.js")),
     event_init = init(),
-    plugin_init = crate::async_runtime::block_on(crate::plugin::init_script(W::plugin_store()))
+    plugin_init = crate::async_runtime::block_on(crate::plugin::init_script(A::plugin_store()))
   );
 
-  let mut webview = W::Builder::new()
-    .init(&init)
-    .title(&title)
-    .width(width as usize)
-    .height(height as usize)
-    .resizable(resizable)
-    .debug(debug)
-    .url(&url)
-    .finish();
-  // TODO waiting for webview window API
-  // webview.set_fullscreen(fullscreen);
-
-  if has_splashscreen {
-    let env_var = envmnt::get_or("TAURI_DIR", "../dist");
-    let path = Path::new(&env_var);
-    let contents = std::fs::read_to_string(path.join("/tauri.js"))?;
-    // inject the tauri.js entry point
-    webview.dispatch(move |_webview| _webview.eval(&contents));
-  }
-
-  let w = webview.clone();
   let application = Arc::new(application);
 
-  webview.bind("__TAURI_INVOKE_HANDLER__", move |_, arg| {
-    let arg = arg.to_string();
-    let application = application.clone();
-    let mut w = w.clone();
-    let content_url = content_url.to_string();
-    let initialized_splashscreen = initialized_splashscreen.clone();
-
-    crate::async_runtime::spawn(async move {
-      let arg = format_arg(&arg);
-
-      if arg == r#"{"cmd":"__initialized"}"# {
-        let source = if has_splashscreen && !initialized_splashscreen.load(Ordering::Relaxed) {
-          initialized_splashscreen.swap(true, Ordering::Relaxed);
-          "splashscreen"
+  let mut webview_application = A::new()?;
+
+  let main_window =
+    webview_application.create_window(A::WindowBuilder::new().resizable(resizable).title(title))?;
+
+  let dispatcher = webview_application.dispatcher(&main_window);
+
+  let tauri_invoke_handler = crate::Callback::<A::Dispatcher> {
+    name: "__TAURI_INVOKE_HANDLER__".to_string(),
+    function: Box::new(move |dispatcher, _, arg| {
+      let arg = arg.into_iter().next().unwrap_or_else(String::new);
+      let application = application.clone();
+      let mut dispatcher = dispatcher.clone();
+      let content_url = content_url.to_string();
+      let initialized_splashscreen = initialized_splashscreen.clone();
+
+      crate::async_runtime::spawn(async move {
+        if arg == r#"{"cmd":"__initialized"}"# {
+          let source = if has_splashscreen && !initialized_splashscreen.load(Ordering::Relaxed) {
+            initialized_splashscreen.swap(true, Ordering::Relaxed);
+            "splashscreen"
+          } else {
+            "window-1"
+          };
+          application
+            .run_setup(&mut dispatcher, source.to_string())
+            .await;
+          if source == "window-1" {
+            crate::plugin::ready(A::plugin_store(), &mut dispatcher).await;
+          }
+        } else if arg == r#"{"cmd":"closeSplashscreen"}"# {
+          dispatcher.eval(&format!(r#"window.location.href = "{}""#, content_url));
         } else {
-          "window-1"
-        };
-        application.run_setup(&mut w, source.to_string()).await;
-        if source == "window-1" {
-          crate::plugin::ready(W::plugin_store(), &mut w).await;
-        }
-      } else if arg == r#"{"cmd":"closeSplashscreen"}"# {
-        w.dispatch(move |w| {
-          w.eval(&format!(r#"window.location.href = "{}""#, content_url));
-        });
-      } else {
-        let mut endpoint_handle = crate::endpoints::handle(&mut w, &arg)
-          .await
-          .map_err(|e| e.to_string());
-        if let Err(ref tauri_handle_error) = endpoint_handle {
-          if tauri_handle_error.contains("unknown variant") {
-            let error = match application.run_invoke_handler(&mut w, &arg).await {
-              Ok(handled) => {
-                if handled {
-                  String::from("")
-                } else {
-                  tauri_handle_error.to_string()
+          let mut endpoint_handle = crate::endpoints::handle(&mut dispatcher, &arg)
+            .await
+            .map_err(|e| e.to_string());
+          if let Err(ref tauri_handle_error) = endpoint_handle {
+            if tauri_handle_error.contains("unknown variant") {
+              let error = match application.run_invoke_handler(&mut dispatcher, &arg).await {
+                Ok(handled) => {
+                  if handled {
+                    String::from("")
+                  } else {
+                    tauri_handle_error.to_string()
+                  }
                 }
-              }
-              Err(e) => e,
-            };
-            endpoint_handle = Err(error);
+                Err(e) => e,
+              };
+              endpoint_handle = Err(error);
+            }
           }
-        }
-        if let Err(ref app_handle_error) = endpoint_handle {
-          if app_handle_error.contains("unknown variant") {
-            let error = match crate::plugin::extend_api(W::plugin_store(), &mut w, &arg).await {
-              Ok(handled) => {
-                if handled {
-                  String::from("")
-                } else {
-                  app_handle_error.to_string()
-                }
-              }
-              Err(e) => e,
-            };
-            endpoint_handle = Err(error);
+          if let Err(ref app_handle_error) = endpoint_handle {
+            if app_handle_error.contains("unknown variant") {
+              let error =
+                match crate::plugin::extend_api(A::plugin_store(), &mut dispatcher, &arg).await {
+                  Ok(handled) => {
+                    if handled {
+                      String::from("")
+                    } else {
+                      app_handle_error.to_string()
+                    }
+                  }
+                  Err(e) => e,
+                };
+              endpoint_handle = Err(error);
+            }
           }
-        }
-        endpoint_handle = endpoint_handle.map_err(|e| e.replace("'", "\\'"));
-        if let Err(handler_error_message) = endpoint_handle {
-          if !handler_error_message.is_empty() {
-            let _ = w.dispatch(move |w| {
-              w.eval(&get_api_error_message(&arg, handler_error_message));
-            });
+          endpoint_handle = endpoint_handle.map_err(|e| e.replace("'", "\\'"));
+          if let Err(handler_error_message) = endpoint_handle {
+            if !handler_error_message.is_empty() {
+              dispatcher.eval(&get_api_error_message(&arg, handler_error_message));
+            }
           }
         }
-      }
-    });
-  });
+      });
+      0
+    }),
+  };
+
+  webview_application.create_webview(
+    A::WebviewBuilder::new()
+      .url(url)
+      .initialization_script(&init),
+    main_window,
+    vec![tauri_invoke_handler],
+  )?;
+
+  // TODO waiting for webview window API
+  // webview.set_fullscreen(fullscreen);
 
-  Ok(webview)
+  Ok((webview_application, dispatcher))
 }
 
 // Formats an invoke handler error message to print to console.error
@@ -394,15 +385,6 @@ fn get_api_error_message(arg: &str, handler_error_message: String) -> String {
   )
 }
 
-// Transform `[payload]` to `payload`
-fn format_arg(arg: &str) -> String {
-  arg
-    .chars()
-    .skip(1)
-    .take(arg.chars().count() - 2)
-    .collect::<String>()
-}
-
 #[cfg(test)]
 mod test {
   use super::Content;
@@ -501,13 +483,4 @@ mod test {
       }
     }
   }
-
-  #[test]
-  fn test_format_arg() {
-    let input = &["[payload]", "[påyløad]"];
-    let expected = &[String::from("payload"), String::from("påyløad")];
-    for (i, e) in input.iter().zip(expected) {
-      assert_eq!(&super::format_arg(i), e);
-    }
-  }
 }

+ 70 - 64
tauri/src/endpoints.rs

@@ -16,10 +16,13 @@ mod http;
 #[cfg(notification)]
 mod notification;
 
-use crate::Webview;
+use crate::{ApplicationDispatcherExt, Event};
 
 #[allow(unused_variables)]
-pub(crate) async fn handle<W: Webview + 'static>(webview: &mut W, arg: &str) -> crate::Result<()> {
+pub(crate) async fn handle<D: ApplicationDispatcherExt + 'static>(
+  dispatcher: &mut D,
+  arg: &str,
+) -> crate::Result<()> {
   use cmd::Cmd::*;
   match serde_json::from_str(arg) {
     Err(e) => Err(e.into()),
@@ -32,9 +35,9 @@ pub(crate) async fn handle<W: Webview + 'static>(webview: &mut W, arg: &str) ->
           error,
         } => {
           #[cfg(read_text_file)]
-          file_system::read_text_file(webview, path, options, callback, error).await;
+          file_system::read_text_file(dispatcher, path, options, callback, error).await;
           #[cfg(not(read_text_file))]
-          allowlist_error(webview, error, "readTextFile");
+          allowlist_error(dispatcher, error, "readTextFile");
         }
         ReadBinaryFile {
           path,
@@ -43,9 +46,9 @@ pub(crate) async fn handle<W: Webview + 'static>(webview: &mut W, arg: &str) ->
           error,
         } => {
           #[cfg(read_binary_file)]
-          file_system::read_binary_file(webview, path, options, callback, error).await;
+          file_system::read_binary_file(dispatcher, path, options, callback, error).await;
           #[cfg(not(read_binary_file))]
-          allowlist_error(webview, error, "readBinaryFile");
+          allowlist_error(dispatcher, error, "readBinaryFile");
         }
         WriteFile {
           path,
@@ -55,9 +58,9 @@ pub(crate) async fn handle<W: Webview + 'static>(webview: &mut W, arg: &str) ->
           error,
         } => {
           #[cfg(write_file)]
-          file_system::write_file(webview, path, contents, options, callback, error).await;
+          file_system::write_file(dispatcher, path, contents, options, callback, error).await;
           #[cfg(not(write_file))]
-          allowlist_error(webview, error, "writeFile");
+          allowlist_error(dispatcher, error, "writeFile");
         }
         WriteBinaryFile {
           path,
@@ -67,9 +70,10 @@ pub(crate) async fn handle<W: Webview + 'static>(webview: &mut W, arg: &str) ->
           error,
         } => {
           #[cfg(write_binary_file)]
-          file_system::write_binary_file(webview, path, contents, options, callback, error).await;
+          file_system::write_binary_file(dispatcher, path, contents, options, callback, error)
+            .await;
           #[cfg(not(write_binary_file))]
-          allowlist_error(webview, error, "writeBinaryFile");
+          allowlist_error(dispatcher, error, "writeBinaryFile");
         }
         ReadDir {
           path,
@@ -78,9 +82,9 @@ pub(crate) async fn handle<W: Webview + 'static>(webview: &mut W, arg: &str) ->
           error,
         } => {
           #[cfg(read_dir)]
-          file_system::read_dir(webview, path, options, callback, error).await;
+          file_system::read_dir(dispatcher, path, options, callback, error).await;
           #[cfg(not(read_dir))]
-          allowlist_error(webview, error, "readDir");
+          allowlist_error(dispatcher, error, "readDir");
         }
         CopyFile {
           source,
@@ -90,9 +94,9 @@ pub(crate) async fn handle<W: Webview + 'static>(webview: &mut W, arg: &str) ->
           error,
         } => {
           #[cfg(copy_file)]
-          file_system::copy_file(webview, source, destination, options, callback, error).await;
+          file_system::copy_file(dispatcher, source, destination, options, callback, error).await;
           #[cfg(not(copy_file))]
-          allowlist_error(webview, error, "copyFile");
+          allowlist_error(dispatcher, error, "copyFile");
         }
         CreateDir {
           path,
@@ -101,9 +105,9 @@ pub(crate) async fn handle<W: Webview + 'static>(webview: &mut W, arg: &str) ->
           error,
         } => {
           #[cfg(create_dir)]
-          file_system::create_dir(webview, path, options, callback, error).await;
+          file_system::create_dir(dispatcher, path, options, callback, error).await;
           #[cfg(not(create_dir))]
-          allowlist_error(webview, error, "createDir");
+          allowlist_error(dispatcher, error, "createDir");
         }
         RemoveDir {
           path,
@@ -112,9 +116,9 @@ pub(crate) async fn handle<W: Webview + 'static>(webview: &mut W, arg: &str) ->
           error,
         } => {
           #[cfg(remove_dir)]
-          file_system::remove_dir(webview, path, options, callback, error).await;
+          file_system::remove_dir(dispatcher, path, options, callback, error).await;
           #[cfg(not(remove_dir))]
-          allowlist_error(webview, error, "removeDir");
+          allowlist_error(dispatcher, error, "removeDir");
         }
         RemoveFile {
           path,
@@ -123,9 +127,9 @@ pub(crate) async fn handle<W: Webview + 'static>(webview: &mut W, arg: &str) ->
           error,
         } => {
           #[cfg(remove_file)]
-          file_system::remove_file(webview, path, options, callback, error).await;
+          file_system::remove_file(dispatcher, path, options, callback, error).await;
           #[cfg(not(remove_file))]
-          allowlist_error(webview, error, "removeFile");
+          allowlist_error(dispatcher, error, "removeFile");
         }
         RenameFile {
           old_path,
@@ -135,9 +139,9 @@ pub(crate) async fn handle<W: Webview + 'static>(webview: &mut W, arg: &str) ->
           error,
         } => {
           #[cfg(rename_file)]
-          file_system::rename_file(webview, old_path, new_path, options, callback, error).await;
+          file_system::rename_file(dispatcher, old_path, new_path, options, callback, error).await;
           #[cfg(not(rename_file))]
-          allowlist_error(webview, error, "renameFile");
+          allowlist_error(dispatcher, error, "renameFile");
         }
         ResolvePath {
           path,
@@ -146,17 +150,18 @@ pub(crate) async fn handle<W: Webview + 'static>(webview: &mut W, arg: &str) ->
           error,
         } => {
           #[cfg(path_api)]
-          path::resolve_path(webview, path, directory, callback, error).await;
+          path::resolve_path(dispatcher, path, directory, callback, error).await;
           #[cfg(not(path_api))]
-          allowlist_error(webview, error, "pathApi");
+          allowlist_error(dispatcher, error, "pathApi");
         }
         SetTitle { title } => {
-          #[cfg(set_title)]
+          // TODO
+          /*#[cfg(set_title)]
           webview.dispatch(move |w| {
             w.set_title(&title);
-          });
+          });*/
           #[cfg(not(set_title))]
-          throw_allowlist_error(webview, "title");
+          throw_allowlist_error(dispatcher, "title");
         }
         Execute {
           command,
@@ -165,22 +170,22 @@ pub(crate) async fn handle<W: Webview + 'static>(webview: &mut W, arg: &str) ->
           error,
         } => {
           #[cfg(execute)]
-          crate::call(webview, command, args, callback, error).await;
+          crate::call(dispatcher, command, args, callback, error).await;
           #[cfg(not(execute))]
-          throw_allowlist_error(webview, "execute");
+          throw_allowlist_error(dispatcher, "execute");
         }
         Open { uri } => {
           #[cfg(open)]
           browser::open(uri);
           #[cfg(not(open))]
-          throw_allowlist_error(webview, "open");
+          throw_allowlist_error(dispatcher, "open");
         }
         ValidateSalt {
           salt,
           callback,
           error,
         } => {
-          salt::validate(webview, salt, callback, error)?;
+          salt::validate(dispatcher, salt, callback, error)?;
         }
         Listen {
           event,
@@ -190,18 +195,16 @@ pub(crate) async fn handle<W: Webview + 'static>(webview: &mut W, arg: &str) ->
           #[cfg(event)]
           {
             let js_string = event::listen_fn(event, handler, once)?;
-            webview.dispatch(move |w| {
-              w.eval(&js_string);
-            });
+            dispatcher.eval(&js_string);
           }
           #[cfg(not(event))]
-          throw_allowlist_error(webview, "event");
+          throw_allowlist_error(dispatcher, "event");
         }
         Emit { event, payload } => {
           #[cfg(event)]
           crate::event::on_event(event, payload);
           #[cfg(not(event))]
-          throw_allowlist_error(webview, "event");
+          throw_allowlist_error(dispatcher, "event");
         }
         OpenDialog {
           options,
@@ -209,9 +212,9 @@ pub(crate) async fn handle<W: Webview + 'static>(webview: &mut W, arg: &str) ->
           error,
         } => {
           #[cfg(open_dialog)]
-          dialog::open(webview, options, callback, error)?;
+          dialog::open(dispatcher, options, callback, error)?;
           #[cfg(not(open_dialog))]
-          allowlist_error(webview, error, "title");
+          allowlist_error(dispatcher, error, "title");
         }
         SaveDialog {
           options,
@@ -219,9 +222,9 @@ pub(crate) async fn handle<W: Webview + 'static>(webview: &mut W, arg: &str) ->
           error,
         } => {
           #[cfg(save_dialog)]
-          dialog::save(webview, options, callback, error)?;
+          dialog::save(dispatcher, options, callback, error)?;
           #[cfg(not(save_dialog))]
-          throw_allowlist_error(webview, "saveDialog");
+          throw_allowlist_error(dispatcher, "saveDialog");
         }
         MessageDialog { message } => {
           let exe = std::env::current_exe()?;
@@ -229,8 +232,11 @@ pub(crate) async fn handle<W: Webview + 'static>(webview: &mut W, arg: &str) ->
           let app_name = exe
             .file_name()
             .expect("failed to get exe filename")
-            .to_string_lossy();
-          dialog::message(app_name.to_string(), message);
+            .to_string_lossy()
+            .to_string();
+          dispatcher.send_event(Event::Run(Box::new(move || {
+            dialog::message(app_name, message);
+          })));
         }
         AskDialog {
           title,
@@ -240,7 +246,7 @@ pub(crate) async fn handle<W: Webview + 'static>(webview: &mut W, arg: &str) ->
         } => {
           let exe = std::env::current_exe()?;
           dialog::ask(
-            webview,
+            dispatcher,
             title.unwrap_or_else(|| {
               let exe_dir = exe.parent().expect("failed to get exe directory");
               exe
@@ -260,9 +266,9 @@ pub(crate) async fn handle<W: Webview + 'static>(webview: &mut W, arg: &str) ->
           error,
         } => {
           #[cfg(http_request)]
-          http::make_request(webview, *options, callback, error).await;
+          http::make_request(dispatcher, *options, callback, error).await;
           #[cfg(not(http_request))]
-          allowlist_error(webview, error, "httpRequest");
+          allowlist_error(dispatcher, error, "httpRequest");
         }
         #[cfg(assets)]
         LoadAsset {
@@ -271,12 +277,12 @@ pub(crate) async fn handle<W: Webview + 'static>(webview: &mut W, arg: &str) ->
           callback,
           error,
         } => {
-          asset::load(webview, asset, asset_type, callback, error).await;
+          asset::load(dispatcher, asset, asset_type, callback, error).await;
         }
         CliMatches { callback, error } => {
           #[cfg(cli)]
           crate::execute_promise(
-            webview,
+            dispatcher,
             async move {
               match crate::cli::get_matches() {
                 Some(matches) => Ok(matches),
@@ -289,7 +295,7 @@ pub(crate) async fn handle<W: Webview + 'static>(webview: &mut W, arg: &str) ->
           .await;
           #[cfg(not(cli))]
           api_error(
-            webview,
+            dispatcher,
             error,
             "CLI definition not set under tauri.conf.json > tauri > cli (https://tauri.studio/docs/api/config#tauri.cli)",
           );
@@ -300,21 +306,21 @@ pub(crate) async fn handle<W: Webview + 'static>(webview: &mut W, arg: &str) ->
           error,
         } => {
           #[cfg(notification)]
-          notification::send(webview, options, callback, error).await;
+          notification::send(dispatcher, options, callback, error).await;
           #[cfg(not(notification))]
-          allowlist_error(webview, error, "notification");
+          allowlist_error(dispatcher, error, "notification");
         }
         IsNotificationPermissionGranted { callback, error } => {
           #[cfg(notification)]
-          notification::is_permission_granted(webview, callback, error).await;
+          notification::is_permission_granted(dispatcher, callback, error).await;
           #[cfg(not(notification))]
-          allowlist_error(webview, error, "notification");
+          allowlist_error(dispatcher, error, "notification");
         }
         RequestNotificationPermission { callback, error } => {
           #[cfg(notification)]
-          notification::request_permission(webview, callback, error)?;
+          notification::request_permission(dispatcher, callback, error)?;
           #[cfg(not(notification))]
-          allowlist_error(webview, error, "notification");
+          allowlist_error(dispatcher, error, "notification");
         }
       }
       Ok(())
@@ -323,17 +329,19 @@ pub(crate) async fn handle<W: Webview + 'static>(webview: &mut W, arg: &str) ->
 }
 
 #[allow(dead_code)]
-fn api_error<W: Webview>(webview: &mut W, error_fn: String, message: &str) {
+fn api_error<D: ApplicationDispatcherExt>(dispatcher: &mut D, error_fn: String, message: &str) {
   let reject_code = tauri_api::rpc::format_callback(error_fn, message);
-  let _ = webview.dispatch(move |w| {
-    w.eval(&reject_code);
-  });
+  let _ = dispatcher.eval(&reject_code);
 }
 
 #[allow(dead_code)]
-fn allowlist_error<W: Webview>(webview: &mut W, error_fn: String, allowlist_key: &str) {
+fn allowlist_error<D: ApplicationDispatcherExt>(
+  dispatcher: &mut D,
+  error_fn: String,
+  allowlist_key: &str,
+) {
   api_error(
-    webview,
+    dispatcher,
     error_fn,
     &format!(
       "{}' not on the allowlist (https://tauri.studio/docs/api/config#tauri.allowlist)",
@@ -343,14 +351,12 @@ fn allowlist_error<W: Webview>(webview: &mut W, error_fn: String, allowlist_key:
 }
 
 #[allow(dead_code)]
-fn throw_allowlist_error<W: Webview>(webview: &mut W, allowlist_key: &str) {
+fn throw_allowlist_error<D: ApplicationDispatcherExt>(dispatcher: &mut D, allowlist_key: &str) {
   let reject_code = format!(
     r#"throw new Error("'{}' not on the allowlist")"#,
     allowlist_key
   );
-  let _ = webview.dispatch(move |w| {
-    w.eval(&reject_code);
-  });
+  let _ = dispatcher.eval(&reject_code);
 }
 
 #[cfg(test)]

+ 16 - 18
tauri/src/endpoints/asset.rs

@@ -1,17 +1,17 @@
-use crate::Webview;
+use crate::ApplicationDispatcherExt;
 use std::path::PathBuf;
 
 #[allow(clippy::option_env_unwrap)]
-pub async fn load<W: Webview + 'static>(
-  webview: &mut W,
+pub async fn load<D: ApplicationDispatcherExt + 'static>(
+  dispatcher: &mut D,
   asset: String,
   asset_type: String,
   callback: String,
   error: String,
 ) {
-  let mut webview_mut = webview.clone();
+  let mut dispatcher_ = dispatcher.clone();
   crate::execute_promise(
-    webview,
+    dispatcher,
     async move {
       let mut path = PathBuf::from(if asset.starts_with('/') {
         asset.replacen("/", "", 1)
@@ -68,12 +68,11 @@ pub async fn load<W: Webview + 'static>(
         ))
       } else {
         let asset_bytes = read_asset.expect("Failed to read asset type");
-        webview_mut.dispatch(move |webview_ref| {
-          let asset_str =
-            std::str::from_utf8(&asset_bytes).expect("failed to convert asset bytes to u8 slice");
-          if asset_type == "stylesheet" {
-            webview_ref.eval(&format!(
-              r#"
+        let asset_str =
+          std::str::from_utf8(&asset_bytes).expect("failed to convert asset bytes to u8 slice");
+        if asset_type == "stylesheet" {
+          dispatcher_.eval(&format!(
+            r#"
                 (function (content) {{
                   var css = document.createElement('style')
                   css.type = 'text/css'
@@ -84,13 +83,12 @@ pub async fn load<W: Webview + 'static>(
                   document.getElementsByTagName("head")[0].appendChild(css);
                 }})(`{css}`)
               "#,
-              // Escape octal sequences, which aren't allowed in template literals
-              css = asset_str.replace("\\", "\\\\").as_str()
-            ));
-          } else {
-            webview_ref.eval(asset_str);
-          }
-        });
+            // Escape octal sequences, which aren't allowed in template literals
+            css = asset_str.replace("\\", "\\\\").as_str()
+          ));
+        } else {
+          dispatcher_.eval(asset_str);
+        }
         Ok("Asset loaded successfully".to_string())
       }
     },

+ 18 - 20
tauri/src/endpoints/dialog.rs

@@ -3,7 +3,7 @@ use crate::api::dialog::{
   ask as ask_dialog, message as message_dialog, pick_folder, save_file, select, select_multiple,
   DialogSelection, Response,
 };
-use crate::Webview;
+use crate::ApplicationDispatcherExt;
 use serde_json::Value as JsonValue;
 
 /// maps a dialog response to a JS value to eval
@@ -18,15 +18,15 @@ fn map_response(response: Response) -> JsonValue {
 
 /// Shows an open dialog.
 #[cfg(open_dialog)]
-pub fn open<W: Webview>(
-  webview: &mut W,
+pub fn open<D: ApplicationDispatcherExt + 'static>(
+  dispatcher: &mut D,
   options: OpenDialogOptions,
   callback: String,
   error: String,
 ) -> crate::Result<()> {
   crate::execute_promise_sync(
-    webview,
-    async move {
+    dispatcher,
+    move || {
       let response = if options.multiple {
         select_multiple(options.filter, options.default_path)
       } else if options.directory {
@@ -38,24 +38,24 @@ pub fn open<W: Webview>(
     },
     callback,
     error,
-  )?;
+  );
   Ok(())
 }
 
 /// Shows a save dialog.
 #[cfg(save_dialog)]
-pub fn save<W: Webview>(
-  webview: &mut W,
+pub fn save<D: ApplicationDispatcherExt + 'static>(
+  dispatcher: &mut D,
   options: SaveDialogOptions,
   callback: String,
   error: String,
 ) -> crate::Result<()> {
   crate::execute_promise_sync(
-    webview,
-    async move { save_file(options.filter, options.default_path).map(map_response) },
+    dispatcher,
+    move || save_file(options.filter, options.default_path).map(map_response),
     callback,
     error,
-  )?;
+  );
   Ok(())
 }
 
@@ -65,23 +65,21 @@ pub fn message(title: String, message: String) {
 }
 
 /// Shows a dialog with a yes/no question.
-pub fn ask<W: Webview>(
-  webview: &mut W,
+pub fn ask<D: ApplicationDispatcherExt + 'static>(
+  dispatcher: &mut D,
   title: String,
   message: String,
   callback: String,
   error: String,
 ) -> crate::Result<()> {
   crate::execute_promise_sync(
-    webview,
-    async move {
-      match ask_dialog(message, title) {
-        DialogSelection::Yes => Ok(true),
-        _ => Ok(false),
-      }
+    dispatcher,
+    move || match ask_dialog(message, title) {
+      DialogSelection::Yes => Ok(true),
+      _ => Ok(false),
     },
     callback,
     error,
-  )?;
+  );
   Ok(())
 }

+ 32 - 32
tauri/src/endpoints/file_system.rs

@@ -1,4 +1,4 @@
-use crate::Webview;
+use crate::ApplicationDispatcherExt;
 
 use tauri_api::dir;
 use tauri_api::file;
@@ -13,15 +13,15 @@ use super::cmd::{DirOperationOptions, FileOperationOptions};
 
 /// Reads a directory.
 #[cfg(read_dir)]
-pub async fn read_dir<W: Webview>(
-  webview: &mut W,
+pub async fn read_dir<D: ApplicationDispatcherExt>(
+  dispatcher: &mut D,
   path: PathBuf,
   options: Option<DirOperationOptions>,
   callback: String,
   error: String,
 ) {
   crate::execute_promise(
-    webview,
+    dispatcher,
     async move {
       let (recursive, dir) = if let Some(options_value) = options {
         (options_value.recursive, options_value.dir)
@@ -38,8 +38,8 @@ pub async fn read_dir<W: Webview>(
 
 /// Copies a file.
 #[cfg(copy_file)]
-pub async fn copy_file<W: Webview>(
-  webview: &mut W,
+pub async fn copy_file<D: ApplicationDispatcherExt>(
+  dispatcher: &mut D,
   source: PathBuf,
   destination: PathBuf,
   options: Option<FileOperationOptions>,
@@ -47,7 +47,7 @@ pub async fn copy_file<W: Webview>(
   error: String,
 ) {
   crate::execute_promise(
-    webview,
+    dispatcher,
     async move {
       let (src, dest) = match options.and_then(|o| o.dir) {
         Some(dir) => (
@@ -66,15 +66,15 @@ pub async fn copy_file<W: Webview>(
 
 /// Creates a directory.
 #[cfg(create_dir)]
-pub async fn create_dir<W: Webview>(
-  webview: &mut W,
+pub async fn create_dir<D: ApplicationDispatcherExt>(
+  dispatcher: &mut D,
   path: PathBuf,
   options: Option<DirOperationOptions>,
   callback: String,
   error: String,
 ) {
   crate::execute_promise(
-    webview,
+    dispatcher,
     async move {
       let (recursive, dir) = if let Some(options_value) = options {
         (options_value.recursive, options_value.dir)
@@ -98,15 +98,15 @@ pub async fn create_dir<W: Webview>(
 
 /// Removes a directory.
 #[cfg(remove_dir)]
-pub async fn remove_dir<W: Webview>(
-  webview: &mut W,
+pub async fn remove_dir<D: ApplicationDispatcherExt>(
+  dispatcher: &mut D,
   path: PathBuf,
   options: Option<DirOperationOptions>,
   callback: String,
   error: String,
 ) {
   crate::execute_promise(
-    webview,
+    dispatcher,
     async move {
       let (recursive, dir) = if let Some(options_value) = options {
         (options_value.recursive, options_value.dir)
@@ -130,15 +130,15 @@ pub async fn remove_dir<W: Webview>(
 
 /// Removes a file
 #[cfg(remove_file)]
-pub async fn remove_file<W: Webview>(
-  webview: &mut W,
+pub async fn remove_file<D: ApplicationDispatcherExt>(
+  dispatcher: &mut D,
   path: PathBuf,
   options: Option<FileOperationOptions>,
   callback: String,
   error: String,
 ) {
   crate::execute_promise(
-    webview,
+    dispatcher,
     async move {
       let resolved_path = resolve_path(path, options.and_then(|o| o.dir))?;
       fs::remove_file(resolved_path).map_err(|e| e.into())
@@ -151,8 +151,8 @@ pub async fn remove_file<W: Webview>(
 
 /// Renames a file.
 #[cfg(rename_file)]
-pub async fn rename_file<W: Webview>(
-  webview: &mut W,
+pub async fn rename_file<D: ApplicationDispatcherExt>(
+  dispatcher: &mut D,
   old_path: PathBuf,
   new_path: PathBuf,
   options: Option<FileOperationOptions>,
@@ -160,7 +160,7 @@ pub async fn rename_file<W: Webview>(
   error: String,
 ) {
   crate::execute_promise(
-    webview,
+    dispatcher,
     async move {
       let (old, new) = match options.and_then(|o| o.dir) {
         Some(dir) => (
@@ -179,8 +179,8 @@ pub async fn rename_file<W: Webview>(
 
 /// Writes a text file.
 #[cfg(write_file)]
-pub async fn write_file<W: Webview>(
-  webview: &mut W,
+pub async fn write_file<D: ApplicationDispatcherExt>(
+  dispatcher: &mut D,
   path: PathBuf,
   contents: String,
   options: Option<FileOperationOptions>,
@@ -188,7 +188,7 @@ pub async fn write_file<W: Webview>(
   error: String,
 ) {
   crate::execute_promise(
-    webview,
+    dispatcher,
     async move {
       File::create(resolve_path(path, options.and_then(|o| o.dir))?)
         .map_err(|e| e.into())
@@ -202,8 +202,8 @@ pub async fn write_file<W: Webview>(
 
 /// Writes a binary file.
 #[cfg(write_binary_file)]
-pub async fn write_binary_file<W: Webview>(
-  webview: &mut W,
+pub async fn write_binary_file<D: ApplicationDispatcherExt>(
+  dispatcher: &mut D,
   path: PathBuf,
   contents: String,
   options: Option<FileOperationOptions>,
@@ -211,7 +211,7 @@ pub async fn write_binary_file<W: Webview>(
   error: String,
 ) {
   crate::execute_promise(
-    webview,
+    dispatcher,
     async move {
       base64::decode(contents)
         .map_err(|e| e.into())
@@ -229,15 +229,15 @@ pub async fn write_binary_file<W: Webview>(
 
 /// Reads a text file.
 #[cfg(read_text_file)]
-pub async fn read_text_file<W: Webview>(
-  webview: &mut W,
+pub async fn read_text_file<D: ApplicationDispatcherExt>(
+  dispatcher: &mut D,
   path: PathBuf,
   options: Option<FileOperationOptions>,
   callback: String,
   error: String,
 ) {
   crate::execute_promise(
-    webview,
+    dispatcher,
     async move { file::read_string(resolve_path(path, options.and_then(|o| o.dir))?) },
     callback,
     error,
@@ -247,15 +247,15 @@ pub async fn read_text_file<W: Webview>(
 
 /// Reads a binary file.
 #[cfg(read_binary_file)]
-pub async fn read_binary_file<W: Webview>(
-  webview: &mut W,
+pub async fn read_binary_file<D: ApplicationDispatcherExt>(
+  dispatcher: &mut D,
   path: PathBuf,
   options: Option<FileOperationOptions>,
   callback: String,
   error: String,
 ) {
   crate::execute_promise(
-    webview,
+    dispatcher,
     async move { file::read_binary(resolve_path(path, options.and_then(|o| o.dir))?) },
     callback,
     error,
@@ -307,7 +307,7 @@ mod test {
 
     //call write file with the path and contents.
     write_file(
-      &mut webview,
+      &mut dispatcher,
       path.clone(),
       contents.clone(),
       String::from(""),

+ 4 - 4
tauri/src/endpoints/http.rs

@@ -1,12 +1,12 @@
-use crate::Webview;
+use crate::ApplicationDispatcherExt;
 use tauri_api::http::{make_request as request, HttpRequestOptions};
 
 /// Makes an HTTP request and resolves the response to the webview
-pub async fn make_request<W: Webview>(
-  webview: &mut W,
+pub async fn make_request<D: ApplicationDispatcherExt>(
+  dispatcher: &mut D,
   options: HttpRequestOptions,
   callback: String,
   error: String,
 ) {
-  crate::execute_promise(webview, async move { request(options) }, callback, error).await;
+  crate::execute_promise(dispatcher, async move { request(options) }, callback, error).await;
 }

+ 15 - 11
tauri/src/endpoints/notification.rs

@@ -1,15 +1,15 @@
 use super::cmd::NotificationOptions;
-use crate::Webview;
+use crate::ApplicationDispatcherExt;
 use serde_json::Value as JsonValue;
 
-pub async fn send<W: Webview>(
-  webview: &mut W,
+pub async fn send<D: ApplicationDispatcherExt>(
+  dispatcher: &mut D,
   options: NotificationOptions,
   callback: String,
   error: String,
 ) {
   crate::execute_promise(
-    webview,
+    dispatcher,
     async move {
       let mut notification = tauri_api::notification::Notification::new().title(options.title);
       if let Some(body) = options.body {
@@ -27,9 +27,13 @@ pub async fn send<W: Webview>(
   .await;
 }
 
-pub async fn is_permission_granted<W: Webview>(webview: &mut W, callback: String, error: String) {
+pub async fn is_permission_granted<D: ApplicationDispatcherExt>(
+  dispatcher: &mut D,
+  callback: String,
+  error: String,
+) {
   crate::execute_promise(
-    webview,
+    dispatcher,
     async move {
       let settings = crate::settings::read_settings()?;
       if let Some(allow_notification) = settings.allow_notification {
@@ -44,14 +48,14 @@ pub async fn is_permission_granted<W: Webview>(webview: &mut W, callback: String
   .await;
 }
 
-pub fn request_permission<W: Webview>(
-  webview: &mut W,
+pub fn request_permission<D: ApplicationDispatcherExt + 'static>(
+  dispatcher: &mut D,
   callback: String,
   error: String,
 ) -> crate::Result<()> {
   crate::execute_promise_sync(
-    webview,
-    async move {
+    dispatcher,
+    move || {
       let mut settings = crate::settings::read_settings()?;
       let granted = "granted".to_string();
       let denied = "denied".to_string();
@@ -78,6 +82,6 @@ pub fn request_permission<W: Webview>(
     },
     callback,
     error,
-  )?;
+  );
   Ok(())
 }

+ 4 - 4
tauri/src/endpoints/path.rs

@@ -1,17 +1,17 @@
 #![cfg(path_api)]
-use crate::Webview;
+use crate::ApplicationDispatcherExt;
 use tauri_api::path;
 use tauri_api::path::BaseDirectory;
 
-pub async fn resolve_path<W: Webview>(
-  webview: &mut W,
+pub async fn resolve_path<D: ApplicationDispatcherExt>(
+  dispatcher: &mut D,
   path: String,
   directory: Option<BaseDirectory>,
   callback: String,
   error: String,
 ) {
   crate::execute_promise(
-    webview,
+    dispatcher,
     async move { path::resolve_path(path, directory) },
     callback,
     error,

+ 4 - 6
tauri/src/endpoints/salt.rs

@@ -1,8 +1,8 @@
-use crate::Webview;
+use crate::ApplicationDispatcherExt;
 
 /// Validates a salt.
-pub fn validate<W: Webview>(
-  webview: &mut W,
+pub fn validate<D: ApplicationDispatcherExt>(
+  dispatcher: &mut D,
   salt: String,
   callback: String,
   error: String,
@@ -13,8 +13,6 @@ pub fn validate<W: Webview>(
     Err("Invalid salt")
   };
   let callback_string = crate::api::rpc::format_callback_result(response, callback, error)?;
-  webview.dispatch(move |w| {
-    w.eval(callback_string.as_str());
-  });
+  dispatcher.eval(callback_string.as_str());
   Ok(())
 }

+ 10 - 12
tauri/src/event.rs

@@ -2,7 +2,7 @@ use std::boxed::Box;
 use std::collections::HashMap;
 use std::sync::{Arc, Mutex};
 
-use crate::Webview;
+use crate::ApplicationDispatcherExt;
 use lazy_static::lazy_static;
 use once_cell::sync::Lazy;
 use serde::Serialize;
@@ -57,8 +57,8 @@ pub fn listen<F: FnMut(Option<String>) + Send + 'static>(id: impl Into<String>,
 }
 
 /// Emits an event to JS.
-pub fn emit<W: Webview, S: Serialize>(
-  webview: &mut W,
+pub fn emit<D: ApplicationDispatcherExt, S: Serialize>(
+  dispatcher: &mut D,
   event: impl AsRef<str> + Send + 'static,
   payload: Option<S>,
 ) -> crate::Result<()> {
@@ -70,15 +70,13 @@ pub fn emit<W: Webview, S: Serialize>(
     JsonValue::Null
   };
 
-  webview.dispatch(move |webview_ref| {
-    webview_ref.eval(&format!(
-      "window['{}']({{type: '{}', payload: {}}}, '{}')",
-      emit_function_name(),
-      event.as_ref(),
-      js_payload,
-      salt
-    ))
-  });
+  dispatcher.eval(&format!(
+    "window['{}']({{type: '{}', payload: {}}}, '{}')",
+    emit_function_name(),
+    event.as_ref(),
+    js_payload,
+    salt
+  ));
 
   Ok(())
 }

+ 31 - 19
tauri/src/lib.rs

@@ -34,15 +34,20 @@ mod webview;
 
 pub(crate) mod async_runtime;
 
+/// A task to run on the main thread.
+pub type SyncTask = Box<dyn FnOnce() + Send>;
+
 /// Alias for a Result with error type anyhow::Error.
 pub use anyhow::Result;
 pub use app::*;
 pub use tauri_api as api;
-pub use webview::*;
+pub use webview::{
+  ApplicationDispatcherExt, ApplicationExt, Callback, Event, WebviewBuilderExt, WindowBuilderExt,
+};
 
 /// The Tauri webview implementations.
 pub mod flavors {
-  pub use webview_official::Webview as Official;
+  pub use super::webview::wry::WryApplication as Wry;
 }
 
 use std::process::Stdio;
@@ -53,21 +58,27 @@ use serde::Serialize;
 /// Synchronously executes the given task
 /// and evaluates its Result to the JS promise described by the `callback` and `error` function names.
 pub fn execute_promise_sync<
-  W: Webview,
+  D: ApplicationDispatcherExt + 'static,
   R: Serialize,
-  F: futures::Future<Output = Result<R>> + Send + 'static,
+  F: FnOnce() -> Result<R> + Send + 'static,
 >(
-  webview: &mut W,
+  dispatcher: &mut D,
   task: F,
   callback: String,
   error: String,
-) -> crate::Result<()> {
-  async_runtime::block_on(async move {
+) {
+  let mut dispatcher_ = dispatcher.clone();
+  dispatcher.send_event(Event::Run(Box::new(move || {
     let callback_string =
-      format_callback_result(task.await.map_err(|err| err.to_string()), callback, error)?;
-    webview.dispatch(move |w| w.eval(callback_string.as_str()));
-    Ok(())
-  })
+      match format_callback_result(task().map_err(|err| err.to_string()), &callback, &error) {
+        Ok(js) => js,
+        Err(e) => {
+          format_callback_result(Result::<(), String>::Err(e.to_string()), &callback, &error)
+            .unwrap()
+        }
+      };
+    dispatcher_.eval(callback_string.as_str());
+  })));
 }
 
 /// Asynchronously executes the given task
@@ -76,11 +87,11 @@ pub fn execute_promise_sync<
 /// If the Result `is_ok()`, the callback will be the `success_callback` function name and the argument will be the Ok value.
 /// If the Result `is_err()`, the callback will be the `error_callback` function name and the argument will be the Err value.
 pub async fn execute_promise<
-  W: Webview,
+  D: ApplicationDispatcherExt,
   R: Serialize,
   F: futures::Future<Output = Result<R>> + Send + 'static,
 >(
-  webview: &mut W,
+  dispatcher: &mut D,
   task: F,
   success_callback: String,
   error_callback: String,
@@ -93,19 +104,19 @@ pub async fn execute_promise<
     Ok(callback_string) => callback_string,
     Err(e) => format_callback(error_callback, e.to_string()),
   };
-  webview.dispatch(move |webview_ref| webview_ref.eval(callback_string.as_str()));
+  dispatcher.eval(callback_string.as_str());
 }
 
 /// Calls the given command and evaluates its output to the JS promise described by the `callback` and `error` function names.
-pub async fn call<W: Webview>(
-  webview: &mut W,
+pub async fn call<D: ApplicationDispatcherExt>(
+  dispatcher: &mut D,
   command: String,
   args: Vec<String>,
   callback: String,
   error: String,
 ) {
   execute_promise(
-    webview,
+    dispatcher,
     async move { api::command::get_output(command, args, Stdio::piped()) },
     callback,
     error,
@@ -114,9 +125,10 @@ pub async fn call<W: Webview>(
 }
 
 /// Closes the splashscreen.
-pub fn close_splashscreen<W: Webview>(webview: &mut W) -> crate::Result<()> {
+pub fn close_splashscreen<D: ApplicationDispatcherExt>(dispatcher: &mut D) -> crate::Result<()> {
   // send a signal to the runner so it knows that it should redirect to the main app content
-  webview.eval(r#"window.__TAURI_INVOKE_HANDLER__({ cmd: "closeSplashscreen" })"#);
+  dispatcher
+    .eval(r#"window.__TAURI_INVOKE_HANDLER__(JSON.stringify({ cmd: "closeSplashscreen" }))"#);
 
   Ok(())
 }

+ 26 - 18
tauri/src/plugin.rs

@@ -1,44 +1,46 @@
 use crate::async_runtime::Mutex;
 
-use crate::Webview;
+use crate::ApplicationDispatcherExt;
 
 use std::sync::Arc;
 
 /// The plugin interface.
 #[async_trait::async_trait]
-pub trait Plugin<W: Webview + 'static>: Sync {
+pub trait Plugin<D: ApplicationDispatcherExt + 'static>: Sync {
   /// The JS script to evaluate on init.
   async fn init_script(&self) -> Option<String> {
     None
   }
   /// Callback invoked when the webview is created.
   #[allow(unused_variables)]
-  async fn created(&self, webview: W) {}
+  async fn created(&self, dispatcher: D) {}
 
   /// Callback invoked when the webview is ready.
   #[allow(unused_variables)]
-  async fn ready(&self, webview: W) {}
+  async fn ready(&self, dispatcher: D) {}
 
   /// Add invoke_handler API extension commands.
   #[allow(unused_variables)]
-  async fn extend_api(&self, webview: W, payload: &str) -> Result<bool, String> {
+  async fn extend_api(&self, dispatcher: D, payload: &str) -> Result<bool, String> {
     Err("unknown variant".to_string())
   }
 }
 
 /// Plugin collection type.
-pub type PluginStore<W> = Arc<Mutex<Vec<Box<dyn Plugin<W> + Sync + Send>>>>;
+pub type PluginStore<D> = Arc<Mutex<Vec<Box<dyn Plugin<D> + Sync + Send>>>>;
 
 /// Registers a plugin.
-pub async fn register<W: Webview + 'static>(
-  store: &PluginStore<W>,
-  plugin: impl Plugin<W> + Sync + Send + 'static,
+pub async fn register<D: ApplicationDispatcherExt + 'static>(
+  store: &PluginStore<D>,
+  plugin: impl Plugin<D> + Sync + Send + 'static,
 ) {
   let mut plugins = store.lock().await;
   plugins.push(Box::new(plugin));
 }
 
-pub(crate) async fn init_script<W: Webview + 'static>(store: &PluginStore<W>) -> String {
+pub(crate) async fn init_script<D: ApplicationDispatcherExt + 'static>(
+  store: &PluginStore<D>,
+) -> String {
   let mut init = String::new();
 
   let plugins = store.lock().await;
@@ -51,28 +53,34 @@ pub(crate) async fn init_script<W: Webview + 'static>(store: &PluginStore<W>) ->
   init
 }
 
-pub(crate) async fn created<W: Webview + 'static>(store: &PluginStore<W>, webview: &mut W) {
+pub(crate) async fn created<D: ApplicationDispatcherExt + 'static>(
+  store: &PluginStore<D>,
+  dispatcher: &mut D,
+) {
   let plugins = store.lock().await;
   for plugin in plugins.iter() {
-    plugin.created(webview.clone()).await;
+    plugin.created(dispatcher.clone()).await;
   }
 }
 
-pub(crate) async fn ready<W: Webview + 'static>(store: &PluginStore<W>, webview: &mut W) {
+pub(crate) async fn ready<D: ApplicationDispatcherExt + 'static>(
+  store: &PluginStore<D>,
+  dispatcher: &mut D,
+) {
   let plugins = store.lock().await;
   for plugin in plugins.iter() {
-    plugin.ready(webview.clone()).await;
+    plugin.ready(dispatcher.clone()).await;
   }
 }
 
-pub(crate) async fn extend_api<W: Webview + 'static>(
-  store: &PluginStore<W>,
-  webview: &mut W,
+pub(crate) async fn extend_api<D: ApplicationDispatcherExt + 'static>(
+  store: &PluginStore<D>,
+  dispatcher: &mut D,
   arg: &str,
 ) -> Result<bool, String> {
   let plugins = store.lock().await;
   for ext in plugins.iter() {
-    match ext.extend_api(webview.clone(), arg).await {
+    match ext.extend_api(dispatcher.clone(), arg).await {
       Ok(handled) => {
         if handled {
           return Ok(true);

+ 94 - 62
tauri/src/webview.rs

@@ -1,83 +1,115 @@
-pub(crate) mod official;
-
-/// Size hints.
-pub enum SizeHint {
-  /// None
-  NONE = 0,
-  /// Min
-  MIN = 1,
-  /// Max
-  MAX = 2,
-  /// Fixed
-  FIXED = 3,
-}
+pub(crate) mod wry;
+
+pub use crate::plugin::PluginStore;
 
-impl Default for SizeHint {
-  fn default() -> Self {
-    Self::NONE
-  }
+/// An event to be posted to the webview event loop.
+pub enum Event {
+  /// Run the given closure.
+  Run(crate::SyncTask),
 }
 
-pub use crate::plugin::PluginStore;
+/// The window builder.
+pub trait WindowBuilderExt: Sized {
+  /// The window type.
+  type Window;
+
+  /// Initializes a new window builder.
+  fn new() -> Self;
+
+  /// Whether the window is resizable or not.
+  fn resizable(self, resizable: bool) -> Self;
+
+  /// The title of the window in the title bar.
+  fn title(self, title: String) -> Self;
+
+  /// Whether the window should be maximized upon creation.
+  fn maximized(self, maximized: bool) -> Self;
+
+  /// Whether the window should be immediately visible upon creation.
+  fn visible(self, visible: bool) -> Self;
+
+  /// Whether the the window should be transparent. If this is true, writing colors
+  /// with alpha values different than `1.0` will produce a transparent window.
+  fn transparent(self, transparent: bool) -> Self;
+
+  /// Whether the window should have borders and bars.
+  fn decorations(self, decorations: bool) -> Self;
+
+  /// Whether the window should always be on top of other windows.
+  fn always_on_top(self, always_on_top: bool) -> Self;
+
+  /// build the window.
+  fn finish(self) -> crate::Result<Self::Window>;
+}
 
 /// The webview builder.
-pub trait WebviewBuilder: Sized {
+pub trait WebviewBuilderExt: Sized {
   /// The webview object that this builder creates.
-  type WebviewObject: Webview<Builder = Self>;
+  type Webview;
 
-  /// Initializes a new instance of the builder.
+  /// Initializes a new webview builder.
   fn new() -> Self;
-  /// Sets the debug flag.
-  fn debug(self, debug: bool) -> Self;
-  /// Sets the window title.
-  fn title(self, title: &str) -> Self;
-  /// Sets the initial url.
-  fn url(self, url: &str) -> Self;
+
+  /// Sets the webview url.
+  fn url(self, url: String) -> Self;
+
   /// Sets the init script.
-  fn init(self, init: &str) -> Self;
-  /// Sets the window width.
-  fn width(self, width: usize) -> Self;
-  /// Sets the window height.
-  fn height(self, height: usize) -> Self;
-  /// Whether the window is resizable or not.
-  fn resizable(self, resizable: SizeHint) -> Self;
+  fn initialization_script(self, init: &str) -> Self;
+
   /// Builds the webview instance.
-  fn finish(self) -> Self::WebviewObject;
+  fn finish(self) -> crate::Result<Self::Webview>;
 }
 
-/// Webview core API.
-pub trait Webview: Clone + Send + Sync + Sized {
-  /// The builder type.
-  type Builder: WebviewBuilder<WebviewObject = Self>;
-
-  /// Returns the static plugin collection.
-  fn plugin_store() -> &'static PluginStore<Self>;
+/// Binds the given callback to a global variable on the window object.
+pub struct Callback<D> {
+  /// Function name to bind.
+  pub name: String,
+  /// Function callback handler.
+  pub function: Box<dyn FnMut(&D, i32, Vec<String>) -> i32 + Send>,
+}
 
-  /// Adds an init JS code.
-  fn init(&mut self, js: &str);
+/// Webview dispatcher. A thread-safe handle to the webview API.
+pub trait ApplicationDispatcherExt: Clone + Send + Sync + Sized {
+  /// Eval a JS string on the current webview.
+  fn eval(&mut self, js: &str);
+  /// Eval a JS string on the webview associated with the given window.
+  fn eval_on_window(&mut self, window_id: &str, js: &str);
+  /// Sends a event to the webview.
+  fn send_event(&self, event: Event);
+}
 
-  /// Sets the window title.
-  fn set_title(&mut self, title: &str);
+/// The application interface.
+/// Manages windows and webviews.
+pub trait ApplicationExt: Sized {
+  /// The webview builder.
+  type WebviewBuilder: WebviewBuilderExt;
+  /// The window builder.
+  type WindowBuilder: WindowBuilderExt;
+  /// The window type.
+  type Window;
+  /// The message dispatcher.
+  type Dispatcher: ApplicationDispatcherExt;
 
-  /// Sets the window size.
-  fn set_size(&mut self, width: i32, height: i32, hint: SizeHint);
+  /// Returns the static plugin collection.
+  fn plugin_store() -> &'static PluginStore<Self::Dispatcher>;
 
-  /// terminate the webview.
-  fn terminate(&mut self);
+  /// Creates a new application.
+  fn new() -> crate::Result<Self>;
 
-  /// eval a string as JS code.
-  fn eval(&mut self, js: &str);
+  /// Gets the message dispatcher for the given window.
+  fn dispatcher(&self, window: &Self::Window) -> Self::Dispatcher;
 
-  /// Dispatches a closure to run on the main thread.
-  fn dispatch<F>(&mut self, f: F)
-  where
-    F: FnOnce(&mut Self) + Send + 'static;
+  /// Creates a new window.
+  fn create_window(&self, window_builder: Self::WindowBuilder) -> crate::Result<Self::Window>;
 
-  /// Binds a new API on the webview.
-  fn bind<F>(&mut self, name: &str, f: F)
-  where
-    F: FnMut(&str, &str);
+  /// Creates a new webview.
+  fn create_webview(
+    &mut self,
+    webview_builder: Self::WebviewBuilder,
+    window: Self::Window,
+    callbacks: Vec<Callback<Self::Dispatcher>>,
+  ) -> crate::Result<()>;
 
-  /// Run the webview event loop.
-  fn run(&mut self);
+  /// Run the application.
+  fn run(self);
 }

+ 0 - 143
tauri/src/webview/official.rs

@@ -1,143 +0,0 @@
-use super::{PluginStore, SizeHint, Webview, WebviewBuilder};
-use once_cell::sync::Lazy;
-
-#[derive(Default)]
-pub struct WebviewOfficialBuilder {
-  title: Option<String>,
-  url: Option<String>,
-  init: Option<String>,
-  eval: Option<String>,
-  size: (usize, usize, SizeHint),
-  debug: bool,
-}
-
-impl WebviewBuilder for WebviewOfficialBuilder {
-  type WebviewObject = webview_official::Webview;
-
-  fn new() -> Self {
-    WebviewOfficialBuilder::default()
-  }
-
-  fn debug(mut self, debug: bool) -> Self {
-    self.debug = debug;
-    self
-  }
-
-  fn title(mut self, title: &str) -> Self {
-    self.title = Some(title.to_string());
-    self
-  }
-
-  fn url(mut self, url: &str) -> Self {
-    self.url = Some(url.to_string());
-    self
-  }
-
-  fn init(mut self, init: &str) -> Self {
-    self.init = Some(init.to_string());
-    self
-  }
-
-  fn width(mut self, width: usize) -> Self {
-    self.size.0 = width;
-    self
-  }
-
-  fn height(mut self, height: usize) -> Self {
-    self.size.1 = height;
-    self
-  }
-
-  fn resizable(mut self, hint: SizeHint) -> Self {
-    self.size.2 = hint;
-    self
-  }
-
-  fn finish(self) -> Self::WebviewObject {
-    let mut w = webview_official::Webview::create(self.debug, None);
-    if let Some(title) = self.title {
-      w.set_title(&title);
-    }
-
-    if let Some(init) = self.init {
-      w.init(&init);
-    }
-
-    if let Some(url) = self.url {
-      w.navigate(&url);
-    }
-
-    if let Some(eval) = self.eval {
-      w.eval(&eval);
-    }
-
-    w.set_size(
-      self.size.0 as i32,
-      self.size.1 as i32,
-      match self.size.2 {
-        SizeHint::NONE => webview_official::SizeHint::NONE,
-        SizeHint::MIN => webview_official::SizeHint::MIN,
-        SizeHint::MAX => webview_official::SizeHint::MAX,
-        SizeHint::FIXED => webview_official::SizeHint::FIXED,
-      },
-    );
-
-    w
-  }
-}
-
-impl Webview for webview_official::Webview {
-  type Builder = WebviewOfficialBuilder;
-
-  fn plugin_store() -> &'static PluginStore<Self> {
-    static PLUGINS: Lazy<PluginStore<webview_official::Webview>> = Lazy::new(Default::default);
-    &PLUGINS
-  }
-
-  fn init(&mut self, js: &str) {
-    self.init(js);
-  }
-
-  fn set_title(&mut self, title: &str) {
-    self.set_title(title);
-  }
-
-  fn set_size(&mut self, width: i32, height: i32, hint: SizeHint) {
-    self.set_size(
-      width,
-      height,
-      match hint {
-        SizeHint::NONE => webview_official::SizeHint::NONE,
-        SizeHint::MIN => webview_official::SizeHint::MIN,
-        SizeHint::MAX => webview_official::SizeHint::MAX,
-        SizeHint::FIXED => webview_official::SizeHint::FIXED,
-      },
-    );
-  }
-
-  fn terminate(&mut self) {
-    self.terminate();
-  }
-
-  fn eval(&mut self, js: &str) {
-    self.eval(js);
-  }
-
-  fn dispatch<F>(&mut self, f: F)
-  where
-    F: FnOnce(&mut Self) + Send + 'static,
-  {
-    self.dispatch(f);
-  }
-
-  fn bind<F>(&mut self, name: &str, f: F)
-  where
-    F: FnMut(&str, &str),
-  {
-    self.bind(name, f);
-  }
-
-  fn run(&mut self) {
-    self.run();
-  }
-}

+ 225 - 0
tauri/src/webview/wry.rs

@@ -0,0 +1,225 @@
+use super::{
+  ApplicationDispatcherExt, ApplicationExt, Callback, Event, WebviewBuilderExt, WindowBuilderExt,
+};
+
+use wry::{ApplicationDispatcher, ApplicationExt as _, WindowExt};
+
+use once_cell::sync::Lazy;
+
+use crate::plugin::PluginStore;
+
+use std::{
+  collections::HashMap,
+  sync::{Arc, Mutex},
+};
+
+impl WindowBuilderExt for wry::AppWindowAttributes {
+  type Window = Self;
+
+  fn new() -> Self {
+    Default::default()
+  }
+
+  fn resizable(mut self, resizable: bool) -> Self {
+    self.resizable = resizable;
+    self
+  }
+
+  fn title(mut self, title: String) -> Self {
+    self.title = title;
+    self
+  }
+
+  fn maximized(mut self, maximized: bool) -> Self {
+    self.maximized = maximized;
+    self
+  }
+
+  fn visible(mut self, visible: bool) -> Self {
+    self.visible = visible;
+    self
+  }
+
+  fn transparent(mut self, transparent: bool) -> Self {
+    self.transparent = transparent;
+    self
+  }
+
+  fn decorations(mut self, decorations: bool) -> Self {
+    self.decorations = decorations;
+    self
+  }
+
+  /// Whether the window should always be on top of other windows.
+  fn always_on_top(mut self, always_on_top: bool) -> Self {
+    self.always_on_top = always_on_top;
+    self
+  }
+
+  /// build the window.
+  fn finish(self) -> crate::Result<Self::Window> {
+    Ok(self)
+  }
+}
+
+/// The webview builder.
+impl WebviewBuilderExt for wry::WebViewAttributes {
+  /// The webview object that this builder creates.
+  type Webview = Self;
+
+  fn new() -> Self {
+    Default::default()
+  }
+
+  fn url(mut self, url: String) -> Self {
+    self.url.replace(url);
+    self
+  }
+
+  fn initialization_script(mut self, init: &str) -> Self {
+    self.initialization_script.push(init.to_string());
+    self
+  }
+
+  fn finish(self) -> crate::Result<Self::Webview> {
+    Ok(self)
+  }
+}
+
+#[derive(Clone)]
+pub struct WryDispatcher {
+  inner: Arc<Mutex<wry::AppDispatcher<Event>>>,
+  current_window: wry::WindowId,
+  windows: Arc<Mutex<HashMap<String, wry::WindowId>>>,
+}
+
+impl ApplicationDispatcherExt for WryDispatcher {
+  fn eval(&mut self, js: &str) {
+    #[cfg(target_os = "linux")]
+    let window_id = self.current_window;
+    #[cfg(not(target_os = "linux"))]
+    let window_id = self.current_window.clone();
+
+    self
+      .inner
+      .lock()
+      .unwrap()
+      .dispatch_message(wry::Message::Script(window_id, js.to_string()))
+      .unwrap();
+  }
+
+  fn eval_on_window(&mut self, window_id: &str, js: &str) {
+    if let Some(window_id) = self.windows.lock().unwrap().get(window_id) {
+      #[cfg(target_os = "linux")]
+      let window_id = *window_id;
+      #[cfg(not(target_os = "linux"))]
+      let window_id = window_id.clone();
+      self
+        .inner
+        .lock()
+        .unwrap()
+        .dispatch_message(wry::Message::Script(window_id, js.to_string()))
+        .unwrap();
+    }
+  }
+
+  fn send_event(&self, event: Event) {
+    self
+      .inner
+      .lock()
+      .unwrap()
+      .dispatch_message(wry::Message::Custom(event))
+      .unwrap();
+  }
+}
+
+/// A wrapper around the wry Application interface.
+pub struct WryApplication {
+  inner: wry::Application<Event>,
+  windows: Arc<Mutex<HashMap<String, wry::WindowId>>>,
+  dispatcher_handle: Arc<Mutex<wry::AppDispatcher<Event>>>,
+}
+
+impl ApplicationExt for WryApplication {
+  type WebviewBuilder = wry::WebViewAttributes;
+  type WindowBuilder = wry::AppWindowAttributes;
+  type Window = wry::Window;
+  type Dispatcher = WryDispatcher;
+
+  fn plugin_store() -> &'static PluginStore<Self::Dispatcher> {
+    static PLUGINS: Lazy<PluginStore<WryDispatcher>> = Lazy::new(Default::default);
+    &PLUGINS
+  }
+
+  fn new() -> crate::Result<Self> {
+    let app =
+      wry::Application::new().map_err(|_| anyhow::anyhow!("failed to create application"))?;
+    let dispatcher = app.dispatcher();
+    let windows = Arc::new(Mutex::new(HashMap::new()));
+
+    Ok(Self {
+      inner: app,
+      windows,
+      dispatcher_handle: Arc::new(Mutex::new(dispatcher)),
+    })
+  }
+
+  fn dispatcher(&self, window: &Self::Window) -> Self::Dispatcher {
+    WryDispatcher {
+      inner: self.dispatcher_handle.clone(),
+      windows: self.windows.clone(),
+      current_window: window.id(),
+    }
+  }
+
+  fn create_window(&self, window_builder: Self::WindowBuilder) -> crate::Result<Self::Window> {
+    let window = self
+      .inner
+      .create_window(window_builder.finish()?)
+      .map_err(|_| anyhow::anyhow!("failed to create window"))?;
+    Ok(window)
+  }
+
+  fn create_webview(
+    &mut self,
+    webview_builder: Self::WebviewBuilder,
+    window: Self::Window,
+    callbacks: Vec<Callback<Self::Dispatcher>>,
+  ) -> crate::Result<()> {
+    let mut wry_callbacks = Vec::new();
+    for mut callback in callbacks {
+      let dispatcher_handle = self.dispatcher_handle.clone();
+      let windows = self.windows.clone();
+      let window_id = window.id();
+
+      let callback = wry::Callback {
+        name: callback.name.to_string(),
+        function: Box::new(move |_, seq, req| {
+          (callback.function)(
+            &WryDispatcher {
+              inner: dispatcher_handle.clone(),
+              windows: windows.clone(),
+              current_window: window_id,
+            },
+            seq,
+            req,
+          )
+        }),
+      };
+      wry_callbacks.push(callback);
+    }
+
+    self
+      .inner
+      .create_webview(window, webview_builder.finish()?, Some(wry_callbacks))
+      .map_err(|_| anyhow::anyhow!("failed to create webview"))?;
+    Ok(())
+  }
+
+  fn run(mut self) {
+    self.inner.set_message_handler(|message| match message {
+      Event::Run(task) => task(),
+    });
+    wry::Application::run(self.inner)
+  }
+}

Niektóre pliki nie zostały wyświetlone z powodu dużej ilości zmienionych plików