Browse Source

refactor(tauri): Webview traits (#1183)

Lucas Fernandes Nogueira 4 years ago
parent
commit
b9ce7b94c4

+ 5 - 0
.changes/webview-traits.md

@@ -0,0 +1,5 @@
+---
+"tauri": minor
+---
+
+The Tauri integration with Webview was refactored to use traits, which allows custom implementations by developers and simplifies changes on the webview implementation.

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

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

+ 1 - 1
tauri/Cargo.toml

@@ -20,7 +20,7 @@ features = [ "all-api" ]
 [dependencies]
 serde_json = "1.0"
 serde = { version = "1.0", features = [ "derive" ] }
-webview_official = "0.1.1"
+webview_official = "0.2.0"
 tauri_includedir = "0.6.0"
 phf = "0.8.0"
 base64 = "0.13.0"

+ 4 - 4
tauri/examples/communication/src-tauri/Cargo.lock

@@ -1883,9 +1883,9 @@ dependencies = [
 
 [[package]]
 name = "webview-official-sys"
-version = "0.1.1"
+version = "0.1.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7ea5c146a1d2e1b41bf9318fb17bb4b4cf809fd1b8d537318473b27aec178a61"
+checksum = "c4aec5fdf5bc938ba5fe47d23b8e02d6beaee395a91e16f0b2eec984a9a9e1d2"
 dependencies = [
  "cc",
  "pkg-config",
@@ -1893,9 +1893,9 @@ dependencies = [
 
 [[package]]
 name = "webview_official"
-version = "0.1.1"
+version = "0.2.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ef1d99ead69b8f362550a88e161590acfe53e7698edfaecc98be15738a23d191"
+checksum = "b38277d3fa288b13db39eeb153f9b8ee3d8e3181648ade05264c1ba99e774999"
 dependencies = [
  "webview-official-sys",
 ]

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

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

+ 21 - 19
tauri/src/app.rs

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

+ 21 - 20
tauri/src/app/runner.rs

@@ -6,7 +6,7 @@ use std::{
   },
 };
 
-use webview_official::{SizeHint, Webview, WebviewBuilder};
+use crate::{SizeHint, Webview, WebviewBuilder};
 
 use super::App;
 #[cfg(embedded_server)]
@@ -20,7 +20,7 @@ enum Content<T> {
 }
 
 /// Main entry point for running the Webview
-pub(crate) fn run(application: App) -> crate::Result<()> {
+pub(crate) fn run<W: Webview + 'static>(application: App<W>) -> crate::Result<()> {
   // setup the content using the config struct depending on the compile target
   let main_content = setup_content()?;
 
@@ -48,8 +48,10 @@ pub(crate) fn run(application: App) -> crate::Result<()> {
   // build the webview
   let mut webview = build_webview(application, main_content, splashscreen_content)?;
 
-  let mut webview_ = webview.as_mut();
-  crate::async_runtime::spawn(async move { crate::plugin::created(&mut webview_).await });
+  let mut webview_ = webview.clone();
+  crate::async_runtime::spawn(async move {
+    crate::plugin::created(W::plugin_store(), &mut webview_).await
+  });
 
   // spawn the embedded server on our server url
   #[cfg(embedded_server)]
@@ -238,11 +240,11 @@ pub fn init() -> String {
 }
 
 // build the webview struct
-fn build_webview<'a>(
-  application: App,
+fn build_webview<W: Webview + 'static>(
+  application: App<W>,
   content: Content<String>,
   splashscreen_content: Option<Content<String>>,
-) -> crate::Result<Webview<'a>> {
+) -> crate::Result<W> {
   let config = get()?;
   let debug = cfg!(debug_assertions);
   // get properties from config struct
@@ -254,7 +256,7 @@ fn build_webview<'a>(
     SizeHint::FIXED
   };
   // let fullscreen = config.tauri.window.fullscreen;
-  let title = config.tauri.window.title.clone().into_boxed_str();
+  let title = config.tauri.window.title.clone();
 
   let has_splashscreen = splashscreen_content.is_some();
   let initialized_splashscreen = Arc::new(AtomicBool::new(false));
@@ -281,18 +283,18 @@ fn build_webview<'a>(
       {plugin_init}
     "#,
     event_init = init(),
-    plugin_init = crate::async_runtime::block_on(crate::plugin::init_script())
+    plugin_init = crate::async_runtime::block_on(crate::plugin::init_script(W::plugin_store()))
   );
 
-  let mut webview = WebviewBuilder::new()
-    .init(Box::leak(init.into_boxed_str()))
-    .title(Box::leak(title))
+  let mut webview = W::Builder::new()
+    .init(&init)
+    .title(&title)
     .width(width as usize)
     .height(height as usize)
-    .resize(resizable)
+    .resizable(resizable)
     .debug(debug)
-    .url(Box::leak(url.into_boxed_str()))
-    .build();
+    .url(&url)
+    .finish();
   // TODO waiting for webview window API
   // webview.set_fullscreen(fullscreen);
 
@@ -304,7 +306,7 @@ fn build_webview<'a>(
     webview.dispatch(move |_webview| _webview.eval(&contents));
   }
 
-  let w = webview.as_mut();
+  let w = webview.clone();
   let application = Arc::new(application);
 
   webview.bind("__TAURI_INVOKE_HANDLER__", move |_, arg| {
@@ -326,13 +328,12 @@ fn build_webview<'a>(
         };
         application.run_setup(&mut w, source.to_string()).await;
         if source == "window-1" {
-          crate::plugin::ready(&mut w).await;
+          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));
-        })
-        .unwrap();
+        });
       } else {
         let mut endpoint_handle = crate::endpoints::handle(&mut w, &arg)
           .await
@@ -354,7 +355,7 @@ fn build_webview<'a>(
         }
         if let Err(ref app_handle_error) = endpoint_handle {
           if app_handle_error.contains("unknown variant") {
-            let error = match crate::plugin::extend_api(&mut w, &arg).await {
+            let error = match crate::plugin::extend_api(W::plugin_store(), &mut w, &arg).await {
               Ok(handled) => {
                 if handled {
                   String::from("")

+ 7 - 7
tauri/src/endpoints.rs

@@ -16,10 +16,10 @@ mod http;
 #[cfg(notification)]
 mod notification;
 
-use webview_official::WebviewMut;
+use crate::Webview;
 
 #[allow(unused_variables)]
-pub(crate) async fn handle(webview: &mut WebviewMut, arg: &str) -> crate::Result<()> {
+pub(crate) async fn handle<W: Webview + 'static>(webview: &mut W, arg: &str) -> crate::Result<()> {
   use cmd::Cmd::*;
   match serde_json::from_str(arg) {
     Err(e) => Err(e.into()),
@@ -154,7 +154,7 @@ pub(crate) async fn handle(webview: &mut WebviewMut, arg: &str) -> crate::Result
           #[cfg(set_title)]
           webview.dispatch(move |w| {
             w.set_title(&title);
-          })?;
+          });
           #[cfg(not(set_title))]
           throw_allowlist_error(webview, "title");
         }
@@ -192,7 +192,7 @@ pub(crate) async fn handle(webview: &mut WebviewMut, arg: &str) -> crate::Result
             let js_string = event::listen_fn(event, handler, once)?;
             webview.dispatch(move |w| {
               w.eval(&js_string);
-            })?;
+            });
           }
           #[cfg(not(event))]
           throw_allowlist_error(webview, "event");
@@ -323,7 +323,7 @@ pub(crate) async fn handle(webview: &mut WebviewMut, arg: &str) -> crate::Result
 }
 
 #[allow(dead_code)]
-fn api_error(webview: &mut WebviewMut, error_fn: String, message: &str) {
+fn api_error<W: Webview>(webview: &mut W, 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);
@@ -331,7 +331,7 @@ fn api_error(webview: &mut WebviewMut, error_fn: String, message: &str) {
 }
 
 #[allow(dead_code)]
-fn allowlist_error(webview: &mut WebviewMut, error_fn: String, allowlist_key: &str) {
+fn allowlist_error<W: Webview>(webview: &mut W, error_fn: String, allowlist_key: &str) {
   api_error(
     webview,
     error_fn,
@@ -343,7 +343,7 @@ fn allowlist_error(webview: &mut WebviewMut, error_fn: String, allowlist_key: &s
 }
 
 #[allow(dead_code)]
-fn throw_allowlist_error(webview: &mut WebviewMut, allowlist_key: &str) {
+fn throw_allowlist_error<W: Webview>(webview: &mut W, allowlist_key: &str) {
   let reject_code = format!(
     r#"throw new Error("'{}' not on the allowlist")"#,
     allowlist_key

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

@@ -1,9 +1,9 @@
+use crate::Webview;
 use std::path::PathBuf;
-use webview_official::WebviewMut;
 
 #[allow(clippy::option_env_unwrap)]
-pub async fn load(
-  webview: &mut WebviewMut,
+pub async fn load<W: Webview + 'static>(
+  webview: &mut W,
   asset: String,
   asset_type: String,
   callback: String,
@@ -90,7 +90,7 @@ pub async fn load(
           } else {
             webview_ref.eval(asset_str);
           }
-        })?;
+        });
         Ok("Asset loaded successfully".to_string())
       }
     },

+ 7 - 7
tauri/src/endpoints/dialog.rs

@@ -3,8 +3,8 @@ 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 serde_json::Value as JsonValue;
-use webview_official::WebviewMut;
 
 /// maps a dialog response to a JS value to eval
 #[cfg(any(open_dialog, save_dialog))]
@@ -18,8 +18,8 @@ fn map_response(response: Response) -> JsonValue {
 
 /// Shows an open dialog.
 #[cfg(open_dialog)]
-pub fn open(
-  webview: &mut WebviewMut,
+pub fn open<W: Webview>(
+  webview: &mut W,
   options: OpenDialogOptions,
   callback: String,
   error: String,
@@ -44,8 +44,8 @@ pub fn open(
 
 /// Shows a save dialog.
 #[cfg(save_dialog)]
-pub fn save(
-  webview: &mut WebviewMut,
+pub fn save<W: Webview>(
+  webview: &mut W,
   options: SaveDialogOptions,
   callback: String,
   error: String,
@@ -65,8 +65,8 @@ pub fn message(title: String, message: String) {
 }
 
 /// Shows a dialog with a yes/no question.
-pub fn ask(
-  webview: &mut WebviewMut,
+pub fn ask<W: Webview>(
+  webview: &mut W,
   title: String,
   message: String,
   callback: String,

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

@@ -1,4 +1,4 @@
-use webview_official::WebviewMut;
+use crate::Webview;
 
 use tauri_api::dir;
 use tauri_api::file;
@@ -13,8 +13,8 @@ use super::cmd::{DirOperationOptions, FileOperationOptions};
 
 /// Reads a directory.
 #[cfg(read_dir)]
-pub async fn read_dir(
-  webview: &mut WebviewMut,
+pub async fn read_dir<W: Webview>(
+  webview: &mut W,
   path: PathBuf,
   options: Option<DirOperationOptions>,
   callback: String,
@@ -38,8 +38,8 @@ pub async fn read_dir(
 
 /// Copies a file.
 #[cfg(copy_file)]
-pub async fn copy_file(
-  webview: &mut WebviewMut,
+pub async fn copy_file<W: Webview>(
+  webview: &mut W,
   source: PathBuf,
   destination: PathBuf,
   options: Option<FileOperationOptions>,
@@ -66,8 +66,8 @@ pub async fn copy_file(
 
 /// Creates a directory.
 #[cfg(create_dir)]
-pub async fn create_dir(
-  webview: &mut WebviewMut,
+pub async fn create_dir<W: Webview>(
+  webview: &mut W,
   path: PathBuf,
   options: Option<DirOperationOptions>,
   callback: String,
@@ -98,8 +98,8 @@ pub async fn create_dir(
 
 /// Removes a directory.
 #[cfg(remove_dir)]
-pub async fn remove_dir(
-  webview: &mut WebviewMut,
+pub async fn remove_dir<W: Webview>(
+  webview: &mut W,
   path: PathBuf,
   options: Option<DirOperationOptions>,
   callback: String,
@@ -130,8 +130,8 @@ pub async fn remove_dir(
 
 /// Removes a file
 #[cfg(remove_file)]
-pub async fn remove_file(
-  webview: &mut WebviewMut,
+pub async fn remove_file<W: Webview>(
+  webview: &mut W,
   path: PathBuf,
   options: Option<FileOperationOptions>,
   callback: String,
@@ -151,8 +151,8 @@ pub async fn remove_file(
 
 /// Renames a file.
 #[cfg(rename_file)]
-pub async fn rename_file(
-  webview: &mut WebviewMut,
+pub async fn rename_file<W: Webview>(
+  webview: &mut W,
   old_path: PathBuf,
   new_path: PathBuf,
   options: Option<FileOperationOptions>,
@@ -179,8 +179,8 @@ pub async fn rename_file(
 
 /// Writes a text file.
 #[cfg(write_file)]
-pub async fn write_file(
-  webview: &mut WebviewMut,
+pub async fn write_file<W: Webview>(
+  webview: &mut W,
   path: PathBuf,
   contents: String,
   options: Option<FileOperationOptions>,
@@ -202,8 +202,8 @@ pub async fn write_file(
 
 /// Writes a binary file.
 #[cfg(write_binary_file)]
-pub async fn write_binary_file(
-  webview: &mut WebviewMut,
+pub async fn write_binary_file<W: Webview>(
+  webview: &mut W,
   path: PathBuf,
   contents: String,
   options: Option<FileOperationOptions>,
@@ -229,8 +229,8 @@ pub async fn write_binary_file(
 
 /// Reads a text file.
 #[cfg(read_text_file)]
-pub async fn read_text_file(
-  webview: &mut WebviewMut,
+pub async fn read_text_file<W: Webview>(
+  webview: &mut W,
   path: PathBuf,
   options: Option<FileOperationOptions>,
   callback: String,
@@ -247,8 +247,8 @@ pub async fn read_text_file(
 
 /// Reads a binary file.
 #[cfg(read_binary_file)]
-pub async fn read_binary_file(
-  webview: &mut WebviewMut,
+pub async fn read_binary_file<W: Webview>(
+  webview: &mut W,
   path: PathBuf,
   options: Option<FileOperationOptions>,
   callback: String,

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

@@ -1,9 +1,9 @@
+use crate::Webview;
 use tauri_api::http::{make_request as request, HttpRequestOptions};
-use webview_official::WebviewMut;
 
 /// Makes an HTTP request and resolves the response to the webview
-pub async fn make_request(
-  webview: &mut WebviewMut,
+pub async fn make_request<W: Webview>(
+  webview: &mut W,
   options: HttpRequestOptions,
   callback: String,
   error: String,

+ 6 - 6
tauri/src/endpoints/notification.rs

@@ -1,9 +1,9 @@
 use super::cmd::NotificationOptions;
+use crate::Webview;
 use serde_json::Value as JsonValue;
-use webview_official::WebviewMut;
 
-pub async fn send(
-  webview: &mut WebviewMut,
+pub async fn send<W: Webview>(
+  webview: &mut W,
   options: NotificationOptions,
   callback: String,
   error: String,
@@ -27,7 +27,7 @@ pub async fn send(
   .await;
 }
 
-pub async fn is_permission_granted(webview: &mut WebviewMut, callback: String, error: String) {
+pub async fn is_permission_granted<W: Webview>(webview: &mut W, callback: String, error: String) {
   crate::execute_promise(
     webview,
     async move {
@@ -44,8 +44,8 @@ pub async fn is_permission_granted(webview: &mut WebviewMut, callback: String, e
   .await;
 }
 
-pub fn request_permission(
-  webview: &mut WebviewMut,
+pub fn request_permission<W: Webview>(
+  webview: &mut W,
   callback: String,
   error: String,
 ) -> crate::Result<()> {

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

@@ -1,10 +1,10 @@
 #![cfg(path_api)]
+use crate::Webview;
 use tauri_api::path;
 use tauri_api::path::BaseDirectory;
-use webview_official::WebviewMut;
 
-pub async fn resolve_path(
-  webview: &mut WebviewMut,
+pub async fn resolve_path<W: Webview>(
+  webview: &mut W,
   path: String,
   directory: Option<BaseDirectory>,
   callback: String,

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

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

+ 4 - 4
tauri/src/event.rs

@@ -2,11 +2,11 @@ use std::boxed::Box;
 use std::collections::HashMap;
 use std::sync::{Arc, Mutex};
 
+use crate::Webview;
 use lazy_static::lazy_static;
 use once_cell::sync::Lazy;
 use serde::Serialize;
 use serde_json::Value as JsonValue;
-use webview_official::WebviewMut;
 
 /// An event handler.
 struct EventHandler {
@@ -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<S: Serialize>(
-  webview: &mut WebviewMut,
+pub fn emit<W: Webview, S: Serialize>(
+  webview: &mut W,
   event: impl AsRef<str> + Send + 'static,
   payload: Option<S>,
 ) -> crate::Result<()> {
@@ -78,7 +78,7 @@ pub fn emit<S: Serialize>(
       js_payload,
       salt
     ))
-  })?;
+  });
 
   Ok(())
 }

+ 17 - 10
tauri/src/lib.rs

@@ -29,6 +29,8 @@ mod endpoints;
 pub mod plugin;
 /// The salt helpers.
 mod salt;
+/// Webview interface.
+mod webview;
 
 pub(crate) mod async_runtime;
 
@@ -36,7 +38,12 @@ pub(crate) mod async_runtime;
 pub use anyhow::Result;
 pub use app::*;
 pub use tauri_api as api;
-pub use webview_official::{Webview, WebviewMut};
+pub use webview::*;
+
+/// The Tauri webview implementations.
+pub mod flavors {
+  pub use webview_official::Webview as Official;
+}
 
 use std::process::Stdio;
 
@@ -46,10 +53,11 @@ 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,
   R: Serialize,
   F: futures::Future<Output = Result<R>> + Send + 'static,
 >(
-  webview: &mut WebviewMut,
+  webview: &mut W,
   task: F,
   callback: String,
   error: String,
@@ -57,7 +65,7 @@ pub fn execute_promise_sync<
   async_runtime::block_on(async 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()))?;
+    webview.dispatch(move |w| w.eval(callback_string.as_str()));
     Ok(())
   })
 }
@@ -68,10 +76,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,
   R: Serialize,
   F: futures::Future<Output = Result<R>> + Send + 'static,
 >(
-  webview: &mut WebviewMut,
+  webview: &mut W,
   task: F,
   success_callback: String,
   error_callback: String,
@@ -84,14 +93,12 @@ 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()))
-    .expect("Failed to dispatch promise callback");
+  webview.dispatch(move |webview_ref| webview_ref.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(
-  webview: &mut WebviewMut,
+pub async fn call<W: Webview>(
+  webview: &mut W,
   command: String,
   args: Vec<String>,
   callback: String,
@@ -107,7 +114,7 @@ pub async fn call(
 }
 
 /// Closes the splashscreen.
-pub fn close_splashscreen(webview: &mut Webview<'_>) -> crate::Result<()> {
+pub fn close_splashscreen<W: Webview>(webview: &mut W) -> 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" })"#);
 

+ 24 - 22
tauri/src/plugin.rs

@@ -1,49 +1,47 @@
 use crate::async_runtime::Mutex;
 
-use once_cell::sync::Lazy;
-use webview_official::WebviewMut;
+use crate::Webview;
 
 use std::sync::Arc;
 
 /// The plugin interface.
 #[async_trait::async_trait]
-pub trait Plugin: Sync {
+pub trait Plugin<W: Webview + '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: WebviewMut) {}
+  async fn created(&self, webview: W) {}
 
   /// Callback invoked when the webview is ready.
   #[allow(unused_variables)]
-  async fn ready(&self, webview: WebviewMut) {}
+  async fn ready(&self, webview: W) {}
 
   /// Add invoke_handler API extension commands.
   #[allow(unused_variables)]
-  async fn extend_api(&self, webview: WebviewMut, payload: &str) -> Result<bool, String> {
+  async fn extend_api(&self, webview: W, payload: &str) -> Result<bool, String> {
     Err("unknown variant".to_string())
   }
 }
 
-type PluginStore = Arc<Mutex<Vec<Box<dyn Plugin + Sync + Send>>>>;
-
-fn plugins() -> &'static PluginStore {
-  static PLUGINS: Lazy<PluginStore> = Lazy::new(Default::default);
-  &PLUGINS
-}
+/// Plugin collection type.
+pub type PluginStore<W> = Arc<Mutex<Vec<Box<dyn Plugin<W> + Sync + Send>>>>;
 
 /// Registers a plugin.
-pub async fn register(plugin: impl Plugin + Sync + Send + 'static) {
-  let mut plugins = plugins().lock().await;
+pub async fn register<W: Webview + 'static>(
+  store: &PluginStore<W>,
+  plugin: impl Plugin<W> + Sync + Send + 'static,
+) {
+  let mut plugins = store.lock().await;
   plugins.push(Box::new(plugin));
 }
 
-pub(crate) async fn init_script() -> String {
+pub(crate) async fn init_script<W: Webview + 'static>(store: &PluginStore<W>) -> String {
   let mut init = String::new();
 
-  let plugins = plugins().lock().await;
+  let plugins = store.lock().await;
   for plugin in plugins.iter() {
     if let Some(init_script) = plugin.init_script().await {
       init.push_str(&format!("(function () {{ {} }})();", init_script));
@@ -53,22 +51,26 @@ pub(crate) async fn init_script() -> String {
   init
 }
 
-pub(crate) async fn created(webview: &mut WebviewMut) {
-  let plugins = plugins().lock().await;
+pub(crate) async fn created<W: Webview + 'static>(store: &PluginStore<W>, webview: &mut W) {
+  let plugins = store.lock().await;
   for plugin in plugins.iter() {
     plugin.created(webview.clone()).await;
   }
 }
 
-pub(crate) async fn ready(webview: &mut WebviewMut) {
-  let plugins = plugins().lock().await;
+pub(crate) async fn ready<W: Webview + 'static>(store: &PluginStore<W>, webview: &mut W) {
+  let plugins = store.lock().await;
   for plugin in plugins.iter() {
     plugin.ready(webview.clone()).await;
   }
 }
 
-pub(crate) async fn extend_api(webview: &mut WebviewMut, arg: &str) -> Result<bool, String> {
-  let plugins = plugins().lock().await;
+pub(crate) async fn extend_api<W: Webview + 'static>(
+  store: &PluginStore<W>,
+  webview: &mut W,
+  arg: &str,
+) -> Result<bool, String> {
+  let plugins = store.lock().await;
   for ext in plugins.iter() {
     match ext.extend_api(webview.clone(), arg).await {
       Ok(handled) => {

+ 83 - 0
tauri/src/webview.rs

@@ -0,0 +1,83 @@
+pub(crate) mod official;
+
+/// Size hints.
+pub enum SizeHint {
+  /// None
+  NONE = 0,
+  /// Min
+  MIN = 1,
+  /// Max
+  MAX = 2,
+  /// Fixed
+  FIXED = 3,
+}
+
+impl Default for SizeHint {
+  fn default() -> Self {
+    Self::NONE
+  }
+}
+
+pub use crate::plugin::PluginStore;
+
+/// The webview builder.
+pub trait WebviewBuilder: Sized {
+  /// The webview object that this builder creates.
+  type WebviewObject: Webview<Builder = Self>;
+
+  /// Initializes a new instance of the 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 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;
+  /// Builds the webview instance.
+  fn finish(self) -> Self::WebviewObject;
+}
+
+/// 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>;
+
+  /// Adds an init JS code.
+  fn init(&mut self, js: &str);
+
+  /// Sets the window title.
+  fn set_title(&mut self, title: &str);
+
+  /// Sets the window size.
+  fn set_size(&mut self, width: i32, height: i32, hint: SizeHint);
+
+  /// terminate the webview.
+  fn terminate(&mut self);
+
+  /// eval a string as JS code.
+  fn eval(&mut self, js: &str);
+
+  /// Dispatches a closure to run on the main thread.
+  fn dispatch<F>(&mut self, f: F)
+  where
+    F: FnOnce(&mut Self) + Send + 'static;
+
+  /// Binds a new API on the webview.
+  fn bind<F>(&mut self, name: &str, f: F)
+  where
+    F: FnMut(&str, &str);
+
+  /// Run the webview event loop.
+  fn run(&mut self);
+}

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

@@ -0,0 +1,143 @@
+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();
+  }
+}