refactor(tauri): Webview traits (#1183)

This commit is contained in:
Lucas Fernandes Nogueira
2021-02-05 00:44:58 -03:00
committed by GitHub
parent 555d667f45
commit b9ce7b94c4
20 changed files with 380 additions and 137 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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<()> {

View File

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

View File

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

View File

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

View File

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

View File

@@ -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
tauri/src/webview.rs Normal file
View File

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

View File

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