diff --git a/.changes/webview-traits.md b/.changes/webview-traits.md new file mode 100644 index 000000000..a5505bb15 --- /dev/null +++ b/.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. diff --git a/cli/tauri.js/templates/src-tauri/src/main.rs b/cli/tauri.js/templates/src-tauri/src/main.rs index c2f44a962..322d1125f 100755 --- a/cli/tauri.js/templates/src-tauri/src/main.rs +++ b/cli/tauri.js/templates/src-tauri/src/main.rs @@ -6,7 +6,7 @@ mod cmd; fn main() { - tauri::AppBuilder::new() + tauri::AppBuilder::::new() .invoke_handler(|_webview, arg| async move { use cmd::Cmd::*; match serde_json::from_str(&arg) { diff --git a/tauri/Cargo.toml b/tauri/Cargo.toml index 62049d98c..7df00ffb0 100644 --- a/tauri/Cargo.toml +++ b/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" diff --git a/tauri/examples/communication/src-tauri/Cargo.lock b/tauri/examples/communication/src-tauri/Cargo.lock index dd07e5f12..757e74311 100644 --- a/tauri/examples/communication/src-tauri/Cargo.lock +++ b/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", ] diff --git a/tauri/examples/communication/src-tauri/src/main.rs b/tauri/examples/communication/src-tauri/src/main.rs index d62dda486..8279efcbe 100644 --- a/tauri/examples/communication/src-tauri/src/main.rs +++ b/tauri/examples/communication/src-tauri/src/main.rs @@ -13,7 +13,7 @@ struct Reply { } fn main() { - tauri::AppBuilder::new() + tauri::AppBuilder::::new() .setup(|webview, _source| async move { let mut webview = webview.clone(); tauri::event::listen(String::from("js-event"), move |msg| { diff --git a/tauri/src/app.rs b/tauri/src/app.rs index 909523959..ba3332dbc 100644 --- a/tauri/src/app.rs +++ b/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 = dyn Fn(W, String) -> BoxFuture<'static, Result<(), String>> + Send + Sync; +type Setup = dyn Fn(W, String) -> BoxFuture<'static, ()> + Send + Sync; /// The application runner. -pub struct App { +pub struct App { /// The JS message handler. - invoke_handler: Option>, + invoke_handler: Option>>, /// The setup callback, invoked when the webview is ready. - setup: Option>, + setup: Option>>, /// The HTML of the splashscreen to render. splashscreen_html: Option, } -impl App { +impl App { /// 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 { 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 { /// The JS message handler. - invoke_handler: Option>, + invoke_handler: Option>>, /// The setup callback, invoked when the webview is ready. - setup: Option>, + setup: Option>>, /// The HTML of the splashscreen to render. splashscreen_html: Option, } -impl AppBuilder { +impl AppBuilder { /// 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> + 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 + 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 + 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 { App { invoke_handler: self.invoke_handler, setup: self.setup, diff --git a/tauri/src/app/runner.rs b/tauri/src/app/runner.rs index 03cf9dc5a..0c011bcc1 100644 --- a/tauri/src/app/runner.rs +++ b/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 { } /// Main entry point for running the Webview -pub(crate) fn run(application: App) -> crate::Result<()> { +pub(crate) fn run(application: App) -> 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( + application: App, content: Content, splashscreen_content: Option>, -) -> crate::Result> { +) -> crate::Result { 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("") diff --git a/tauri/src/endpoints.rs b/tauri/src/endpoints.rs index 6136c888e..4c21a401a 100644 --- a/tauri/src/endpoints.rs +++ b/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(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(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(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(webview: &mut W, allowlist_key: &str) { let reject_code = format!( r#"throw new Error("'{}' not on the allowlist")"#, allowlist_key diff --git a/tauri/src/endpoints/asset.rs b/tauri/src/endpoints/asset.rs index 868cbce31..3a536eaf7 100644 --- a/tauri/src/endpoints/asset.rs +++ b/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( + 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()) } }, diff --git a/tauri/src/endpoints/dialog.rs b/tauri/src/endpoints/dialog.rs index 769a49152..8edf2da15 100644 --- a/tauri/src/endpoints/dialog.rs +++ b/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( + 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( + 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( + webview: &mut W, title: String, message: String, callback: String, diff --git a/tauri/src/endpoints/file_system.rs b/tauri/src/endpoints/file_system.rs index 3e0ea9169..49886be76 100644 --- a/tauri/src/endpoints/file_system.rs +++ b/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( + webview: &mut W, path: PathBuf, options: Option, 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( + webview: &mut W, source: PathBuf, destination: PathBuf, options: Option, @@ -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( + webview: &mut W, path: PathBuf, options: Option, 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( + webview: &mut W, path: PathBuf, options: Option, 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( + webview: &mut W, path: PathBuf, options: Option, 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( + webview: &mut W, old_path: PathBuf, new_path: PathBuf, options: Option, @@ -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( + webview: &mut W, path: PathBuf, contents: String, options: Option, @@ -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( + webview: &mut W, path: PathBuf, contents: String, options: Option, @@ -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( + webview: &mut W, path: PathBuf, options: Option, 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( + webview: &mut W, path: PathBuf, options: Option, callback: String, diff --git a/tauri/src/endpoints/http.rs b/tauri/src/endpoints/http.rs index 2528e181f..46aa986de 100644 --- a/tauri/src/endpoints/http.rs +++ b/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( + webview: &mut W, options: HttpRequestOptions, callback: String, error: String, diff --git a/tauri/src/endpoints/notification.rs b/tauri/src/endpoints/notification.rs index b26dc244f..8417d2243 100644 --- a/tauri/src/endpoints/notification.rs +++ b/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( + 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(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( + webview: &mut W, callback: String, error: String, ) -> crate::Result<()> { diff --git a/tauri/src/endpoints/path.rs b/tauri/src/endpoints/path.rs index 435cb9726..02755a3f8 100644 --- a/tauri/src/endpoints/path.rs +++ b/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( + webview: &mut W, path: String, directory: Option, callback: String, diff --git a/tauri/src/endpoints/salt.rs b/tauri/src/endpoints/salt.rs index e6e45ec6a..32acb5f13 100644 --- a/tauri/src/endpoints/salt.rs +++ b/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( + 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(()) } diff --git a/tauri/src/event.rs b/tauri/src/event.rs index af0c2bbea..d93d039cb 100644 --- a/tauri/src/event.rs +++ b/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) + Send + 'static>(id: impl Into, } /// Emits an event to JS. -pub fn emit( - webview: &mut WebviewMut, +pub fn emit( + webview: &mut W, event: impl AsRef + Send + 'static, payload: Option, ) -> crate::Result<()> { @@ -78,7 +78,7 @@ pub fn emit( js_payload, salt )) - })?; + }); Ok(()) } diff --git a/tauri/src/lib.rs b/tauri/src/lib.rs index 7da3c094e..8c4831ea7 100644 --- a/tauri/src/lib.rs +++ b/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> + 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> + 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( + webview: &mut W, command: String, args: Vec, 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(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" })"#); diff --git a/tauri/src/plugin.rs b/tauri/src/plugin.rs index 608d66a2a..a6c9ae6c3 100644 --- a/tauri/src/plugin.rs +++ b/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: Sync { /// The JS script to evaluate on init. async fn init_script(&self) -> Option { 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 { + async fn extend_api(&self, webview: W, payload: &str) -> Result { Err("unknown variant".to_string()) } } -type PluginStore = Arc>>>; - -fn plugins() -> &'static PluginStore { - static PLUGINS: Lazy = Lazy::new(Default::default); - &PLUGINS -} +/// Plugin collection type. +pub type PluginStore = Arc + 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( + store: &PluginStore, + plugin: impl Plugin + 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(store: &PluginStore) -> 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(store: &PluginStore, 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(store: &PluginStore, 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 { - let plugins = plugins().lock().await; +pub(crate) async fn extend_api( + store: &PluginStore, + webview: &mut W, + arg: &str, +) -> Result { + let plugins = store.lock().await; for ext in plugins.iter() { match ext.extend_api(webview.clone(), arg).await { Ok(handled) => { diff --git a/tauri/src/webview.rs b/tauri/src/webview.rs new file mode 100644 index 000000000..122487d5e --- /dev/null +++ b/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; + + /// 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; + + /// Returns the static plugin collection. + fn plugin_store() -> &'static PluginStore; + + /// 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(&mut self, f: F) + where + F: FnOnce(&mut Self) + Send + 'static; + + /// Binds a new API on the webview. + fn bind(&mut self, name: &str, f: F) + where + F: FnMut(&str, &str); + + /// Run the webview event loop. + fn run(&mut self); +} diff --git a/tauri/src/webview/official.rs b/tauri/src/webview/official.rs new file mode 100644 index 000000000..758d37ed6 --- /dev/null +++ b/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, + url: Option, + init: Option, + eval: Option, + 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 { + static PLUGINS: Lazy> = 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(&mut self, f: F) + where + F: FnOnce(&mut Self) + Send + 'static, + { + self.dispatch(f); + } + + fn bind(&mut self, name: &str, f: F) + where + F: FnMut(&str, &str), + { + self.bind(name, f); + } + + fn run(&mut self) { + self.run(); + } +}