wip: big refactor, removing application cycle + allowing generic parameters to the application (#1400)

Co-authored-by: Lucas Nogueira <lucas@tauri.studio>
This commit is contained in:
chip 2021-04-03 17:41:04 -07:00 committed by GitHub
parent c718bd2382
commit 2158a68d7d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
41 changed files with 2469 additions and 2257 deletions

View File

@ -39,7 +39,7 @@ foreach ($command in $args) {
}
"fmt" {
Write-Output "[$command] checking formatting"
cargo fmt "--" --check
cargo +nightly fmt "--" --check
check_error
}
default {

View File

@ -30,7 +30,7 @@ for command in "$@"; do
;;
fmt)
echo "[$command] checking formatting"
cargo fmt -- --check
cargo +nightly fmt -- --check
;;
*)
echo "[cargo-check.sh] Unknown cargo sub-command: $command"

4
cli/core/Cargo.lock generated
View File

@ -2420,9 +2420,9 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "winreg"
version = "0.7.0"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0120db82e8a1e0b9fb3345a539c478767c0048d842860994d96113d5b667bd69"
checksum = "d107f8c6e916235c4c01cabb3e8acf7bea8ef6a63ca2e7fa0527c049badfc48c"
dependencies = [
"winapi 0.3.9",
]

View File

@ -1,6 +1,4 @@
use anyhow::{Context, Result};
use proc_macro2::Ident;
use quote::format_ident;
use std::{
env::var,
fs::{create_dir_all, File},
@ -18,7 +16,6 @@ use tauri_codegen::{context_codegen, ContextData};
#[derive(Debug)]
pub struct CodegenContext {
config_path: PathBuf,
struct_ident: Ident,
out_file: PathBuf,
}
@ -26,7 +23,6 @@ impl Default for CodegenContext {
fn default() -> Self {
Self {
config_path: PathBuf::from("tauri.conf.json"),
struct_ident: format_ident!("TauriBuildCodegenContext"),
out_file: PathBuf::from("tauri-build-context.rs"),
}
}
@ -48,20 +44,6 @@ impl CodegenContext {
self
}
/// Set the name of the generated struct.
///
/// Don't set this if you are using [`tauri::include_codegen_context!`] as that helper macro
/// expects the default value. This option can be useful if you are not using the helper and
/// instead using [`std::include!`] on the generated code yourself.
///
/// Defaults to `TauriBuildCodegenContext`.
///
/// [`tauri::include_codegen_context!`]: https://docs.rs/tauri/0.12/tauri/macro.include_codegen_context.html
pub fn struct_ident(mut self, ident: impl AsRef<str>) -> Self {
self.struct_ident = format_ident!("{}", ident.as_ref());
self
}
/// Sets the output file's path.
///
/// **Note:** This path should be relative to the `OUT_DIR`.
@ -100,7 +82,9 @@ impl CodegenContext {
let code = context_codegen(ContextData {
config,
config_parent,
struct_ident: self.struct_ident.clone(),
// it's very hard to have a build script for unit tests, so assume this is always called from
// outside the tauri crate, making the ::tauri root valid.
context_path: quote::quote!(::tauri::Context),
})?;
// get the full output file path

View File

@ -1,5 +1,5 @@
use crate::embedded_assets::{EmbeddedAssets, EmbeddedAssetsError};
use proc_macro2::{Ident, TokenStream};
use proc_macro2::TokenStream;
use quote::quote;
use std::path::PathBuf;
use tauri_api::config::Config;
@ -8,7 +8,7 @@ use tauri_api::config::Config;
pub struct ContextData {
pub config: Config,
pub config_parent: PathBuf,
pub struct_ident: Ident,
pub context_path: TokenStream,
}
/// Build an `AsTauriContext` implementation for including in application code.
@ -16,7 +16,7 @@ pub fn context_codegen(data: ContextData) -> Result<TokenStream, EmbeddedAssetsE
let ContextData {
mut config,
config_parent,
struct_ident,
context_path,
} = data;
let dist_dir = config_parent.join(&config.build.dist_dir);
config.build.dist_dir = dist_dir.to_string_lossy().to_string();
@ -25,44 +25,17 @@ pub fn context_codegen(data: ContextData) -> Result<TokenStream, EmbeddedAssetsE
let assets = EmbeddedAssets::new(&dist_dir)?;
// handle default window icons for Windows targets
#[cfg(not(debug_assertions))]
let default_window_icon = if cfg!(windows) {
let icon_path = config_parent.join("icons/icon.ico").display().to_string();
quote!(Some(include_bytes!(#icon_path)))
quote!(Some(include_bytes!(#icon_path).to_vec()))
} else {
quote!(None)
};
// in development builds, don't use an icon as it slows down cargo check
#[cfg(debug_assertions)]
let default_window_icon = quote!(None);
// double braces are purposeful to force the code into a block expression
Ok(quote! {{
use ::tauri::api::private::{OnceCell, AsTauriContext};
static CONFIG: OnceCell<::tauri::api::config::Config>= OnceCell::new();
/// Generated by `tauri-codegen`.
struct #struct_ident;
impl AsTauriContext for #struct_ident {
/// Return a static reference to the config we parsed at build time
fn config() -> &'static ::tauri::api::config::Config {
CONFIG.get_or_init(|| #config)
}
/// Inject assets we generated during build time
fn assets() -> &'static ::tauri::api::assets::EmbeddedAssets {
#assets
}
/// Default window icon to set automatically if exists
fn default_window_icon() -> Option<&'static [u8]> {
#default_window_icon
}
}
#struct_ident {}
}})
Ok(quote!(#context_path {
config: #config,
assets: #assets,
default_window_icon: #default_window_icon,
}))
}

View File

@ -51,7 +51,6 @@ pub enum EmbeddedAssetsError {
pub struct EmbeddedAssets(HashMap<AssetKey, (String, Vec<u8>)>);
impl EmbeddedAssets {
#[cfg(not(debug_assertions))]
/// Compress a directory of assets, ready to be generated into a [`tauri_api::assets::Assets`].
pub fn new(path: &Path) -> Result<Self, EmbeddedAssetsError> {
WalkDir::new(&path)
@ -74,14 +73,6 @@ impl EmbeddedAssets {
.map(Self)
}
#[cfg(debug_assertions)]
/// A dummy EmbeddedAssets for use during development builds.
/// Compressing + including the bytes of assets during development takes a long time.
/// On development builds, assets will simply be resolved & fetched from the configured dist folder.
pub fn new(_: &Path) -> Result<Self, EmbeddedAssetsError> {
Ok(EmbeddedAssets(HashMap::new()))
}
/// Use highest compression level for release, the fastest one for everything else
fn compression_level() -> i32 {
match var("PROFILE").as_ref().map(String::as_str) {
@ -135,10 +126,9 @@ impl ToTokens for EmbeddedAssets {
}
// we expect phf related items to be in path when generating the path code
tokens.append_all(quote! {
tokens.append_all(quote! {{
use ::tauri::api::assets::{EmbeddedAssets, phf, phf::phf_map};
static ASSETS: EmbeddedAssets = EmbeddedAssets::from_zstd(phf_map! { #map });
&ASSETS
});
EmbeddedAssets::from_zstd(phf_map! { #map })
}});
}
}

View File

@ -14,17 +14,16 @@ struct Reply {
fn main() {
tauri::AppBuilder::default()
.setup(move |webview_manager| {
let dispatcher = webview_manager.current_webview().unwrap();
let dispatcher_ = dispatcher.clone();
dispatcher.listen("js-event", move |event| {
.on_page_load(|window, _| {
let window_ = window.clone();
window.listen("js-event".into(), move |event| {
println!("got js-event with message '{:?}'", event.payload());
let reply = Reply {
data: "something else".to_string(),
};
dispatcher_
.emit("rust-event", Some(reply))
window_
.emit(&"rust-event".into(), Some(reply))
.expect("failed to emit");
});
})
@ -33,5 +32,6 @@ fn main() {
cmd::perform_request
])
.build(tauri::generate_context!())
.run();
.run()
.expect("error while running tauri application");
}

View File

@ -12,5 +12,6 @@ fn main() {
tauri::AppBuilder::default()
.invoke_handler(tauri::generate_handler![my_custom_command])
.build(tauri::generate_context!())
.run();
.run()
.expect("error while running tauri application");
}

View File

@ -72,4 +72,4 @@
</script>
</body>
</html>
</html>

View File

@ -3,27 +3,20 @@
windows_subsystem = "windows"
)]
use tauri::WebviewBuilderExt;
use tauri::Attributes;
fn main() {
tauri::AppBuilder::default()
.setup(move |webview_manager| {
if webview_manager.current_window_label() == "Main" {
webview_manager.listen("clicked", move |_| {
println!("got 'clicked' event on global channel");
});
}
let current_webview = webview_manager.current_webview().unwrap();
let label = webview_manager.current_window_label().to_string();
current_webview.listen("clicked", move |_| {
println!("got 'clicked' event on window '{}'", label)
.on_page_load(|window, _payload| {
let label = window.label().to_string();
window.listen("clicked".to_string(), move |_payload| {
println!("got 'clicked' event on window '{}'", label);
});
})
.create_webview("Rust".to_string(), tauri::WindowUrl::App, |mut builder| {
builder = builder.title("Tauri - Rust");
Ok(builder)
.create_window("Rust".to_string(), tauri::WindowUrl::App, |attributes| {
attributes.title("Tauri - Rust")
})
.unwrap()
.build(tauri::generate_context!())
.run();
.run()
.expect("failed to run tauri application");
}

View File

@ -27,14 +27,12 @@
"windows": [
{
"label": "Main",
"url": "app",
"title": "Tauri - Main",
"width": 800,
"height": 600
},
{
"label": "Secondary",
"url": "app",
"title": "Tauri - Secondary",
"width": 600,
"height": 400

View File

@ -1,23 +1,10 @@
use proc_macro2::TokenStream;
use quote::{format_ident, quote};
use syn::{
parse::Parser, punctuated::Punctuated, FnArg, Ident, ItemFn, Meta, NestedMeta, Pat, Path,
ReturnType, Token, Type,
parse::Parser, punctuated::Punctuated, FnArg, Ident, ItemFn, Pat, Path, ReturnType, Token, Type,
};
pub fn generate_command(attrs: Vec<NestedMeta>, function: ItemFn) -> TokenStream {
// Check if "with_manager" attr was passed to macro
let uses_manager = attrs.iter().any(|a| {
if let NestedMeta::Meta(Meta::Path(path)) = a {
path
.get_ident()
.map(|i| *i == "with_manager")
.unwrap_or(false)
} else {
false
}
});
pub fn generate_command(function: ItemFn) -> TokenStream {
let fn_name = function.sig.ident.clone();
let fn_name_str = fn_name.to_string();
let fn_wrapper = format_ident!("{}_wrapper", fn_name);
@ -37,7 +24,7 @@ pub fn generate_command(attrs: Vec<NestedMeta>, function: ItemFn) -> TokenStream
};
// Split function args into names and types
let (mut names, mut types): (Vec<Ident>, Vec<Path>) = function
let (names, types): (Vec<Ident>, Vec<Path>) = function
.sig
.inputs
.iter()
@ -59,22 +46,6 @@ pub fn generate_command(attrs: Vec<NestedMeta>, function: ItemFn) -> TokenStream
})
.unzip();
// If function doesn't take the webview manager, wrapper just takes webview manager generically and ignores it
// Otherwise the wrapper uses the specific type from the original function declaration
let mut manager_arg_type = quote!(::tauri::WebviewManager<A>);
let manager_arg_maybe = match types.first() {
Some(first_type) if uses_manager => {
// Give wrapper specific type
manager_arg_type = quote!(#first_type);
// Remove webview manager arg from list so it isn't expected as arg from JS
types.drain(0..1);
names.drain(0..1);
// Tell wrapper to pass webview manager to original function
quote!(_manager,)
}
// Tell wrapper not to pass webview manager to original function
_ => quote!(),
};
let await_maybe = if function.sig.asyncness.is_some() {
quote!(.await)
} else {
@ -87,18 +58,18 @@ pub fn generate_command(attrs: Vec<NestedMeta>, function: ItemFn) -> TokenStream
// note that all types must implement `serde::Serialize`.
let return_value = if returns_result {
quote! {
match #fn_name(#manager_arg_maybe #(parsed_args.#names),*)#await_maybe {
match #fn_name(#(parsed_args.#names),*)#await_maybe {
Ok(value) => ::core::result::Result::Ok(value),
Err(e) => ::core::result::Result::Err(e),
}
}
} else {
quote! { ::core::result::Result::<_, ()>::Ok(#fn_name(#manager_arg_maybe #(parsed_args.#names),*)#await_maybe) }
quote! { ::core::result::Result::<_, ()>::Ok(#fn_name(#(parsed_args.#names),*)#await_maybe) }
};
quote! {
#function
pub fn #fn_wrapper<A: ::tauri::ApplicationExt + 'static>(_manager: #manager_arg_type, message: ::tauri::InvokeMessage<A>) {
pub fn #fn_wrapper<P: ::tauri::Params>(message: ::tauri::InvokeMessage<P>) {
#[derive(::serde::Deserialize)]
#[serde(rename_all = "camelCase")]
struct ParsedArgs {
@ -134,9 +105,9 @@ pub fn generate_handler(item: proc_macro::TokenStream) -> TokenStream {
});
quote! {
move |webview_manager, message| {
move |message| {
match message.command() {
#(stringify!(#fn_names) => #fn_wrappers(webview_manager, message),)*
#(stringify!(#fn_names) => #fn_wrappers(message),)*
_ => {},
}
}

View File

@ -1,67 +1,80 @@
use proc_macro2::TokenStream;
use quote::{format_ident, quote};
use std::path::PathBuf;
use proc_macro2::{Ident, Span, TokenStream};
use quote::{quote, ToTokens};
use std::{env::VarError, path::PathBuf};
use syn::{
parse::{Parse, ParseBuffer},
punctuated::Punctuated,
LitStr, PathArguments, PathSegment, Token,
};
use tauri_codegen::{context_codegen, get_config, ContextData};
/// Parse the passed [`proc_macro::TokenStream`] and make sure the config file exists.
///
/// The [`proc_macro::TokenStream`] is expected to be empty for the default config path, or passed
/// a [`syn::LitStr`] a custom config path. The custom path can be either relative or absolute, with
/// relative paths being searched from the current working directory of the compiling crate.
///
/// This is a macro so that we can use [`syn::parse_macro_input!`] and easily return a
/// [`std::compile_error!`] when the path input is bad.
#[macro_use]
macro_rules! parse_config_path {
($path:ident) => {
{
use ::std::{env::{var, VarError}, path::PathBuf};
use ::syn::{parse_macro_input, LitStr};
use ::quote::quote;
pub(crate) struct ContextItems {
config_file: PathBuf,
context_path: syn::Path,
}
let path = if $path.is_empty() {
var("CARGO_MANIFEST_DIR").map(|m| PathBuf::from(m).join("tauri.conf.json"))
impl Parse for ContextItems {
fn parse(input: &ParseBuffer) -> syn::parse::Result<Self> {
let config_file = if input.is_empty() {
std::env::var("CARGO_MANIFEST_DIR").map(|m| PathBuf::from(m).join("tauri.conf.json"))
} else {
let raw: LitStr = input.parse()?;
let path = PathBuf::from(raw.value());
if path.is_relative() {
std::env::var("CARGO_MANIFEST_DIR").map(|m| PathBuf::from(m).join(path))
} else {
let raw = parse_macro_input!($path as LitStr);
let path = PathBuf::from(raw.value());
if path.is_relative() {
var("CARGO_MANIFEST_DIR").map(|m| PathBuf::from(m).join(path))
} else {
Ok(path)
}
};
let path = path
.map_err(|error| match error {
VarError::NotPresent => "no CARGO_MANIFEST_DIR env var, this should be set by cargo".into(),
VarError::NotUnicode(_) => "CARGO_MANIFEST_DIR env var contained invalid utf8".into()
})
.and_then(|path| {
if path.exists() {
Ok(path)
} else {
Err(format!(
"no file at path {} exists, expected tauri config file",
path.display()
))
}
});
match path {
Ok(path) => path,
Err(error_string) => return quote!(compile_error!(#error_string)).into(),
Ok(path)
}
}
.map_err(|error| match error {
VarError::NotPresent => "no CARGO_MANIFEST_DIR env var, this should be set by cargo".into(),
VarError::NotUnicode(_) => "CARGO_MANIFEST_DIR env var contained invalid utf8".into(),
})
.and_then(|path| {
if path.exists() {
Ok(path)
} else {
Err(format!(
"no file at path {} exists, expected tauri config file",
path.display()
))
}
})
.map_err(|e| input.error(e))?;
let context_path = if input.is_empty() {
let mut segments = Punctuated::new();
segments.push(PathSegment {
ident: Ident::new("tauri", Span::call_site()),
arguments: PathArguments::None,
});
segments.push(PathSegment {
ident: Ident::new("Context", Span::call_site()),
arguments: PathArguments::None,
});
syn::Path {
leading_colon: Some(Token![::](Span::call_site())),
segments,
}
} else {
let _: Token![,] = input.parse()?;
input.call(syn::Path::parse_mod_style)?
};
Ok(Self {
config_file,
context_path,
})
}
}
pub(crate) fn generate_context(path: PathBuf) -> TokenStream {
let context = get_config(&path)
pub(crate) fn generate_context(context: ContextItems) -> TokenStream {
let context = get_config(&context.config_file)
.map_err(|e| e.to_string())
.map(|(config, config_parent)| ContextData {
config,
config_parent,
struct_ident: format_ident!("AutoTauriContext"),
context_path: context.context_path.to_token_stream(),
})
.and_then(|data| context_codegen(data).map_err(|e| e.to_string()));

View File

@ -1,6 +1,7 @@
extern crate proc_macro;
use crate::context::ContextItems;
use proc_macro::TokenStream;
use syn::{parse_macro_input, AttributeArgs, ItemFn};
use syn::{parse_macro_input, ItemFn};
mod command;
@ -8,10 +9,9 @@ mod command;
mod context;
#[proc_macro_attribute]
pub fn command(attrs: TokenStream, item: TokenStream) -> TokenStream {
pub fn command(_: TokenStream, item: TokenStream) -> TokenStream {
let function = parse_macro_input!(item as ItemFn);
let attrs = parse_macro_input!(attrs as AttributeArgs);
let gen = command::generate_command(attrs, function);
let gen = command::generate_command(function);
gen.into()
}
@ -34,8 +34,8 @@ pub fn generate_handler(item: TokenStream) -> TokenStream {
///
/// todo: link the [`AsTauriContext`] docs
#[proc_macro]
pub fn generate_context(item: TokenStream) -> TokenStream {
pub fn generate_context(items: TokenStream) -> TokenStream {
// this macro is exported from the context module
let path = parse_config_path!(item);
let path = parse_macro_input!(items as ContextItems);
context::generate_context(path).into()
}

View File

@ -69,7 +69,7 @@ impl<P: AsRef<Path>> From<P> for AssetKey {
}
/// Represents a container of file assets that are retrievable during runtime.
pub trait Assets {
pub trait Assets: Send + Sync + 'static {
/// Get the content of the passed [`AssetKey`].
fn get<Key: Into<AssetKey>>(&self, key: Key) -> Option<Cow<'_, [u8]>>;
}

View File

@ -388,17 +388,6 @@ pub struct Config {
#[derive(Debug, Clone, Default, PartialEq, Deserialize)]
pub struct PluginConfig(pub HashMap<String, JsonValue>);
impl PluginConfig {
/// Gets a plugin configuration.
pub fn get<S: AsRef<str>>(&self, plugin_name: S) -> String {
self
.0
.get(plugin_name.as_ref())
.map(|config| config.to_string())
.unwrap_or_else(|| "{}".to_string())
}
}
/// Implement `ToTokens` for all config structs, allowing a literal `Config` to be built.
///
/// This allows for a build script to output the values in a `Config` to a `TokenStream`, which can

View File

@ -23,13 +23,12 @@ serde = { version = "1.0", features = [ "derive" ] }
base64 = "0.13.0"
tokio = { version = "1.4", features = ["rt", "rt-multi-thread", "sync"] }
futures = "0.3"
async-trait = "0.1"
uuid = { version = "0.8.2", features = [ "v4" ] }
thiserror = "1.0.24"
once_cell = "1.7.2"
tauri-api = { version = "0.7.5", path = "../tauri-api" }
tauri-macros = { version = "0.1", path = "../tauri-macros" }
wry = "0.6"
wry = "0.7"
rand = "0.8"
[build-dependencies]

View File

@ -1,494 +0,0 @@
use serde::{Deserialize, Serialize};
use serde_json::Value as JsonValue;
use tauri_api::{
config::Config,
private::AsTauriContext,
rpc::{format_callback, format_callback_result},
};
use std::{
collections::HashMap,
sync::{Arc, Mutex},
};
pub(crate) mod event;
mod utils;
pub(crate) mod webview;
mod webview_manager;
pub use crate::api::config::WindowUrl;
use crate::flavors::Wry;
pub use webview::{
wry::WryApplication, ApplicationDispatcherExt, ApplicationExt, CustomProtocol, FileDropEvent,
FileDropHandler, Icon, Message, RpcRequest, WebviewBuilderExt, WebviewRpcHandler,
};
pub use webview_manager::{WebviewDispatcher, WebviewManager};
type InvokeHandler<A> = dyn Fn(WebviewManager<A>, InvokeMessage<A>) + Send;
type ManagerHook<A> = dyn Fn(WebviewManager<A>) + Send;
type PageLoadHook<A> = dyn Fn(WebviewManager<A>, PageLoadPayload) + Send;
/// Payload from an invoke call.
#[derive(Debug, Deserialize)]
pub(crate) struct InvokePayload {
#[serde(rename = "__tauriModule")]
tauri_module: Option<String>,
callback: String,
error: String,
#[serde(rename = "mainThread", default)]
pub(crate) main_thread: bool,
#[serde(flatten)]
inner: JsonValue,
}
/// An invoke message.
pub struct InvokeMessage<A: ApplicationExt> {
webview_manager: WebviewManager<A>,
command: String,
payload: InvokePayload,
}
impl<A: ApplicationExt + 'static> InvokeMessage<A> {
pub(crate) fn new(
webview_manager: WebviewManager<A>,
command: String,
payload: InvokePayload,
) -> Self {
Self {
webview_manager,
command,
payload,
}
}
/// The invoke command.
pub fn command(&self) -> &str {
&self.command
}
/// The invoke payload.
pub fn payload(&self) -> JsonValue {
self.payload.inner.clone()
}
/// Reply to the invoke promise with a async task.
pub fn respond_async<
T: Serialize,
E: Serialize,
F: std::future::Future<Output = Result<T, E>> + Send + 'static,
>(
self,
task: F,
) {
if self.payload.main_thread {
crate::async_runtime::block_on(async move {
return_task(
&self.webview_manager,
task,
self.payload.callback,
self.payload.error,
)
.await;
});
} else {
crate::async_runtime::spawn(async move {
return_task(
&self.webview_manager,
task,
self.payload.callback,
self.payload.error,
)
.await;
});
}
}
/// Reply to the invoke promise running the given closure.
pub fn respond_closure<T: Serialize, E: Serialize, F: FnOnce() -> Result<T, E>>(self, f: F) {
return_closure(
&self.webview_manager,
f,
self.payload.callback,
self.payload.error,
)
}
/// Resolve the invoke promise with a value.
pub fn resolve<S: Serialize>(self, value: S) {
return_result(
&self.webview_manager,
Result::<S, ()>::Ok(value),
self.payload.callback,
self.payload.error,
)
}
/// Reject the invoke promise with a value.
pub fn reject<S: Serialize>(self, value: S) {
return_result(
&self.webview_manager,
Result::<(), S>::Err(value),
self.payload.callback,
self.payload.error,
)
}
}
/// Asynchronously executes the given task
/// and evaluates its Result to the JS promise described by the `success_callback` and `error_callback` function names.
///
/// 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.
async fn return_task<
A: ApplicationExt + 'static,
T: Serialize,
E: Serialize,
F: std::future::Future<Output = Result<T, E>> + Send + 'static,
>(
webview_manager: &crate::WebviewManager<A>,
task: F,
success_callback: String,
error_callback: String,
) {
let result = task.await;
return_closure(webview_manager, || result, success_callback, error_callback)
}
fn return_closure<
A: ApplicationExt + 'static,
T: Serialize,
E: Serialize,
F: FnOnce() -> Result<T, E>,
>(
webview_manager: &crate::WebviewManager<A>,
f: F,
success_callback: String,
error_callback: String,
) {
return_result(webview_manager, f(), success_callback, error_callback)
}
fn return_result<A: ApplicationExt + 'static, T: Serialize, E: Serialize>(
webview_manager: &crate::WebviewManager<A>,
result: Result<T, E>,
success_callback: String,
error_callback: String,
) {
let callback_string =
match format_callback_result(result, success_callback, error_callback.clone()) {
Ok(callback_string) => callback_string,
Err(e) => format_callback(error_callback, e.to_string()),
};
if let Ok(dispatcher) = webview_manager.current_webview() {
let _ = dispatcher.eval(callback_string.as_str());
}
}
/// `App` runtime information.
pub struct Context {
pub(crate) config: &'static Config,
pub(crate) default_window_icon: Option<&'static [u8]>,
pub(crate) assets: &'static tauri_api::assets::EmbeddedAssets,
}
impl Context {
pub(crate) fn new<Context: AsTauriContext>(_: Context) -> Self {
Self {
config: Context::config(),
default_window_icon: Context::default_window_icon(),
assets: Context::assets(),
}
}
}
pub(crate) struct Webview<A: ApplicationExt> {
pub(crate) builder: A::WebviewBuilder,
pub(crate) label: String,
pub(crate) url: WindowUrl,
}
/// The payload for the "page_load" hook.
#[derive(Debug, Clone, Deserialize)]
pub struct PageLoadPayload {
url: String,
}
impl PageLoadPayload {
/// The page URL.
pub fn url(&self) -> &str {
&self.url
}
}
/// The application runner.
pub struct App<A: ApplicationExt> {
/// The JS message handler.
invoke_handler: Option<Box<InvokeHandler<A>>>,
/// The page load hook, invoked when the webview performs a navigation.
on_page_load: Option<Box<PageLoadHook<A>>>,
/// The setup hook, invoked when the webviews have been created.
setup: Option<Box<ManagerHook<A>>>,
/// The context the App was created with
pub(crate) context: Context,
pub(crate) dispatchers: Arc<Mutex<HashMap<String, WebviewDispatcher<A::Dispatcher>>>>,
pub(crate) webviews: Option<Vec<Webview<A>>>,
url: String,
window_labels: Arc<Mutex<Vec<String>>>,
plugin_initialization_script: String,
}
impl<A: ApplicationExt + 'static> App<A> {
/// Runs the app until it finishes.
pub fn run(mut self) {
{
let mut window_labels = self.window_labels.lock().unwrap();
for window_config in self.context.config.tauri.windows.clone() {
let window_url = window_config.url.clone();
let window_label = window_config.label.to_string();
window_labels.push(window_label.to_string());
let webview = A::WebviewBuilder::from(webview::WindowConfig(window_config));
let mut webviews = self.webviews.take().unwrap();
webviews.push(Webview {
label: window_label,
builder: webview,
url: window_url,
});
self.webviews = Some(webviews);
}
}
run(self).expect("failed to run application");
}
/// Runs the invoke handler if defined.
/// Returns whether the message was consumed or not.
/// The message is considered consumed if the handler exists and returns an Ok Result.
pub(crate) fn run_invoke_handler(
&self,
dispatcher: &WebviewManager<A>,
message: InvokeMessage<A>,
) {
if let Some(ref invoke_handler) = self.invoke_handler {
invoke_handler(dispatcher.clone(), message);
}
}
/// Runs the setup hook if defined.
pub(crate) fn run_setup(&self, dispatcher: WebviewManager<A>) {
if let Some(ref setup) = self.setup {
setup(dispatcher);
}
}
/// Runs the on page load hook if defined.
pub(crate) fn run_on_page_load(&self, dispatcher: &WebviewManager<A>, payload: PageLoadPayload) {
if let Some(ref on_page_load) = self.on_page_load {
on_page_load(dispatcher.clone(), payload);
}
}
}
type WebviewContext<A> = (
<A as ApplicationExt>::WebviewBuilder,
Option<WebviewRpcHandler<<A as ApplicationExt>::Dispatcher>>,
Option<CustomProtocol>,
Option<FileDropHandler>,
);
trait WebviewInitializer<A: ApplicationExt> {
fn init_webview(&self, webview: Webview<A>) -> crate::Result<WebviewContext<A>>;
fn on_webview_created(&self, webview_label: String, dispatcher: A::Dispatcher);
}
impl<A: ApplicationExt + 'static> WebviewInitializer<A> for Arc<Mutex<App<A>>> {
fn init_webview(&self, webview: Webview<A>) -> crate::Result<WebviewContext<A>> {
let application = self.lock().unwrap();
let webview_manager = WebviewManager::new(
self.clone(),
application.dispatchers.clone(),
webview.label.to_string(),
);
let (webview_builder, rpc_handler, custom_protocol) = utils::build_webview(
self.clone(),
webview,
&webview_manager,
&application.url,
&application.window_labels.lock().unwrap(),
&application.plugin_initialization_script,
&application.context,
)?;
let file_drop_handler: Box<dyn Fn(FileDropEvent) -> bool + Send> = Box::new(move |event| {
let webview_manager = webview_manager.clone();
crate::async_runtime::block_on(async move {
let webview = webview_manager.current_webview().unwrap();
let _ = match event {
FileDropEvent::Hovered(paths) => webview.emit("tauri://file-drop-hover", Some(paths)),
FileDropEvent::Dropped(paths) => webview.emit("tauri://file-drop", Some(paths)),
FileDropEvent::Cancelled => webview.emit("tauri://file-drop-cancelled", Some(())),
};
});
true
});
Ok((
webview_builder,
rpc_handler,
custom_protocol,
Some(file_drop_handler),
))
}
fn on_webview_created(&self, webview_label: String, dispatcher: A::Dispatcher) {
self.lock().unwrap().dispatchers.lock().unwrap().insert(
webview_label.to_string(),
WebviewDispatcher::new(dispatcher, webview_label),
);
}
}
/// The App builder.
pub struct AppBuilder<A = Wry>
where
A: ApplicationExt,
{
/// The JS message handler.
invoke_handler: Option<Box<InvokeHandler<A>>>,
/// The setup hook.
setup: Option<Box<ManagerHook<A>>>,
/// Page load hook.
on_page_load: Option<Box<PageLoadHook<A>>>,
/// The webview dispatchers.
dispatchers: Arc<Mutex<HashMap<String, WebviewDispatcher<A::Dispatcher>>>>,
/// The created webviews.
webviews: Vec<Webview<A>>,
}
impl<A: ApplicationExt + 'static> AppBuilder<A> {
/// Creates a new App builder.
pub fn new() -> Self {
Self {
invoke_handler: None,
setup: None,
on_page_load: None,
dispatchers: Default::default(),
webviews: Default::default(),
}
}
/// Defines the JS message handler callback.
pub fn invoke_handler<F: Fn(WebviewManager<A>, InvokeMessage<A>) + Send + 'static>(
mut self,
invoke_handler: F,
) -> Self {
self.invoke_handler = Some(Box::new(invoke_handler));
self
}
/// Defines the setup hook.
pub fn setup<F: Fn(WebviewManager<A>) + Send + 'static>(mut self, setup: F) -> Self {
self.setup = Some(Box::new(setup));
self
}
/// Defines the page load hook.
pub fn on_page_load<F: Fn(WebviewManager<A>, PageLoadPayload) + Send + 'static>(
mut self,
on_page_load: F,
) -> Self {
self.on_page_load = Some(Box::new(on_page_load));
self
}
/// Adds a plugin to the runtime.
pub fn plugin(self, plugin: impl crate::plugin::Plugin<A> + Send + 'static) -> Self {
crate::plugin::register(A::plugin_store(), plugin);
self
}
/// Creates a new webview.
pub fn create_webview<F: FnOnce(A::WebviewBuilder) -> crate::Result<A::WebviewBuilder>>(
mut self,
label: String,
url: WindowUrl,
f: F,
) -> crate::Result<Self> {
let builder = f(A::WebviewBuilder::new())?;
self.webviews.push(Webview {
label,
builder,
url,
});
Ok(self)
}
/// Builds the App.
pub fn build(self, context: impl AsTauriContext) -> App<A> {
let window_labels: Vec<String> = self.webviews.iter().map(|w| w.label.to_string()).collect();
let plugin_initialization_script = crate::plugin::initialization_script(A::plugin_store());
let context = Context::new(context);
let url = utils::get_url(&context);
App {
invoke_handler: self.invoke_handler,
setup: self.setup,
on_page_load: self.on_page_load,
context,
dispatchers: self.dispatchers,
webviews: Some(self.webviews),
url,
window_labels: Arc::new(Mutex::new(window_labels)),
plugin_initialization_script,
}
}
}
/// Make `Wry` the default `ApplicationExt` for `AppBuilder`
impl Default for AppBuilder<Wry> {
fn default() -> Self {
Self::new()
}
}
fn run<A: ApplicationExt + 'static>(mut application: App<A>) -> crate::Result<()> {
let plugin_config = application.context.config.plugins.clone();
crate::plugin::initialize(A::plugin_store(), plugin_config)?;
let webviews = application.webviews.take().unwrap();
let dispatchers = application.dispatchers.clone();
let application = Arc::new(Mutex::new(application));
let mut webview_app = A::new()?;
let mut main_webview_manager = None;
for webview in webviews {
let webview_label = webview.label.to_string();
let webview_manager = WebviewManager::new(
application.clone(),
dispatchers.clone(),
webview_label.to_string(),
);
if main_webview_manager.is_none() {
main_webview_manager = Some(webview_manager.clone());
}
let (webview_builder, rpc_handler, custom_protocol, file_drop_handler) =
application.init_webview(webview)?;
let dispatcher = webview_app.create_webview(
webview_builder,
rpc_handler,
custom_protocol,
file_drop_handler,
)?;
application.on_webview_created(webview_label, dispatcher);
crate::plugin::created(A::plugin_store(), &webview_manager);
}
if let Some(main_webview_manager) = main_webview_manager {
application.lock().unwrap().run_setup(main_webview_manager);
}
webview_app.run();
Ok(())
}

View File

@ -1,244 +0,0 @@
use std::{
boxed::Box,
collections::HashMap,
sync::{Arc, Mutex},
};
use crate::ApplicationDispatcherExt;
use once_cell::sync::Lazy;
use serde::Serialize;
use serde_json::Value as JsonValue;
use uuid::Uuid;
/// Event identifier.
pub type EventId = u64;
/// An event handler.
struct EventHandler {
/// Event identifier.
id: EventId,
/// A event handler might be global or tied to a window.
window_label: Option<String>,
/// The on event callback.
on_event: Box<dyn Fn(EventPayload) + Send>,
}
type Listeners = Arc<Mutex<HashMap<String, Vec<EventHandler>>>>;
static EMIT_FUNCTION_NAME: Lazy<String> = Lazy::new(|| Uuid::new_v4().to_string());
static EVENT_LISTENERS_OBJECT_NAME: Lazy<String> = Lazy::new(|| Uuid::new_v4().to_string());
static EVENT_QUEUE_OBJECT_NAME: Lazy<String> = Lazy::new(|| Uuid::new_v4().to_string());
/// Gets the listeners map.
fn listeners() -> &'static Listeners {
static LISTENERS: Lazy<Listeners> = Lazy::new(Default::default);
&LISTENERS
}
/// the emit JS function name
pub fn emit_function_name() -> String {
EMIT_FUNCTION_NAME.to_string()
}
/// the event listeners JS object name
pub fn event_listeners_object_name() -> String {
EVENT_LISTENERS_OBJECT_NAME.to_string()
}
/// the event queue JS object name
pub fn event_queue_object_name() -> String {
EVENT_QUEUE_OBJECT_NAME.to_string()
}
#[derive(Debug, Clone)]
pub struct EventPayload {
id: EventId,
payload: Option<String>,
}
impl EventPayload {
/// The event identifier.
pub fn id(&self) -> EventId {
self.id
}
/// The event payload.
pub fn payload(&self) -> Option<&String> {
self.payload.as_ref()
}
}
/// Adds an event listener for JS events.
pub fn listen<F: Fn(EventPayload) + Send + 'static>(
event_name: impl AsRef<str>,
window_label: Option<String>,
handler: F,
) -> EventId {
let mut l = listeners()
.lock()
.expect("Failed to lock listeners: listen()");
let id = rand::random();
let handler = EventHandler {
id,
window_label,
on_event: Box::new(handler),
};
if let Some(listeners) = l.get_mut(event_name.as_ref()) {
listeners.push(handler);
} else {
l.insert(event_name.as_ref().to_string(), vec![handler]);
}
id
}
/// Listen to an JS event and immediately unlisten.
pub fn once<F: Fn(EventPayload) + Send + 'static>(
event_name: impl AsRef<str>,
window_label: Option<String>,
handler: F,
) {
listen(event_name, window_label, move |event| {
unlisten(event.id);
handler(event);
});
}
/// Removes an event listener.
pub fn unlisten(event_id: EventId) {
crate::async_runtime::spawn(async move {
let mut event_listeners = listeners()
.lock()
.expect("Failed to lock listeners: listen()");
for listeners in event_listeners.values_mut() {
if let Some(index) = listeners.iter().position(|l| l.id == event_id) {
listeners.remove(index);
}
}
})
}
/// Emits an event to JS.
pub fn emit<D: ApplicationDispatcherExt, S: Serialize>(
webview_dispatcher: &crate::WebviewDispatcher<D>,
event: impl AsRef<str>,
payload: Option<S>,
) -> crate::Result<()> {
let salt = crate::salt::generate();
let js_payload = if let Some(payload_value) = payload {
serde_json::to_value(payload_value)?
} else {
JsonValue::Null
};
webview_dispatcher.eval(&format!(
"window['{}']({{event: '{}', payload: {}}}, '{}')",
emit_function_name(),
event.as_ref(),
js_payload,
salt
))?;
Ok(())
}
/// Triggers the given event with its payload.
pub(crate) fn on_event(event: String, window_label: Option<&str>, data: Option<String>) {
let mut l = listeners()
.lock()
.expect("Failed to lock listeners: on_event()");
if l.contains_key(&event) {
let listeners = l.get_mut(&event).expect("Failed to get mutable handler");
for handler in listeners {
if let Some(target_window_label) = window_label {
// if the emitted event targets a specifid window, only triggers the listeners associated to that window
if handler.window_label.as_deref() == Some(target_window_label) {
let payload = data.clone();
(handler.on_event)(EventPayload {
id: handler.id,
payload,
});
}
} else {
// otherwise triggers all listeners
let payload = data.clone();
(handler.on_event)(EventPayload {
id: handler.id,
payload,
});
}
}
}
}
#[cfg(test)]
mod test {
use super::*;
use proptest::prelude::*;
// dummy event handler function
fn event_fn(s: EventPayload) {
println!("{:?}", s);
}
proptest! {
#![proptest_config(ProptestConfig::with_cases(10000))]
#[test]
// check to see if listen() is properly passing keys into the LISTENERS map
fn listeners_check_key(e in "[a-z]+") {
// clone e as the key
let key = e.clone();
// pass e and an dummy func into listen
listen(e, None, event_fn);
// lock mutex
let l = listeners().lock().unwrap();
// check if the generated key is in the map
assert_eq!(l.contains_key(&key), true);
}
#[test]
// check to see if listen inputs a handler function properly into the LISTENERS map.
fn listeners_check_fn(e in "[a-z]+") {
// clone e as the key
let key = e.clone();
// pass e and an dummy func into listen
listen(e, None, event_fn);
// lock mutex
let mut l = listeners().lock().unwrap();
// check if l contains key
if l.contains_key(&key) {
// grab key if it exists
let handler = l.get_mut(&key);
// check to see if we get back a handler or not
match handler {
// pass on Some(handler)
Some(_) => {},
// Fail on None
None => panic!("handler is None")
}
}
}
#[test]
// check to see if on_event properly grabs the stored function from listen.
fn check_on_event(e in "[a-z]+", d in "[a-z]+") {
// clone e as the key
let key = e.clone();
// call listen with e and the event_fn dummy func
listen(e.clone(), None, event_fn);
// call on event with e and d.
on_event(e, None, Some(d));
// lock the mutex
let l = listeners().lock().unwrap();
// assert that the key is contained in the listeners map
assert!(l.contains_key(&key));
}
}
}

View File

@ -1,303 +0,0 @@
use crate::{
api::{assets::Assets, config::WindowUrl},
app::Icon,
ApplicationExt, WebviewBuilderExt,
};
use super::{
webview::{CustomProtocol, WebviewBuilderExtPrivate, WebviewRpcHandler},
App, Context, InvokeMessage, InvokePayload, PageLoadPayload, RpcRequest, Webview, WebviewManager,
};
use serde_json::Value as JsonValue;
use std::{
borrow::Cow,
sync::{Arc, Mutex},
};
// setup content for dev-server
#[cfg(dev)]
pub(super) fn get_url(context: &Context) -> String {
let config = &context.config;
if config.build.dev_path.starts_with("http") {
config.build.dev_path.clone()
} else {
let path = "index.html";
format!(
"data:text/html;base64,{}",
base64::encode(
context
.assets
.get(&path)
.ok_or_else(|| crate::Error::AssetNotFound(path.to_string()))
.map(Cow::into_owned)
.expect("Unable to find `index.html` under your devPath folder")
)
)
}
}
#[cfg(custom_protocol)]
pub(super) fn get_url(context: &Context) -> String {
// Custom protocol doesn't require any setup, so just return URL
format!("tauri://{}", context.config.tauri.bundle.identifier)
}
pub(super) fn initialization_script(
plugin_initialization_script: &str,
with_global_tauri: bool,
) -> String {
format!(
r#"
{bundle_script}
{core_script}
{event_initialization_script}
if (window.rpc) {{
window.__TAURI__.invoke("__initialized", {{ url: window.location.href }})
}} else {{
window.addEventListener('DOMContentLoaded', function () {{
window.__TAURI__.invoke("__initialized", {{ url: window.location.href }})
}})
}}
{plugin_initialization_script}
"#,
core_script = include_str!("../../scripts/core.js"),
bundle_script = if with_global_tauri {
include_str!("../../scripts/bundle.js")
} else {
""
},
event_initialization_script = event_initialization_script(),
plugin_initialization_script = plugin_initialization_script
)
}
fn event_initialization_script() -> String {
return format!(
"
window['{queue}'] = [];
window['{fn}'] = function (eventData, salt, ignoreQueue) {{
const listeners = (window['{listeners}'] && window['{listeners}'][eventData.event]) || []
if (!ignoreQueue && listeners.length === 0) {{
window['{queue}'].push({{
eventData: eventData,
salt: salt
}})
}}
if (listeners.length > 0) {{
window.__TAURI__.invoke('tauri', {{
__tauriModule: 'Internal',
message: {{
cmd: 'validateSalt',
salt: salt
}}
}}).then(function (flag) {{
if (flag) {{
for (let i = listeners.length - 1; i >= 0; i--) {{
const listener = listeners[i]
eventData.id = listener.id
listener.handler(eventData)
}}
}}
}})
}}
}}
",
fn = crate::event::emit_function_name(),
queue = crate::event::event_queue_object_name(),
listeners = crate::event::event_listeners_object_name()
);
}
pub(super) type BuiltWebview<A> = (
<A as ApplicationExt>::WebviewBuilder,
Option<WebviewRpcHandler<<A as ApplicationExt>::Dispatcher>>,
Option<CustomProtocol>,
);
// build the webview.
pub(super) fn build_webview<A: ApplicationExt + 'static>(
application: Arc<Mutex<App<A>>>,
webview: Webview<A>,
webview_manager: &WebviewManager<A>,
content_url: &str,
window_labels: &[String],
plugin_initialization_script: &str,
context: &Context,
) -> crate::Result<BuiltWebview<A>> {
let webview_url = match &webview.url {
WindowUrl::App => content_url.to_string(),
WindowUrl::Custom(url) => url.to_string(),
};
let is_local = match webview.url {
WindowUrl::App => true,
WindowUrl::Custom(url) => &url[0..8] == "tauri://",
};
let (webview_builder, rpc_handler, custom_protocol) = if is_local {
let mut webview_builder = webview.builder.url(webview_url)
.initialization_script(&initialization_script(plugin_initialization_script, context.config.build.with_global_tauri))
.initialization_script(&format!(
r#"
window.__TAURI__.__windows = {window_labels_array}.map(function (label) {{ return {{ label: label }} }});
window.__TAURI__.__currentWindow = {{ label: "{current_window_label}" }}
"#,
window_labels_array =
serde_json::to_string(&window_labels).unwrap(),
current_window_label = webview.label,
));
if !webview_builder.has_icon() {
if let Some(default_window_icon) = &context.default_window_icon {
webview_builder = webview_builder.icon(Icon::Raw(default_window_icon.to_vec()))?;
}
}
let webview_manager_ = webview_manager.clone();
let rpc_handler: Box<dyn Fn(<A as ApplicationExt>::Dispatcher, RpcRequest) + Send> =
Box::new(move |_, request: RpcRequest| {
let command = request.command.clone();
let arg = request
.params
.unwrap()
.as_array_mut()
.unwrap()
.first_mut()
.unwrap_or(&mut JsonValue::Null)
.take();
let webview_manager = webview_manager_.clone();
match serde_json::from_value::<InvokePayload>(arg) {
Ok(message) => {
let _ = on_message(application.clone(), webview_manager, command, message);
}
Err(e) => {
if let Ok(dispatcher) = webview_manager.current_webview() {
let error: crate::Error = e.into();
let _ = dispatcher.eval(&format!(
r#"console.error({})"#,
JsonValue::String(error.to_string())
));
}
}
}
});
let bundle_identifier = context.config.tauri.bundle.identifier.clone();
#[cfg(debug_assertions)]
let dist_dir = std::path::PathBuf::from(context.config.build.dist_dir.clone());
#[cfg(not(debug_assertions))]
let assets = context.assets;
let custom_protocol = CustomProtocol {
name: "tauri".into(),
handler: Box::new(move |path| {
let mut path = path
.split('?')
// ignore query string
.next()
.unwrap()
.to_string()
.replace(&format!("tauri://{}", bundle_identifier), "");
if path.ends_with('/') {
path.pop();
}
let path = if path.is_empty() {
// if the url is `tauri://${appId}`, we should load `index.html`
"index.html".to_string()
} else {
// skip leading `/`
path.chars().skip(1).collect::<String>()
};
// In development builds, resolve, read and directly serve assets in the configured dist folder.
#[cfg(debug_assertions)]
{
dist_dir
.canonicalize()
.or_else(|_| Err(crate::Error::AssetNotFound(path.clone())))
.and_then(|pathbuf| {
pathbuf
.join(path.clone())
.canonicalize()
.or_else(|_| Err(crate::Error::AssetNotFound(path.clone())))
.and_then(|pathbuf| {
match std::fs::read(pathbuf) {
Ok(asset) => Ok(asset),
Err(e) => {
eprintln!("Error reading asset from dist: {:?}", e); // TODO log::error!
Err(crate::Error::AssetNotFound(path.clone()))
}
}
})
})
}
// In release builds, fetch + serve decompressed embedded assets.
#[cfg(not(debug_assertions))]
{
assets
.get(&path)
.ok_or(crate::Error::AssetNotFound(path))
.map(Cow::into_owned)
}
}),
};
(webview_builder, Some(rpc_handler), Some(custom_protocol))
} else {
(webview.builder.url(webview_url), None, None)
};
Ok((webview_builder, rpc_handler, custom_protocol))
}
fn on_message<A: ApplicationExt + 'static>(
application: Arc<Mutex<App<A>>>,
webview_manager: WebviewManager<A>,
command: String,
payload: InvokePayload,
) -> crate::Result<()> {
let message = InvokeMessage::new(webview_manager.clone(), command.to_string(), payload);
if &command == "__initialized" {
let payload: PageLoadPayload = serde_json::from_value(message.payload())?;
application
.lock()
.unwrap()
.run_on_page_load(&webview_manager, payload.clone());
crate::plugin::on_page_load(A::plugin_store(), &webview_manager, payload);
} else if let Some(module) = &message.payload.tauri_module {
let module = module.to_string();
crate::endpoints::handle(
&webview_manager,
module,
message,
&application.lock().unwrap().context,
);
} else if command.starts_with("plugin:") {
crate::plugin::extend_api(A::plugin_store(), &webview_manager, command, message);
} else {
application
.lock()
.unwrap()
.run_invoke_handler(&webview_manager, message);
}
Ok(())
}
#[cfg(test)]
mod test {
use crate::{generate_context, Context};
#[test]
fn check_get_url() {
let context = generate_context!("test/fixture/src-tauri/tauri.conf.json");
let context = Context::new(context);
let res = super::get_url(&context);
#[cfg(custom_protocol)]
assert!(res == "tauri://studio.tauri.example");
#[cfg(dev)]
{
let config = &context.config;
assert_eq!(res, config.build.dev_path);
}
}
}

View File

@ -1,303 +0,0 @@
pub mod wry;
use crate::plugin::PluginStore;
use std::path::PathBuf;
use serde_json::Value as JsonValue;
/// A icon definition.
pub enum Icon {
/// Icon from file path.
File(String),
/// Icon from raw bytes.
Raw(Vec<u8>),
}
/// Messages to dispatch to the application.
pub enum Message {
// webview messages
/// Eval a script on the webview.
EvalScript(String),
// window messages
/// Updates the window resizable flag.
SetResizable(bool),
/// Updates the window title.
SetTitle(String),
/// Maximizes the window.
Maximize,
/// Unmaximizes the window.
Unmaximize,
/// Minimizes the window.
Minimize,
/// Unminimizes the window.
Unminimize,
/// Shows the window.
Show,
/// Hides the window.
Hide,
/// Updates the hasDecorations flag.
SetDecorations(bool),
/// Updates the window alwaysOnTop flag.
SetAlwaysOnTop(bool),
/// Updates the window width.
SetWidth(f64),
/// Updates the window height.
SetHeight(f64),
/// Resizes the window.
Resize {
/// New width.
width: f64,
/// New height.
height: f64,
},
/// Updates the window min size.
SetMinSize {
/// New value for the window min width.
min_width: f64,
/// New value for the window min height.
min_height: f64,
},
/// Updates the window max size.
SetMaxSize {
/// New value for the window max width.
max_width: f64,
/// New value for the window max height.
max_height: f64,
},
/// Updates the X position.
SetX(f64),
/// Updates the Y position.
SetY(f64),
/// Updates the window position.
SetPosition {
/// New value for the window X coordinate.
x: f64,
/// New value for the window Y coordinate.
y: f64,
},
/// Updates the window fullscreen state.
SetFullscreen(bool),
/// Updates the window icon.
SetIcon(Icon),
}
pub struct WindowConfig(pub crate::api::config::WindowConfig);
pub trait WebviewBuilderExtPrivate: Sized {
/// Sets the webview url.
fn url(self, url: String) -> Self;
}
/// The webview builder.
pub trait WebviewBuilderExt: Sized {
/// The webview object that this builder creates.
type Webview;
/// Initializes a new webview builder.
fn new() -> Self;
/// Sets the init script.
fn initialization_script(self, init: &str) -> Self;
/// The horizontal position of the window's top left corner.
fn x(self, x: f64) -> Self;
/// The vertical position of the window's top left corner.
fn y(self, y: f64) -> Self;
/// Window width.
fn width(self, width: f64) -> Self;
/// Window height.
fn height(self, height: f64) -> Self;
/// Window min width.
fn min_width(self, min_width: f64) -> Self;
/// Window min height.
fn min_height(self, min_height: f64) -> Self;
/// Window max width.
fn max_width(self, max_width: f64) -> Self;
/// Window max height.
fn max_height(self, max_height: f64) -> 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<S: Into<String>>(self, title: S) -> Self;
/// Whether to start the window in fullscreen or not.
fn fullscreen(self, fullscreen: bool) -> 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;
/// Sets the window icon.
fn icon(self, icon: Icon) -> crate::Result<Self>;
/// Whether the icon was set or not.
fn has_icon(&self) -> bool;
/// User data path for the webview. Actually only supported on Windows.
fn user_data_path(self, user_data_path: Option<PathBuf>) -> Self;
/// Builds the webview instance.
fn finish(self) -> crate::Result<Self::Webview>;
}
/// Rpc request.
pub struct RpcRequest {
/// RPC command.
pub command: String,
/// Params.
pub params: Option<JsonValue>,
}
/// Rpc handler.
pub type WebviewRpcHandler<D> = Box<dyn Fn(D, RpcRequest) + Send>;
/// Uses a custom handler to resolve file requests
pub struct CustomProtocol {
/// Name of the protocol
pub name: String,
/// Handler for protocol
pub handler: Box<dyn Fn(&str) -> crate::Result<Vec<u8>> + Send>,
}
/// The file drop event payload.
#[derive(Debug, Clone)]
pub enum FileDropEvent {
/// The file(s) have been dragged onto the window, but have not been dropped yet.
Hovered(Vec<PathBuf>),
/// The file(s) have been dropped onto the window.
Dropped(Vec<PathBuf>),
/// The file drop was aborted.
Cancelled,
}
/// File drop handler callback
/// Return `true` in the callback to block the OS' default behavior of handling a file drop..
pub type FileDropHandler = Box<dyn Fn(FileDropEvent) -> bool + Send>;
/// Webview dispatcher. A thread-safe handle to the webview API.
pub trait ApplicationDispatcherExt: Clone + Send + Sized {
/// The webview builder type.
type WebviewBuilder: WebviewBuilderExt + WebviewBuilderExtPrivate + From<WindowConfig> + Send;
/// Creates a webview.
fn create_webview(
&self,
webview_builder: Self::WebviewBuilder,
rpc_handler: Option<WebviewRpcHandler<Self>>,
custom_protocol: Option<CustomProtocol>,
file_drop_handler: Option<FileDropHandler>,
) -> crate::Result<Self>;
/// Updates the window resizable flag.
fn set_resizable(&self, resizable: bool) -> crate::Result<()>;
/// Updates the window title.
fn set_title<S: Into<String>>(&self, title: S) -> crate::Result<()>;
/// Maximizes the window.
fn maximize(&self) -> crate::Result<()>;
/// Unmaximizes the window.
fn unmaximize(&self) -> crate::Result<()>;
/// Minimizes the window.
fn minimize(&self) -> crate::Result<()>;
/// Unminimizes the window.
fn unminimize(&self) -> crate::Result<()>;
/// Shows the window.
fn show(&self) -> crate::Result<()>;
/// Hides the window.
fn hide(&self) -> crate::Result<()>;
/// Closes the window.
fn close(&self) -> crate::Result<()>;
/// Updates the hasDecorations flag.
fn set_decorations(&self, decorations: bool) -> crate::Result<()>;
/// Updates the window alwaysOnTop flag.
fn set_always_on_top(&self, always_on_top: bool) -> crate::Result<()>;
/// Updates the window width.
fn set_width(&self, width: f64) -> crate::Result<()>;
/// Updates the window height.
fn set_height(&self, height: f64) -> crate::Result<()>;
/// Resizes the window.
fn resize(&self, width: f64, height: f64) -> crate::Result<()>;
/// Updates the window min size.
fn set_min_size(&self, min_width: f64, min_height: f64) -> crate::Result<()>;
/// Updates the window max size.
fn set_max_size(&self, max_width: f64, max_height: f64) -> crate::Result<()>;
/// Updates the X position.
fn set_x(&self, x: f64) -> crate::Result<()>;
/// Updates the Y position.
fn set_y(&self, y: f64) -> crate::Result<()>;
/// Updates the window position.
fn set_position(&self, x: f64, y: f64) -> crate::Result<()>;
/// Updates the window fullscreen state.
fn set_fullscreen(&self, fullscreen: bool) -> crate::Result<()>;
/// Updates the window icon.
fn set_icon(&self, icon: Icon) -> crate::Result<()>;
/// Evals a script on the webview.
fn eval_script<S: Into<String>>(&self, script: S) -> crate::Result<()>;
}
/// The application interface.
/// Manages windows and webviews.
pub trait ApplicationExt: Sized {
/// The webview builder.
type WebviewBuilder: WebviewBuilderExt + WebviewBuilderExtPrivate + From<WindowConfig> + Send;
/// The message dispatcher.
type Dispatcher: ApplicationDispatcherExt<WebviewBuilder = Self::WebviewBuilder>;
/// Returns the static plugin collection.
fn plugin_store() -> &'static PluginStore<Self>;
/// Creates a new application.
fn new() -> crate::Result<Self>;
/// Creates a new webview.
fn create_webview(
&mut self,
webview_builder: Self::WebviewBuilder,
rpc_handler: Option<WebviewRpcHandler<Self::Dispatcher>>,
custom_protocol: Option<CustomProtocol>,
file_drop_handler: Option<FileDropHandler>,
) -> crate::Result<Self::Dispatcher>;
/// Run the application.
fn run(self);
}

View File

@ -1,347 +0,0 @@
use std::{
collections::HashMap,
future::Future,
sync::{Arc, Mutex},
};
use super::{
event::{EventId, EventPayload},
App, ApplicationDispatcherExt, ApplicationExt, Icon, Webview, WebviewBuilderExt,
WebviewInitializer,
};
use crate::{api::config::WindowUrl, flavors::Wry};
use serde::Serialize;
/// The webview dispatcher.
#[derive(Clone)]
pub struct WebviewDispatcher<A: Clone> {
dispatcher: A,
window_label: String,
}
impl<A: ApplicationDispatcherExt> WebviewDispatcher<A> {
pub(crate) fn new(dispatcher: A, window_label: String) -> Self {
Self {
dispatcher,
window_label,
}
}
/// The label of the window tied to this dispatcher.
pub fn window_label(&self) -> &str {
&self.window_label
}
/// Listen to a webview event.
pub fn listen<F: Fn(EventPayload) + Send + 'static>(
&self,
event: impl AsRef<str>,
handler: F,
) -> EventId {
super::event::listen(event, Some(self.window_label.to_string()), handler)
}
/// Listen to a webview event and unlisten after the first event.
pub fn once<F: Fn(EventPayload) + Send + 'static>(&self, event: impl AsRef<str>, handler: F) {
super::event::once(event, Some(self.window_label.to_string()), handler)
}
/// Unregister the event listener with the given id.
pub fn unlisten(&self, event_id: EventId) {
super::event::unlisten(event_id)
}
/// Emits an event to the webview.
pub fn emit<S: Serialize>(
&self,
event: impl AsRef<str>,
payload: Option<S>,
) -> crate::Result<()> {
super::event::emit(&self, event, payload)
}
/// Emits an event from the webview.
pub(crate) fn on_event(&self, event: String, data: Option<String>) {
super::event::on_event(event, Some(&self.window_label), data)
}
/// Evaluates a JS script.
pub fn eval(&self, js: &str) -> crate::Result<()> {
self.dispatcher.eval_script(js)
}
/// Updates the window resizable flag.
pub fn set_resizable(&self, resizable: bool) -> crate::Result<()> {
self.dispatcher.set_resizable(resizable)
}
/// Updates the window title.
pub fn set_title(&self, title: &str) -> crate::Result<()> {
self.dispatcher.set_title(title.to_string())
}
/// Maximizes the window.
pub fn maximize(&self) -> crate::Result<()> {
self.dispatcher.maximize()
}
/// Unmaximizes the window.
pub fn unmaximize(&self) -> crate::Result<()> {
self.dispatcher.unmaximize()
}
/// Minimizes the window.
pub fn minimize(&self) -> crate::Result<()> {
self.dispatcher.minimize()
}
/// Unminimizes the window.
pub fn unminimize(&self) -> crate::Result<()> {
self.dispatcher.unminimize()
}
/// Sets the window visibility to true.
pub fn show(&self) -> crate::Result<()> {
self.dispatcher.show()
}
/// Sets the window visibility to false.
pub fn hide(&self) -> crate::Result<()> {
self.dispatcher.hide()
}
/// Closes the window.
pub fn close(&self) -> crate::Result<()> {
self.dispatcher.close()
}
/// Whether the window should have borders and bars.
pub fn set_decorations(&self, decorations: bool) -> crate::Result<()> {
self.dispatcher.set_decorations(decorations)
}
/// Whether the window should always be on top of other windows.
pub fn set_always_on_top(&self, always_on_top: bool) -> crate::Result<()> {
self.dispatcher.set_always_on_top(always_on_top)
}
/// Sets the window width.
pub fn set_width(&self, width: impl Into<f64>) -> crate::Result<()> {
self.dispatcher.set_width(width.into())
}
/// Sets the window height.
pub fn set_height(&self, height: impl Into<f64>) -> crate::Result<()> {
self.dispatcher.set_height(height.into())
}
/// Resizes the window.
pub fn resize(&self, width: impl Into<f64>, height: impl Into<f64>) -> crate::Result<()> {
self.dispatcher.resize(width.into(), height.into())
}
/// Sets the window min size.
pub fn set_min_size(
&self,
min_width: impl Into<f64>,
min_height: impl Into<f64>,
) -> crate::Result<()> {
self
.dispatcher
.set_min_size(min_width.into(), min_height.into())
}
/// Sets the window max size.
pub fn set_max_size(
&self,
max_width: impl Into<f64>,
max_height: impl Into<f64>,
) -> crate::Result<()> {
self
.dispatcher
.set_max_size(max_width.into(), max_height.into())
}
/// Sets the window x position.
pub fn set_x(&self, x: impl Into<f64>) -> crate::Result<()> {
self.dispatcher.set_x(x.into())
}
/// Sets the window y position.
pub fn set_y(&self, y: impl Into<f64>) -> crate::Result<()> {
self.dispatcher.set_y(y.into())
}
/// Sets the window position.
pub fn set_position(&self, x: impl Into<f64>, y: impl Into<f64>) -> crate::Result<()> {
self.dispatcher.set_position(x.into(), y.into())
}
/// Sets the window fullscreen state.
pub fn set_fullscreen(&self, fullscreen: bool) -> crate::Result<()> {
self.dispatcher.set_fullscreen(fullscreen)
}
/// Sets the window icon.
pub fn set_icon(&self, icon: Icon) -> crate::Result<()> {
self.dispatcher.set_icon(icon)
}
}
/// The webview manager.
pub struct WebviewManager<A = Wry>
where
A: ApplicationExt,
{
application: Arc<Mutex<App<A>>>,
dispatchers: Arc<Mutex<HashMap<String, WebviewDispatcher<A::Dispatcher>>>>,
current_webview_window_label: String,
}
impl<A: ApplicationExt> Clone for WebviewManager<A> {
fn clone(&self) -> Self {
Self {
application: self.application.clone(),
dispatchers: self.dispatchers.clone(),
current_webview_window_label: self.current_webview_window_label.to_string(),
}
}
}
impl<A: ApplicationExt + 'static> WebviewManager<A> {
pub(crate) fn new(
application: Arc<Mutex<App<A>>>,
dispatchers: Arc<Mutex<HashMap<String, WebviewDispatcher<A::Dispatcher>>>>,
label: String,
) -> Self {
Self {
application,
dispatchers,
current_webview_window_label: label,
}
}
/// Spawns an asynchronous task
pub fn spawn<F>(task: F)
where
F: Future + Send + 'static,
F::Output: Send + 'static,
{
crate::async_runtime::spawn(task)
}
/// Returns the label of the window associated with the current context.
pub fn current_window_label(&self) -> &str {
&self.current_webview_window_label
}
/// Gets the webview associated with the current context.
pub fn current_webview(&self) -> crate::Result<WebviewDispatcher<A::Dispatcher>> {
self.get_webview(&self.current_webview_window_label)
}
/// Gets the webview associated with the given window label.
pub fn get_webview(&self, window_label: &str) -> crate::Result<WebviewDispatcher<A::Dispatcher>> {
self
.dispatchers
.lock()
.unwrap()
.get(window_label)
.ok_or(crate::Error::WebviewNotFound)
.map(|d| d.clone())
}
/// Creates a new webview.
pub async fn create_webview<F: FnOnce(A::WebviewBuilder) -> crate::Result<A::WebviewBuilder>>(
&self,
label: String,
url: WindowUrl,
f: F,
) -> crate::Result<WebviewDispatcher<A::Dispatcher>> {
let builder = f(A::WebviewBuilder::new())?;
let webview = Webview {
url,
label: label.to_string(),
builder,
};
self
.application
.lock()
.unwrap()
.window_labels
.lock()
.unwrap()
.push(label.to_string());
let (webview_builder, rpc_handler, custom_protocol, file_drop_handler) =
self.application.init_webview(webview)?;
let window_dispatcher = self.current_webview()?.dispatcher.create_webview(
webview_builder,
rpc_handler,
custom_protocol,
file_drop_handler,
)?;
let webview_manager = Self::new(
self.application.clone(),
self.dispatchers.clone(),
label.to_string(),
);
self
.application
.on_webview_created(label.to_string(), window_dispatcher.clone());
crate::plugin::created(A::plugin_store(), &webview_manager);
Ok(WebviewDispatcher::new(window_dispatcher, label))
}
/// Listen to a global event.
/// An event from any webview will trigger the handler.
pub fn listen<F: Fn(EventPayload) + Send + 'static>(
&self,
event: impl AsRef<str>,
handler: F,
) -> EventId {
super::event::listen(event, None, handler)
}
/// Listen to a global event and unlisten after the first event.
pub fn once<F: Fn(EventPayload) + Send + 'static>(&self, event: impl AsRef<str>, handler: F) {
super::event::once(event, None, handler)
}
/// Unregister the global event listener with the given id.
pub fn unlisten(&self, event_id: EventId) {
super::event::unlisten(event_id)
}
/// Emits an event to all webviews.
pub fn emit<S: Serialize + Clone>(
&self,
event: impl AsRef<str>,
payload: Option<S>,
) -> crate::Result<()> {
for dispatcher in self.dispatchers.lock().unwrap().values() {
super::event::emit(&dispatcher, event.as_ref(), payload.clone())?;
}
Ok(())
}
pub(crate) fn emit_except<S: Serialize + Clone>(
&self,
except_label: String,
event: impl AsRef<str>,
payload: Option<S>,
) -> crate::Result<()> {
for dispatcher in self.dispatchers.lock().unwrap().values() {
if dispatcher.window_label != except_label {
super::event::emit(&dispatcher, event.as_ref(), payload.clone())?;
}
}
Ok(())
}
/// Emits a global event from the webview.
pub(crate) fn on_event(&self, event: String, data: Option<String>) {
super::event::on_event(event, None, data)
}
}

View File

@ -1,3 +1,7 @@
use crate::{api::config::Config, hooks::InvokeMessage, runtime::Params};
use serde::{Deserialize, Serialize};
use serde_json::Value as JsonValue;
mod cli;
mod dialog;
mod event;
@ -10,11 +14,6 @@ mod notification;
mod shell;
mod window;
use crate::{app::Context, ApplicationExt, InvokeMessage};
use serde::{Deserialize, Serialize};
use serde_json::Value as JsonValue;
/// The response for a JS `invoke` call.
pub struct InvokeResponse {
json: crate::Result<JsonValue>,
@ -44,31 +43,27 @@ enum Module {
}
impl Module {
fn run<A: ApplicationExt + 'static>(
self,
webview_manager: crate::WebviewManager<A>,
message: InvokeMessage<A>,
context: &Context,
) {
fn run<M: Params>(self, message: InvokeMessage<M>, config: &Config) {
let window = message.window();
match self {
Self::Fs(cmd) => message
.respond_async(async move { cmd.run().and_then(|r| r.json).map_err(|e| e.to_string()) }),
Self::Window(cmd) => message.respond_async(async move {
cmd
.run(&webview_manager)
.run(window)
.await
.and_then(|r| r.json)
.map_err(|e| e.to_string())
}),
Self::Shell(cmd) => message.respond_async(async move {
cmd
.run(webview_manager)
.run(window)
.and_then(|r| r.json)
.map_err(|e| e.to_string())
}),
Self::Event(cmd) => message.respond_async(async move {
cmd
.run(&webview_manager)
.run(window)
.and_then(|r| r.json)
.map_err(|e| e.to_string())
}),
@ -77,7 +72,7 @@ impl Module {
Self::Dialog(cmd) => message
.respond_async(async move { cmd.run().and_then(|r| r.json).map_err(|e| e.to_string()) }),
Self::Cli(cmd) => {
if let Some(cli_config) = context.config.tauri.cli.clone() {
if let Some(cli_config) = config.tauri.cli.clone() {
message.respond_async(async move {
cmd
.run(&cli_config)
@ -87,7 +82,7 @@ impl Module {
}
}
Self::Notification(cmd) => {
let identifier = context.config.tauri.bundle.identifier.clone();
let identifier = config.tauri.bundle.identifier.clone();
message.respond_async(async move {
cmd
.run(identifier)
@ -104,7 +99,7 @@ impl Module {
}),
Self::GlobalShortcut(cmd) => message.respond_async(async move {
cmd
.run(&webview_manager)
.run(window)
.and_then(|r| r.json)
.map_err(|e| e.to_string())
}),
@ -112,18 +107,13 @@ impl Module {
}
}
pub(crate) fn handle<A: ApplicationExt + 'static>(
webview_manager: &crate::WebviewManager<A>,
module: String,
message: InvokeMessage<A>,
context: &Context,
) {
pub(crate) fn handle<M: Params>(module: String, message: InvokeMessage<M>, config: &Config) {
let mut payload = message.payload();
if let JsonValue::Object(ref mut obj) = payload {
obj.insert("module".to_string(), JsonValue::String(module));
}
match serde_json::from_value::<Module>(payload) {
Ok(module) => module.run(webview_manager.clone(), message, context),
Ok(module) => module.run(message, config),
Err(e) => message.reject(e.to_string()),
}
}

View File

@ -1,4 +1,7 @@
use super::InvokeResponse;
use crate::{
endpoints::InvokeResponse,
runtime::{sealed::ManagerPrivate, window::Window, Manager, Params},
};
use serde::Deserialize;
/// The API descriptor.
@ -21,22 +24,15 @@ pub enum Cmd {
}
impl Cmd {
pub fn run<A: crate::ApplicationExt + 'static>(
self,
webview_manager: &crate::WebviewManager<A>,
) -> crate::Result<InvokeResponse> {
pub fn run<M: Params>(self, window: Window<M>) -> crate::Result<InvokeResponse> {
match self {
Self::Listen { event, handler } => {
let event_id = rand::random();
webview_manager
.current_webview()?
.eval(&listen_js(event, event_id, handler))?;
window.eval(&listen_js(&window, event, event_id, handler))?;
Ok(event_id.into())
}
Self::Unlisten { event_id } => {
webview_manager
.current_webview()?
.eval(&unlisten_js(event_id))?;
window.eval(&unlisten_js(&window, event_id))?;
Ok(().into())
}
Self::Emit {
@ -44,17 +40,22 @@ impl Cmd {
window_label,
payload,
} => {
if let Some(label) = window_label {
let dispatcher = webview_manager.get_webview(&label)?;
// dispatch the event to Rust listeners
dispatcher.on_event(event.to_string(), payload.clone());
// dispatch the event to JS listeners
dispatcher.emit(event, payload)?;
let e: M::Event = event
.parse()
.unwrap_or_else(|_| panic!("todo: invalid event str"));
let window_label: Option<M::Label> = window_label.map(|l| {
l.parse()
.unwrap_or_else(|_| panic!("todo: invalid window label"))
});
// dispatch the event to Rust listeners
window.trigger(e.clone(), payload.clone());
if let Some(target) = window_label {
window.emit_to(&target, e, payload)?;
} else {
// dispatch the event to Rust listeners
webview_manager.on_event(event.to_string(), payload.clone());
// dispatch the event to JS listeners
webview_manager.emit(event, payload)?;
window.emit_all(e, payload)?;
}
Ok(().into())
}
@ -62,7 +63,7 @@ impl Cmd {
}
}
pub fn unlisten_js(event_id: u64) -> String {
pub fn unlisten_js<M: Params>(window: &Window<M>, event_id: u64) -> String {
format!(
"
for (var event in (window['{listeners}'] || {{}})) {{
@ -72,12 +73,17 @@ pub fn unlisten_js(event_id: u64) -> String {
}}
}}
",
listeners = crate::app::event::event_listeners_object_name(),
listeners = window.manager().event_listeners_object_name(),
event_id = event_id,
)
}
pub fn listen_js(event: String, event_id: u64, handler: String) -> String {
pub fn listen_js<M: Params>(
window: &Window<M>,
event: String,
event_id: u64,
handler: String,
) -> String {
format!(
"if (window['{listeners}'] === void 0) {{
window['{listeners}'] = {{}}
@ -95,24 +101,11 @@ pub fn listen_js(event: String, event_id: u64, handler: String) -> String {
window['{emit}'](e.eventData, e.salt, true)
}}
",
listeners = crate::app::event::event_listeners_object_name(),
queue = crate::app::event::event_queue_object_name(),
emit = crate::app::event::emit_function_name(),
listeners = window.manager().event_listeners_object_name(),
queue = window.manager().event_queue_object_name(),
emit = window.manager().event_emit_function_name(),
event = event,
event_id = event_id,
handler = handler
)
}
#[cfg(test)]
mod test {
use proptest::prelude::*;
// check the listen_js for various usecases.
proptest! {
#[test]
fn check_listen_js(event in "", id in proptest::bits::u64::ANY, handler in "") {
super::listen_js(event, id, handler);
}
}
}

View File

@ -1,5 +1,5 @@
use super::InvokeResponse;
use crate::{api::path::BaseDirectory, ApplicationDispatcherExt};
use crate::api::path::BaseDirectory;
use serde::{Deserialize, Serialize};
use tauri_api::{dir, file, path::resolve_path};

View File

@ -1,12 +1,12 @@
use super::InvokeResponse;
#[cfg(global_shortcut_all)]
use crate::api::shortcuts::ShortcutManager;
use crate::app::WebviewDispatcher;
use crate::runtime::{window::Window, Dispatch, Params};
use once_cell::sync::Lazy;
use serde::Deserialize;
use std::sync::{Arc, Mutex};
#[cfg(global_shortcut_all)]
use crate::api::shortcuts::ShortcutManager;
#[cfg(global_shortcut_all)]
type ShortcutManagerHandle = Arc<Mutex<ShortcutManager>>;
@ -36,8 +36,8 @@ pub enum Cmd {
}
#[cfg(global_shortcut_all)]
fn register_shortcut<A: crate::ApplicationDispatcherExt + 'static>(
dispatcher: WebviewDispatcher<A>,
fn register_shortcut<D: Dispatch>(
dispatcher: D,
manager: &mut ShortcutManager,
shortcut: String,
handler: String,
@ -47,33 +47,36 @@ fn register_shortcut<A: crate::ApplicationDispatcherExt + 'static>(
handler.to_string(),
serde_json::Value::String(shortcut.clone()),
);
let _ = dispatcher.eval(callback_string.as_str());
let _ = dispatcher.eval_script(callback_string.as_str());
})?;
Ok(())
}
#[cfg(not(global_shortcut_all))]
impl Cmd {
pub fn run<A: crate::ApplicationExt + 'static>(
self,
webview_manager: &crate::WebviewManager<A>,
) -> crate::Result<InvokeResponse> {
#[cfg(not(global_shortcut_all))]
return Err(crate::Error::ApiNotAllowlisted(
pub fn run<M: Params>(self, _window: Window<M>) -> crate::Result<InvokeResponse> {
Err(crate::Error::ApiNotAllowlisted(
"globalShortcut > all".to_string(),
));
#[cfg(global_shortcut_all)]
))
}
}
#[cfg(global_shortcut_all)]
impl Cmd {
pub fn run<M: Params>(self, window: Window<M>) -> crate::Result<InvokeResponse> {
match self {
Self::Register { shortcut, handler } => {
let dispatcher = webview_manager.current_webview()?;
let dispatcher = window.dispatcher();
let mut manager = manager_handle().lock().unwrap();
register_shortcut(dispatcher, &mut manager, shortcut, handler)?;
Ok(().into())
}
Self::RegisterAll { shortcuts, handler } => {
let dispatcher = webview_manager.current_webview()?;
let dispatcher = window.dispatcher();
let mut manager = manager_handle().lock().unwrap();
for shortcut in shortcuts {
register_shortcut(dispatcher.clone(), &mut manager, shortcut, handler.clone())?;
let dispatch = dispatcher.clone();
register_shortcut(dispatch, &mut manager, shortcut, handler.clone())?;
}
Ok(().into())
}

View File

@ -1,15 +1,13 @@
use super::InvokeResponse;
use crate::{
api::{
command::{Command, CommandChild, CommandEvent},
rpc::format_callback,
},
app::ApplicationExt,
endpoints::InvokeResponse,
runtime::{window::Window, Params},
};
use once_cell::sync::Lazy;
use serde::Deserialize;
use std::{
collections::HashMap,
sync::{Arc, Mutex},
@ -57,10 +55,7 @@ pub enum Cmd {
}
impl Cmd {
pub fn run<A: ApplicationExt + 'static>(
self,
webview_manager: crate::WebviewManager<A>,
) -> crate::Result<InvokeResponse> {
pub fn run<M: Params>(self, window: Window<M>) -> crate::Result<InvokeResponse> {
match self {
Self::Execute {
program,
@ -87,9 +82,7 @@ impl Cmd {
command_childs().lock().unwrap().remove(&pid);
}
let js = format_callback(on_event_fn.clone(), serde_json::to_value(event).unwrap());
if let Ok(dispatcher) = webview_manager.current_webview() {
let _ = dispatcher.eval(js.as_str());
}
let _ = window.eval(js.as_str());
}
});

View File

@ -1,11 +1,19 @@
use super::InvokeResponse;
use crate::app::{ApplicationExt, Icon};
use crate::{
endpoints::InvokeResponse,
runtime::{
webview::{Icon, WindowConfig},
window::{PendingWindow, Window},
Manager, Params,
},
};
use serde::Deserialize;
use std::path::PathBuf;
#[derive(Deserialize)]
#[serde(untagged)]
pub enum IconDto {
File(String),
File(PathBuf),
Raw(Vec<u8>),
}
@ -90,14 +98,10 @@ struct WindowCreatedEvent {
}
impl Cmd {
pub async fn run<A: ApplicationExt + 'static>(
self,
webview_manager: &crate::WebviewManager<A>,
) -> crate::Result<InvokeResponse> {
pub async fn run<M: Params>(self, mut window: Window<M>) -> crate::Result<InvokeResponse> {
if cfg!(not(window_all)) {
Err(crate::Error::ApiNotAllowlisted("window > all".to_string()))
} else {
let current_webview = webview_manager.current_webview()?;
match self {
Self::CreateWebview { options } => {
#[cfg(not(window_create))]
@ -106,48 +110,48 @@ impl Cmd {
));
#[cfg(window_create)]
{
let label = options.label.to_string();
webview_manager
.create_webview(label.to_string(), options.url.clone(), |_| {
Ok(crate::app::webview::WindowConfig(options).into())
})
.await?;
webview_manager.emit_except(
label.to_string(),
"tauri://window-created",
Some(WindowCreatedEvent { label }),
let label: M::Label = options
.label
.parse()
.unwrap_or_else(|_| panic!("todo: label parsing"));
let url = options.url.clone();
let pending = PendingWindow::new(WindowConfig(options), label.clone(), url);
window.create_window(pending)?.emit_others_internal(
"tauri://window-created".to_string(),
Some(WindowCreatedEvent {
label: label.to_string(),
}),
)?;
}
}
Self::SetResizable { resizable } => current_webview.set_resizable(resizable)?,
Self::SetTitle { title } => current_webview.set_title(&title)?,
Self::Maximize => current_webview.maximize()?,
Self::Unmaximize => current_webview.unmaximize()?,
Self::Minimize => current_webview.minimize()?,
Self::Unminimize => current_webview.unminimize()?,
Self::Show => current_webview.show()?,
Self::Hide => current_webview.hide()?,
Self::Close => current_webview.close()?,
Self::SetDecorations { decorations } => current_webview.set_decorations(decorations)?,
Self::SetAlwaysOnTop { always_on_top } => {
current_webview.set_always_on_top(always_on_top)?
}
Self::SetWidth { width } => current_webview.set_width(width)?,
Self::SetHeight { height } => current_webview.set_height(height)?,
Self::Resize { width, height } => current_webview.resize(width, height)?,
Self::SetResizable { resizable } => window.set_resizable(resizable)?,
Self::SetTitle { title } => window.set_title(&title)?,
Self::Maximize => window.maximize()?,
Self::Unmaximize => window.unmaximize()?,
Self::Minimize => window.minimize()?,
Self::Unminimize => window.unminimize()?,
Self::Show => window.show()?,
Self::Hide => window.hide()?,
Self::Close => window.close()?,
Self::SetDecorations { decorations } => window.set_decorations(decorations)?,
Self::SetAlwaysOnTop { always_on_top } => window.set_always_on_top(always_on_top)?,
Self::SetWidth { width } => window.set_width(width)?,
Self::SetHeight { height } => window.set_height(height)?,
Self::Resize { width, height } => window.resize(width, height)?,
Self::SetMinSize {
min_width,
min_height,
} => current_webview.set_min_size(min_width, min_height)?,
} => window.set_min_size(min_width, min_height)?,
Self::SetMaxSize {
max_width,
max_height,
} => current_webview.set_max_size(max_width, max_height)?,
Self::SetX { x } => current_webview.set_x(x)?,
Self::SetY { y } => current_webview.set_y(y)?,
Self::SetPosition { x, y } => current_webview.set_position(x, y)?,
Self::SetFullscreen { fullscreen } => current_webview.set_fullscreen(fullscreen)?,
Self::SetIcon { icon } => current_webview.set_icon(icon.into())?,
} => window.set_max_size(max_width, max_height)?,
Self::SetX { x } => window.set_x(x)?,
Self::SetY { y } => window.set_y(y)?,
Self::SetPosition { x, y } => window.set_position(x, y)?,
Self::SetFullscreen { fullscreen } => window.set_fullscreen(fullscreen)?,
Self::SetIcon { icon } => window.set_icon(icon.into())?,
}
Ok(().into())
}

View File

@ -46,6 +46,9 @@ pub enum Error {
/// Invalid args when running a command.
#[error("invalid args for command `{0}`: {1}")]
InvalidArgs(&'static str, serde_json::Error),
/// Encountered an error in the setup hook,
#[error("error encountered during setup hood: {0}")]
Setup(#[from] Box<dyn std::error::Error>),
}
impl From<serde_json::Error> for Error {

219
tauri/src/event.rs Normal file
View File

@ -0,0 +1,219 @@
use crate::runtime::tag::Tag;
use std::{
boxed::Box,
collections::HashMap,
fmt,
hash::Hash,
sync::{Arc, Mutex},
};
use uuid::Uuid;
/// Represents an event handler.
#[derive(Debug, Clone, Copy, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct EventHandler(Uuid);
impl fmt::Display for EventHandler {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.0.fmt(f)
}
}
/// An event that was triggered.
#[derive(Debug, Clone)]
pub struct Event {
id: EventHandler,
data: Option<String>,
}
impl Event {
/// The [`EventHandler`] that was triggered.
pub fn id(&self) -> EventHandler {
self.id
}
/// The event payload.
pub fn payload(&self) -> Option<&str> {
self.data.as_deref()
}
}
/// Stored in [`Listeners`] to be called upon when the event that stored it is triggered.
struct Handler<Window: Tag> {
window: Option<Window>,
callback: Box<dyn Fn(Event) + Send>,
}
/// A collection of handlers. Multiple handlers can represent the same event.
type Handlers<Event, Window> = HashMap<Event, HashMap<EventHandler, Handler<Window>>>;
#[derive(Clone)]
pub(crate) struct Listeners<Event: Tag, Window: Tag> {
inner: Arc<Mutex<Handlers<Event, Window>>>,
function_name: Uuid,
listeners_object_name: Uuid,
queue_object_name: Uuid,
}
impl<E: Tag, L: Tag> Default for Listeners<E, L> {
fn default() -> Self {
Self {
inner: Arc::new(Mutex::default()),
function_name: Uuid::new_v4(),
listeners_object_name: Uuid::new_v4(),
queue_object_name: Uuid::new_v4(),
}
}
}
impl<E: Tag, L: Tag> Listeners<E, L> {
/// Randomly generated function name to represent the JavaScript event function.
pub(crate) fn function_name(&self) -> String {
self.function_name.to_string()
}
/// Randomly generated listener object name to represent the JavaScript event listener object.
pub(crate) fn listeners_object_name(&self) -> String {
self.function_name.to_string()
}
/// Randomly generated queue object name to represent the JavaScript event queue object.
pub(crate) fn queue_object_name(&self) -> String {
self.queue_object_name.to_string()
}
/// Adds an event listener for JS events.
pub(crate) fn listen<F: Fn(Event) + Send + 'static>(
&self,
event: E,
window: Option<L>,
handler: F,
) -> EventHandler {
let id = EventHandler(Uuid::new_v4());
let handler = Handler {
window,
callback: Box::new(handler),
};
self
.inner
.lock()
.expect("poisoned event mutex")
.entry(event)
.or_default()
.insert(id, handler);
id
}
/// Listen to a JS event and immediately unlisten.
pub(crate) fn once<F: Fn(Event) + Send + 'static>(
&self,
event: E,
window: Option<L>,
handler: F,
) {
let self_ = self.clone();
self.listen(event, window, move |e| {
self_.unlisten(e.id);
handler(e);
});
}
/// Removes an event listener.
pub(crate) fn unlisten(&self, handler_id: EventHandler) {
self
.inner
.lock()
.expect("poisoned event mutex")
.values_mut()
.for_each(|handler| {
handler.remove(&handler_id);
})
}
/// Triggers the given global event with its payload.
pub(crate) fn trigger(&self, event: E, window: Option<L>, data: Option<String>) {
if let Some(handlers) = self.inner.lock().expect("poisoned event mutex").get(&event) {
for (&id, handler) in handlers {
if window.is_none() || window == handler.window {
let data = data.clone();
let payload = Event { id, data };
(handler.callback)(payload)
}
}
}
}
}
#[cfg(test)]
mod test {
use super::*;
use proptest::prelude::*;
// dummy event handler function
fn event_fn(s: Event) {
println!("{:?}", s);
}
proptest! {
#![proptest_config(ProptestConfig::with_cases(10000))]
// check to see if listen() is properly passing keys into the LISTENERS map
#[test]
fn listeners_check_key(e in "[a-z]+") {
let listeners: Listeners<String, String> = Default::default();
// clone e as the key
let key = e.clone();
// pass e and an dummy func into listen
listeners.listen(e, None, event_fn);
// lock mutex
let l = listeners.inner.lock().unwrap();
// check if the generated key is in the map
assert_eq!(l.contains_key(&key), true);
}
// check to see if listen inputs a handler function properly into the LISTENERS map.
#[test]
fn listeners_check_fn(e in "[a-z]+") {
let listeners: Listeners<String, String> = Default::default();
// clone e as the key
let key = e.clone();
// pass e and an dummy func into listen
listeners.listen(e, None, event_fn);
// lock mutex
let mut l = listeners.inner.lock().unwrap();
// check if l contains key
if l.contains_key(&key) {
// grab key if it exists
let handler = l.get_mut(&key);
// check to see if we get back a handler or not
match handler {
// pass on Some(handler)
Some(_) => {},
// Fail on None
None => panic!("handler is None")
}
}
}
// check to see if on_event properly grabs the stored function from listen.
#[test]
fn check_on_event(e in "[a-z]+", d in "[a-z]+") {
let listeners: Listeners<String, String> = Default::default();
// clone e as the key
let key = e.clone();
// call listen with e and the event_fn dummy func
listeners.listen(e.clone(), None, event_fn);
// call on event with e and d.
listeners.trigger(e, None, Some(d));
// lock the mutex
let l = listeners.inner.lock().unwrap();
// assert that the key is contained in the listeners map
assert!(l.contains_key(&key));
}
}
}

164
tauri/src/hooks.rs Normal file
View File

@ -0,0 +1,164 @@
use crate::{
api::rpc::{format_callback, format_callback_result},
runtime::{app::App, window::Window, Params},
};
use serde::{Deserialize, Serialize};
use std::future::Future;
/// A closure that is run when the Tauri application is setting up.
pub type SetupHook<M> = Box<dyn Fn(&mut App<M>) -> Result<(), Box<dyn std::error::Error>> + Send>;
/// A closure that is run everytime Tauri receives a message it doesn't explicitly handle.
pub type InvokeHandler<M> = dyn Fn(InvokeMessage<M>) + Send + Sync + 'static;
/// A closure that is run once every time a window is created and loaded.
pub type OnPageLoad<M> = dyn Fn(Window<M>, PageLoadPayload) + Send + Sync + 'static;
/// The payload for the [`OnPageLoad`] hook.
#[derive(Debug, Clone, Deserialize)]
pub struct PageLoadPayload {
url: String,
}
impl PageLoadPayload {
/// The page URL.
pub fn url(&self) -> &str {
&self.url
}
}
/// Payload from an invoke call.
#[derive(Debug, Deserialize)]
pub(crate) struct InvokePayload {
#[serde(rename = "__tauriModule")]
pub(crate) tauri_module: Option<String>,
pub(crate) callback: String,
pub(crate) error: String,
#[serde(rename = "mainThread", default)]
pub(crate) main_thread: bool,
#[serde(flatten)]
pub(crate) inner: serde_json::Value,
}
/// An invoke message.
pub struct InvokeMessage<M: Params> {
window: Window<M>,
command: String,
/// Allow our crate to access the payload without cloning it.
pub(crate) payload: InvokePayload,
}
impl<M: Params> InvokeMessage<M> {
/// Create an new [`InvokeMessage`] from a payload send to a window.
pub(crate) fn new(window: Window<M>, command: String, payload: InvokePayload) -> Self {
Self {
window,
command,
payload,
}
}
/// The invoke command.
pub fn command(&self) -> &str {
&self.command
}
/// The invoke payload.
pub fn payload(&self) -> serde_json::Value {
self.payload.inner.clone()
}
/// The window that received the invoke.
pub fn window(&self) -> Window<M> {
self.window.clone()
}
/// Reply to the invoke promise with an async task.
pub fn respond_async<
T: Serialize,
Err: Serialize,
F: Future<Output = Result<T, Err>> + Send + 'static,
>(
self,
task: F,
) {
if self.payload.main_thread {
crate::async_runtime::block_on(async move {
Self::return_task(self.window, task, self.payload.callback, self.payload.error).await;
});
} else {
crate::async_runtime::spawn(async move {
Self::return_task(self.window, task, self.payload.callback, self.payload.error).await;
});
}
}
/// Reply to the invoke promise running the given closure.
pub fn respond_closure<T: Serialize, Err: Serialize, F: FnOnce() -> Result<T, Err>>(self, f: F) {
Self::return_closure(self.window, f, self.payload.callback, self.payload.error)
}
/// Resolve the invoke promise with a value.
pub fn resolve<S: Serialize>(self, value: S) {
Self::return_result(
self.window,
Result::<S, ()>::Ok(value),
self.payload.callback,
self.payload.error,
)
}
/// Reject the invoke promise with a value.
pub fn reject<S: Serialize>(self, value: S) {
Self::return_result(
self.window,
Result::<(), S>::Err(value),
self.payload.callback,
self.payload.error,
)
}
/// Asynchronously executes the given task
/// and evaluates its Result to the JS promise described by the `success_callback` and `error_callback` function names.
///
/// 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 return_task<
T: Serialize,
Err: Serialize,
F: std::future::Future<Output = Result<T, Err>> + Send + 'static,
>(
window: Window<M>,
task: F,
success_callback: String,
error_callback: String,
) {
let result = task.await;
Self::return_closure(window, || result, success_callback, error_callback)
}
pub(crate) fn return_closure<T: Serialize, Err: Serialize, F: FnOnce() -> Result<T, Err>>(
window: Window<M>,
f: F,
success_callback: String,
error_callback: String,
) {
Self::return_result(window, f(), success_callback, error_callback)
}
pub(crate) fn return_result<T: Serialize, Err: Serialize>(
window: Window<M>,
result: Result<T, Err>,
success_callback: String,
error_callback: String,
) {
let callback_string =
match format_callback_result(result, success_callback, error_callback.clone()) {
Ok(callback_string) => callback_string,
Err(e) => format_callback(error_callback, e.to_string()),
};
let _ = window.eval(&callback_string);
}
}

View File

@ -6,37 +6,41 @@
//! Tauri uses (and contributes to) the MIT licensed project that you can find at [webview](https://github.com/webview/webview).
#![warn(missing_docs, rust_2018_idioms)]
/// The Tauri-specific settings for your app e.g. notification permission status.
/// The Tauri error enum.
pub use error::Error;
pub use tauri_api as api;
pub(crate) use tauri_api::private::async_runtime;
pub use tauri_macros::*;
/// The Tauri-specific settings for your runtime e.g. notification permission status.
pub mod settings;
/// The webview application entry.
mod app;
/// The Tauri API endpoints.
mod endpoints;
mod error;
mod event;
mod hooks;
/// The plugin manager module contains helpers to manage runtime plugins.
pub mod plugin;
/// The internal runtime between an [`App`] and the webview.
pub mod runtime;
/// The salt helpers.
mod salt;
/// The Tauri error enum.
pub use error::Error;
/// Tauri result type.
pub type Result<T> = std::result::Result<T, Error>;
/// A task to run on the main thread.
pub type SyncTask = Box<dyn FnOnce() + Send>;
pub use app::*;
pub use tauri_api as api;
pub(crate) use tauri_api::private::async_runtime;
pub use tauri_macros::*;
/// The Tauri webview implementations.
pub mod flavors {
pub use super::app::WryApplication as Wry;
}
/// types likely to be used by applications
pub use {
api::config::WindowUrl,
hooks::InvokeMessage,
runtime::app::AppBuilder,
runtime::webview::Attributes,
runtime::{Context, Manager, Params},
};
/// Easy helper function to use the Tauri Context you made during build time.
#[macro_export]

View File

@ -1,17 +1,19 @@
use crate::{
api::config::PluginConfig, ApplicationExt, InvokeMessage, PageLoadPayload, WebviewManager,
api::config::PluginConfig,
hooks::{InvokeMessage, PageLoadPayload},
runtime::{window::Window, Params},
};
use std::sync::{Arc, Mutex};
use serde_json::Value as JsonValue;
use std::collections::HashMap;
/// The plugin interface.
pub trait Plugin<A: ApplicationExt + 'static>: Send {
pub trait Plugin<M: Params>: Send {
/// The plugin name. Used as key on the plugin config object.
fn name(&self) -> &'static str;
/// Initialize the plugin.
#[allow(unused_variables)]
fn initialize(&mut self, config: String) -> crate::Result<()> {
fn initialize(&mut self, config: JsonValue) -> crate::Result<()> {
Ok(())
}
@ -26,94 +28,82 @@ pub trait Plugin<A: ApplicationExt + 'static>: Send {
/// Callback invoked when the webview is created.
#[allow(unused_variables)]
fn created(&mut self, webview_manager: WebviewManager<A>) {}
fn created(&mut self, window: Window<M>) {}
/// Callback invoked when the webview performs a navigation.
#[allow(unused_variables)]
fn on_page_load(&mut self, webview_manager: WebviewManager<A>, payload: PageLoadPayload) {}
fn on_page_load(&mut self, window: Window<M>, payload: PageLoadPayload) {}
/// Add invoke_handler API extension commands.
#[allow(unused_variables)]
fn extend_api(&mut self, webview_manager: WebviewManager<A>, message: InvokeMessage<A>) {}
fn extend_api(&mut self, message: InvokeMessage<M>) {}
}
/// Plugin collection type.
pub type PluginStore<A> = Arc<Mutex<Vec<Box<dyn Plugin<A> + Send>>>>;
/// Registers a plugin.
pub fn register<A: ApplicationExt + 'static>(
store: &PluginStore<A>,
plugin: impl Plugin<A> + Send + 'static,
) {
let mut plugins = store.lock().unwrap();
plugins.push(Box::new(plugin));
pub struct PluginStore<M: Params> {
store: HashMap<&'static str, Box<dyn Plugin<M>>>,
}
pub(crate) fn initialize<A: ApplicationExt + 'static>(
store: &PluginStore<A>,
plugins_config: PluginConfig,
) -> crate::Result<()> {
let mut plugins = store.lock().unwrap();
for plugin in plugins.iter_mut() {
let plugin_config = plugins_config.get(plugin.name());
plugin.initialize(plugin_config)?;
}
Ok(())
}
pub(crate) fn initialization_script<A: ApplicationExt + 'static>(store: &PluginStore<A>) -> String {
let mut plugins = store.lock().unwrap();
let mut initialization_script = String::new();
for plugin in plugins.iter_mut() {
if let Some(plugin_initialization_script) = plugin.initialization_script() {
initialization_script.push_str(&format!(
"(function () {{ {} }})();",
plugin_initialization_script
));
}
}
initialization_script
}
pub(crate) fn created<A: ApplicationExt + 'static>(
store: &PluginStore<A>,
webview_manager: &crate::WebviewManager<A>,
) {
let mut plugins = store.lock().unwrap();
for plugin in plugins.iter_mut() {
plugin.created(webview_manager.clone());
}
}
pub(crate) fn on_page_load<A: ApplicationExt + 'static>(
store: &PluginStore<A>,
webview_manager: &crate::WebviewManager<A>,
payload: PageLoadPayload,
) {
let mut plugins = store.lock().unwrap();
for plugin in plugins.iter_mut() {
plugin.on_page_load(webview_manager.clone(), payload.clone());
}
}
pub(crate) fn extend_api<A: ApplicationExt + 'static>(
store: &PluginStore<A>,
webview_manager: &crate::WebviewManager<A>,
command: String,
message: InvokeMessage<A>,
) {
let mut plugins = store.lock().unwrap();
let target_plugin_name = command
.replace("plugin:", "")
.split('|')
.next()
.unwrap()
.to_string();
for plugin in plugins.iter_mut() {
if plugin.name() == target_plugin_name {
plugin.extend_api(webview_manager.clone(), message);
break;
impl<M: Params> Default for PluginStore<M> {
fn default() -> Self {
Self {
store: HashMap::new(),
}
}
}
impl<M: Params> PluginStore<M> {
/// Adds a plugin to the store.
///
/// Returns `true` if a plugin with the same name is already in the store.
pub fn register<P: Plugin<M> + 'static>(&mut self, plugin: P) -> bool {
self.store.insert(plugin.name(), Box::new(plugin)).is_some()
}
/// Initializes all plugins in the store.
pub(crate) fn initialize(&mut self, config: &PluginConfig) -> crate::Result<()> {
self.store.values_mut().try_for_each(|plugin| {
plugin.initialize(config.0.get(plugin.name()).cloned().unwrap_or_default())
})
}
/// Generates an initialization script from all plugins in the store.
pub(crate) fn initialization_script(&self) -> String {
self
.store
.values()
.filter_map(|p| p.initialization_script())
.fold(String::new(), |acc, script| {
format!("{}\n(function () {{ {} }})();", acc, script)
})
}
/// Runs the created hook for all plugins in the store.
pub(crate) fn created(&mut self, window: Window<M>) {
self
.store
.values_mut()
.for_each(|plugin| plugin.created(window.clone()))
}
/// Runs the on_page_load hook for all plugins in the store.
pub(crate) fn on_page_load(&mut self, window: Window<M>, payload: PageLoadPayload) {
self
.store
.values_mut()
.for_each(|plugin| plugin.on_page_load(window.clone(), payload.clone()))
}
pub(crate) fn extend_api(&mut self, command: String, message: InvokeMessage<M>) {
let target = command
.replace("plugin:", "")
.split('|')
.next()
.expect("target plugin name empty")
.to_string();
if let Some(plugin) = self.store.get_mut(target.as_str()) {
plugin.extend_api(message);
}
}
}

185
tauri/src/runtime/app.rs Normal file
View File

@ -0,0 +1,185 @@
use crate::{
api::{assets::Assets, config::WindowUrl},
hooks::{InvokeHandler, InvokeMessage, OnPageLoad, PageLoadPayload, SetupHook},
plugin::{Plugin, PluginStore},
runtime::{
flavor::wry::Wry,
manager::WindowManager,
sealed::ManagerPrivate,
tag::Tag,
webview::{Attributes, WindowConfig},
window::{PendingWindow, Window},
Context, Dispatch, Manager, Params, Runtime, RuntimeOrDispatch,
},
};
/// A handle to the currently running application.
pub struct App<M: Params> {
runtime: M::Runtime,
manager: M,
}
impl<M: Params> Manager<M> for App<M> {}
impl<M: Params> ManagerPrivate<M> for App<M> {
fn manager(&self) -> &M {
&self.manager
}
fn runtime(&mut self) -> RuntimeOrDispatch<'_, M> {
RuntimeOrDispatch::Runtime(&mut self.runtime)
}
}
#[allow(missing_docs)]
pub struct Runner<M: Params> {
pending_windows: Vec<PendingWindow<M>>,
manager: M,
setup: SetupHook<M>,
}
impl<M: Params> Runner<M> {
/// Consume and run the [`Application`] until it is finished.
pub fn run(mut self) -> crate::Result<()> {
// set up all the windows defined in the config
for config in self.manager.config().tauri.windows.clone() {
let url = config.url.clone();
let label = config
.label
.parse()
.unwrap_or_else(|_| panic!("bad label: {}", config.label));
self
.pending_windows
.push(PendingWindow::new(WindowConfig(config), label, url));
}
self.manager.initialize_plugins()?;
let labels = self
.pending_windows
.iter()
.map(|p| p.label.clone())
.collect::<Vec<_>>();
let mut app = App {
runtime: M::Runtime::new()?,
manager: self.manager,
};
let pending_windows = self.pending_windows;
for pending in pending_windows {
let pending = app.manager.prepare_window(pending, &labels)?;
let detached = app.runtime.create_window(pending)?;
app.manager.attach_window(detached);
}
(self.setup)(&mut app)?;
app.runtime.run();
Ok(())
}
}
/// The App builder.
pub struct AppBuilder<E, L, A, R>
where
E: Tag,
L: Tag,
A: Assets,
R: Runtime,
{
/// The JS message handler.
invoke_handler: Box<InvokeHandler<WindowManager<E, L, A, R>>>,
/// The setup hook.
setup: SetupHook<WindowManager<E, L, A, R>>,
/// Page load hook.
on_page_load: Box<OnPageLoad<WindowManager<E, L, A, R>>>,
/// windows to create when starting up.
pending_windows: Vec<PendingWindow<WindowManager<E, L, A, R>>>,
/// All passed plugins
plugins: PluginStore<WindowManager<E, L, A, R>>,
}
impl<E, L, A, R> AppBuilder<E, L, A, R>
where
E: Tag,
L: Tag,
A: Assets,
R: Runtime,
{
/// Creates a new App builder.
pub fn new() -> Self {
Self {
setup: Box::new(|_| Ok(())),
invoke_handler: Box::new(|_| ()),
on_page_load: Box::new(|_, _| ()),
pending_windows: Default::default(),
plugins: PluginStore::default(),
}
}
/// Defines the JS message handler callback.
pub fn invoke_handler<F>(mut self, invoke_handler: F) -> Self
where
F: Fn(InvokeMessage<WindowManager<E, L, A, R>>) + Send + Sync + 'static,
{
self.invoke_handler = Box::new(invoke_handler);
self
}
/// Defines the setup hook.
pub fn setup<F>(mut self, setup: F) -> Self
where
F: Fn(&mut App<WindowManager<E, L, A, R>>) -> Result<(), Box<dyn std::error::Error>>
+ Send
+ 'static,
{
self.setup = Box::new(setup);
self
}
/// Defines the page load hook.
pub fn on_page_load<F>(mut self, on_page_load: F) -> Self
where
F: Fn(Window<WindowManager<E, L, A, R>>, PageLoadPayload) + Send + Sync + 'static,
{
self.on_page_load = Box::new(on_page_load);
self
}
/// Adds a plugin to the runtime.
pub fn plugin<P: Plugin<WindowManager<E, L, A, R>> + 'static>(mut self, plugin: P) -> Self {
self.plugins.register(plugin);
self
}
/// Creates a new webview.
pub fn create_window<F>(mut self, label: L, url: WindowUrl, setup: F) -> Self
where
F: FnOnce(<R::Dispatcher as Dispatch>::Attributes) -> <R::Dispatcher as Dispatch>::Attributes,
{
let attributes = setup(<R::Dispatcher as Dispatch>::Attributes::new());
self
.pending_windows
.push(PendingWindow::new(attributes, label, url));
self
}
/// Builds the [`App`] and the underlying [`Runtime`].
pub fn build(self, context: Context<A>) -> Runner<WindowManager<E, L, A, R>> {
Runner {
pending_windows: self.pending_windows,
setup: self.setup,
manager: WindowManager::with_handlers(context, self.invoke_handler, self.on_page_load),
}
}
}
/// Make `Wry` the default `ApplicationExt` for `AppBuilder`
impl<A: Assets> Default for AppBuilder<String, String, A, Wry> {
fn default() -> Self {
Self::new()
}
}

View File

@ -0,0 +1,3 @@
//! Officially supported webview runtimes.
pub mod wry;

View File

@ -1,22 +1,24 @@
use super::{
ApplicationDispatcherExt, ApplicationExt, CustomProtocol, FileDropEvent, FileDropHandler, Icon,
RpcRequest, WebviewBuilderExt, WebviewBuilderExtPrivate, WebviewRpcHandler, WindowConfig,
};
//! The [`wry`] webview runtime.
use crate::plugin::PluginStore;
use once_cell::sync::Lazy;
use crate::runtime::{
webview::{
Attributes, AttributesPrivate, CustomProtocol, FileDropEvent, FileDropHandler, Icon,
RpcRequest, WebviewRpcHandler, WindowConfig,
},
window::{DetachedWindow, PendingWindow},
Dispatch, Params, Runtime,
};
use std::{convert::TryFrom, path::PathBuf};
#[cfg(target_os = "windows")]
use std::fs::create_dir_all;
#[cfg(target_os = "windows")]
use tauri_api::path::{resolve_path, BaseDirectory};
use std::{
convert::{TryFrom, TryInto},
path::PathBuf,
};
/// Wraps a Tauri icon into a format [`wry`] expects the icon to be in.
pub struct WryIcon(wry::Icon);
impl TryFrom<Icon> for wry::Icon {
impl TryFrom<Icon> for WryIcon {
type Error = crate::Error;
fn try_from(icon: Icon) -> Result<Self, Self::Error> {
let icon = match icon {
@ -27,11 +29,11 @@ impl TryFrom<Icon> for wry::Icon {
wry::Icon::from_bytes(raw).map_err(|e| crate::Error::InvalidIcon(e.to_string()))?
}
};
Ok(icon)
Ok(Self(icon))
}
}
impl WebviewBuilderExtPrivate for wry::Attributes {
impl AttributesPrivate for wry::Attributes {
fn url(mut self, url: String) -> Self {
self.url.replace(url);
self
@ -98,9 +100,8 @@ impl From<WindowConfig> for wry::Attributes {
}
/// The webview builder.
impl WebviewBuilderExt for wry::Attributes {
/// The webview object that this builder creates.
type Webview = Self;
impl Attributes for wry::Attributes {
type Icon = WryIcon;
fn new() -> Self {
Default::default()
@ -191,9 +192,9 @@ impl WebviewBuilderExt for wry::Attributes {
self
}
fn icon(mut self, icon: Icon) -> crate::Result<Self> {
self.icon = Some(icon.try_into()?);
Ok(self)
fn icon(mut self, icon: Self::Icon) -> Self {
self.icon = Some(icon.0);
self
}
fn has_icon(&self) -> bool {
@ -205,8 +206,8 @@ impl WebviewBuilderExt for wry::Attributes {
self
}
fn finish(self) -> crate::Result<Self::Webview> {
Ok(self)
fn build(self) -> Self {
self
}
}
@ -229,270 +230,316 @@ impl From<wry::FileDropEvent> for FileDropEvent {
}
}
/// A dispatcher for a [`wry`] runtime.
#[derive(Clone)]
pub struct WryDispatcher(wry::WindowProxy, wry::ApplicationProxy);
pub struct WryDispatcher {
window: wry::WindowProxy,
application: wry::ApplicationProxy,
}
impl ApplicationDispatcherExt for WryDispatcher {
type WebviewBuilder = wry::Attributes;
impl Dispatch for WryDispatcher {
type Runtime = Wry;
type Icon = WryIcon;
type Attributes = wry::Attributes;
fn create_webview(
&self,
attributes: Self::WebviewBuilder,
rpc_handler: Option<WebviewRpcHandler<Self>>,
custom_protocol: Option<CustomProtocol>,
file_drop_handler: Option<FileDropHandler>,
) -> crate::Result<Self> {
let app_dispatcher = self.1.clone();
fn create_window<M: Params<Runtime = Self::Runtime>>(
&mut self,
pending: PendingWindow<M>,
) -> crate::Result<DetachedWindow<M>> {
let PendingWindow {
attributes,
rpc_handler,
custom_protocol,
file_drop_handler,
label,
..
} = pending;
let wry_rpc_handler = Box::new(
move |dispatcher: wry::WindowProxy, request: wry::RpcRequest| {
if let Some(handler) = &rpc_handler {
handler(
WryDispatcher(dispatcher, app_dispatcher.clone()),
request.into(),
);
}
None
},
);
let proxy = self.application.clone();
let file_drop_handler = Box::new(move |event: wry::FileDropEvent| {
if let Some(handler) = &file_drop_handler {
handler(event.into())
} else {
false
}
});
let rpc_handler =
rpc_handler.map(|handler| create_rpc_handler(proxy.clone(), label.clone(), handler));
let window_dispatcher = self
.1
let file_drop_handler = file_drop_handler
.map(|handler| create_file_drop_handler(proxy.clone(), label.clone(), handler));
let window = self
.application
.add_window_with_configs(
attributes,
Some(wry_rpc_handler),
custom_protocol.map(|p| wry::CustomProtocol {
name: p.name.clone(),
handler: Box::new(move |a| (*p.handler)(a).map_err(|_| wry::Error::InitScriptError)),
}),
Some(file_drop_handler),
rpc_handler,
custom_protocol.map(create_custom_protocol),
file_drop_handler,
)
.map_err(|_| crate::Error::FailedToSendMessage)?;
Ok(Self(window_dispatcher, self.1.clone()))
.map_err(|_| crate::Error::CreateWebview)?;
let dispatcher = WryDispatcher {
window,
application: proxy,
};
Ok(DetachedWindow { label, dispatcher })
}
fn set_resizable(&self, resizable: bool) -> crate::Result<()> {
self
.0
.window
.set_resizable(resizable)
.map_err(|_| crate::Error::FailedToSendMessage)
}
fn set_title<S: Into<String>>(&self, title: S) -> crate::Result<()> {
self
.0
.window
.set_title(title)
.map_err(|_| crate::Error::FailedToSendMessage)
}
fn maximize(&self) -> crate::Result<()> {
self
.0
.window
.maximize()
.map_err(|_| crate::Error::FailedToSendMessage)
}
fn unmaximize(&self) -> crate::Result<()> {
self
.0
.window
.unmaximize()
.map_err(|_| crate::Error::FailedToSendMessage)
}
fn minimize(&self) -> crate::Result<()> {
self
.0
.window
.minimize()
.map_err(|_| crate::Error::FailedToSendMessage)
}
fn unminimize(&self) -> crate::Result<()> {
self
.0
.window
.unminimize()
.map_err(|_| crate::Error::FailedToSendMessage)
}
fn show(&self) -> crate::Result<()> {
self.0.show().map_err(|_| crate::Error::FailedToSendMessage)
self
.window
.show()
.map_err(|_| crate::Error::FailedToSendMessage)
}
fn hide(&self) -> crate::Result<()> {
self.0.hide().map_err(|_| crate::Error::FailedToSendMessage)
self
.window
.hide()
.map_err(|_| crate::Error::FailedToSendMessage)
}
fn close(&self) -> crate::Result<()> {
self
.0
.window
.close()
.map_err(|_| crate::Error::FailedToSendMessage)
}
fn set_decorations(&self, decorations: bool) -> crate::Result<()> {
self
.0
.window
.set_decorations(decorations)
.map_err(|_| crate::Error::FailedToSendMessage)
}
fn set_always_on_top(&self, always_on_top: bool) -> crate::Result<()> {
self
.0
.window
.set_always_on_top(always_on_top)
.map_err(|_| crate::Error::FailedToSendMessage)
}
fn set_width(&self, width: f64) -> crate::Result<()> {
self
.0
.window
.set_width(width)
.map_err(|_| crate::Error::FailedToSendMessage)
}
fn set_height(&self, height: f64) -> crate::Result<()> {
self
.0
.window
.set_height(height)
.map_err(|_| crate::Error::FailedToSendMessage)
}
fn resize(&self, width: f64, height: f64) -> crate::Result<()> {
self
.0
.window
.resize(width, height)
.map_err(|_| crate::Error::FailedToSendMessage)
}
fn set_min_size(&self, min_width: f64, min_height: f64) -> crate::Result<()> {
self
.0
.window
.set_min_size(min_width, min_height)
.map_err(|_| crate::Error::FailedToSendMessage)
}
fn set_max_size(&self, max_width: f64, max_height: f64) -> crate::Result<()> {
self
.0
.window
.set_max_size(max_width, max_height)
.map_err(|_| crate::Error::FailedToSendMessage)
}
fn set_x(&self, x: f64) -> crate::Result<()> {
self
.0
.window
.set_x(x)
.map_err(|_| crate::Error::FailedToSendMessage)
}
fn set_y(&self, y: f64) -> crate::Result<()> {
self
.0
.window
.set_y(y)
.map_err(|_| crate::Error::FailedToSendMessage)
}
fn set_position(&self, x: f64, y: f64) -> crate::Result<()> {
self
.0
.window
.set_position(x, y)
.map_err(|_| crate::Error::FailedToSendMessage)
}
fn set_fullscreen(&self, fullscreen: bool) -> crate::Result<()> {
self
.0
.window
.set_fullscreen(fullscreen)
.map_err(|_| crate::Error::FailedToSendMessage)
}
fn set_icon(&self, icon: Icon) -> crate::Result<()> {
fn set_icon(&self, icon: Self::Icon) -> crate::Result<()> {
self
.0
.set_icon(icon.try_into()?)
.window
.set_icon(icon.0)
.map_err(|_| crate::Error::FailedToSendMessage)
}
fn eval_script<S: Into<String>>(&self, script: S) -> crate::Result<()> {
self
.0
.window
.evaluate_script(script)
.map_err(|_| crate::Error::FailedToSendMessage)
}
}
/// A wrapper around the wry Application interface.
pub struct WryApplication {
pub struct Wry {
inner: wry::Application,
}
impl ApplicationExt for WryApplication {
type WebviewBuilder = wry::Attributes;
impl Runtime for Wry {
type Dispatcher = WryDispatcher;
fn plugin_store() -> &'static PluginStore<Self> {
static PLUGINS: Lazy<PluginStore<WryApplication>> = Lazy::new(Default::default);
&PLUGINS
}
fn new() -> crate::Result<Self> {
let app = wry::Application::new().map_err(|_| crate::Error::CreateWebview)?;
Ok(Self { inner: app })
}
fn create_webview(
fn create_window<M: Params<Runtime = Self>>(
&mut self,
webview_builder: Self::WebviewBuilder,
rpc_handler: Option<WebviewRpcHandler<Self::Dispatcher>>,
custom_protocol: Option<CustomProtocol>,
file_drop_handler: Option<FileDropHandler>,
) -> crate::Result<Self::Dispatcher> {
let app_dispatcher = self.inner.application_proxy();
pending: PendingWindow<M>,
) -> crate::Result<DetachedWindow<M>> {
let PendingWindow {
attributes,
rpc_handler,
custom_protocol,
file_drop_handler,
label,
..
} = pending;
let app_dispatcher_ = app_dispatcher.clone();
let wry_rpc_handler = Box::new(
move |dispatcher: wry::WindowProxy, request: wry::RpcRequest| {
if let Some(handler) = &rpc_handler {
handler(
WryDispatcher(dispatcher, app_dispatcher_.clone()),
request.into(),
);
}
None
},
);
let proxy = self.inner.application_proxy();
let file_drop_handler = Box::new(move |event: wry::FileDropEvent| {
if let Some(handler) = &file_drop_handler {
handler(event.into())
} else {
false
}
});
let rpc_handler =
rpc_handler.map(|handler| create_rpc_handler(proxy.clone(), label.clone(), handler));
let dispatcher = self
let file_drop_handler = file_drop_handler
.map(|handler| create_file_drop_handler(proxy.clone(), label.clone(), handler));
let window = self
.inner
.add_window_with_configs(
webview_builder.finish()?,
Some(wry_rpc_handler),
custom_protocol.map(|p| wry::CustomProtocol {
name: p.name.clone(),
handler: Box::new(move |a| (*p.handler)(a).map_err(|_| wry::Error::InitScriptError)),
}),
Some(file_drop_handler),
attributes,
rpc_handler,
custom_protocol.map(create_custom_protocol),
file_drop_handler,
)
.map_err(|_| crate::Error::CreateWebview)?;
Ok(WryDispatcher(dispatcher, app_dispatcher))
let dispatcher = WryDispatcher {
window,
application: proxy,
};
Ok(DetachedWindow { label, dispatcher })
}
fn run(self) {
wry::Application::run(self.inner)
}
}
/// Create a wry rpc handler from a tauri rpc handler.
fn create_rpc_handler<M: Params<Runtime = Wry>>(
app_proxy: wry::ApplicationProxy,
label: M::Label,
handler: WebviewRpcHandler<M>,
) -> wry::WindowRpcHandler {
Box::new(move |window, request| {
handler(
DetachedWindow {
dispatcher: WryDispatcher {
window,
application: app_proxy.clone(),
},
label: label.clone(),
},
request.into(),
);
None
})
}
/// Create a wry file drop handler from a tauri file drop handler.
fn create_file_drop_handler<M: Params<Runtime = Wry>>(
app_proxy: wry::ApplicationProxy,
label: M::Label,
handler: FileDropHandler<M>,
) -> wry::WindowFileDropHandler {
Box::new(move |window, event| {
handler(
event.into(),
DetachedWindow {
dispatcher: WryDispatcher {
window,
application: app_proxy.clone(),
},
label: label.clone(),
},
)
})
}
/// Create a wry custom protocol from a tauri custom protocol.
fn create_custom_protocol(custom_protocol: CustomProtocol) -> wry::CustomProtocol {
wry::CustomProtocol {
name: custom_protocol.name.clone(),
handler: Box::new(move |data| {
(custom_protocol.handler)(data).map_err(|_| wry::Error::InitScriptError)
}),
}
}

View File

@ -0,0 +1,527 @@
use crate::{
api::{
assets::Assets,
config::{Config, WindowUrl},
},
event::{Event, EventHandler, Listeners},
hooks::{InvokeHandler, InvokeMessage, InvokePayload, OnPageLoad, PageLoadPayload},
plugin::PluginStore,
runtime::{
sealed::ParamsPrivate,
tag::{tags_to_javascript_array, Tag, ToJavascript},
webview::{
Attributes, AttributesPrivate, CustomProtocol, FileDropEvent, FileDropHandler,
WebviewRpcHandler,
},
window::{DetachedWindow, PendingWindow, Window},
Context, Dispatch, Icon, Params, Runtime,
},
};
use serde::Serialize;
use serde_json::Value as JsonValue;
use std::{
borrow::Cow,
collections::HashSet,
convert::TryInto,
sync::{Arc, Mutex},
};
pub struct InnerWindowManager<M: Params> {
windows: Mutex<HashSet<Window<M>>>,
plugins: Mutex<PluginStore<M>>,
listeners: Listeners<M::Event, M::Label>,
/// The JS message handler.
invoke_handler: Box<InvokeHandler<M>>,
/// The page load hook, invoked when the webview performs a navigation.
on_page_load: Box<OnPageLoad<M>>,
config: Config,
assets: Arc<M::Assets>,
default_window_icon: Option<Vec<u8>>,
}
pub struct WindowManager<E, L, A, R>
where
E: Tag,
L: Tag,
A: Assets + 'static,
R: Runtime,
{
pub(crate) inner: Arc<InnerWindowManager<Self>>,
}
impl<E, L, A, R> Clone for WindowManager<E, L, A, R>
where
E: Tag,
L: Tag,
A: Assets + 'static,
R: Runtime,
{
fn clone(&self) -> Self {
Self {
inner: self.inner.clone(),
}
}
}
impl<E, L, A, R> WindowManager<E, L, A, R>
where
E: Tag,
L: Tag,
A: Assets,
R: Runtime,
{
pub(crate) fn with_handlers(
context: Context<A>,
invoke_handler: Box<InvokeHandler<Self>>,
on_page_load: Box<OnPageLoad<Self>>,
) -> Self {
Self {
inner: Arc::new(InnerWindowManager {
windows: Mutex::default(),
plugins: Mutex::default(),
listeners: Listeners::default(),
invoke_handler,
on_page_load,
config: context.config,
assets: Arc::new(context.assets),
default_window_icon: context.default_window_icon,
}),
}
}
// setup content for dev-server
#[cfg(dev)]
fn get_url(&self) -> String {
if self.inner.config.build.dev_path.starts_with("http") {
self.inner.config.build.dev_path.clone()
} else {
let path = "index.html";
format!(
"data:text/html;base64,{}",
base64::encode(
self
.inner
.assets
.get(&path)
.ok_or_else(|| crate::Error::AssetNotFound(path.to_string()))
.map(Cow::into_owned)
.expect("Unable to find `index.html` under your devPath folder")
)
)
}
}
#[cfg(custom_protocol)]
fn get_url(&self) -> String {
format!("tauri://{}", self.inner.config.tauri.bundle.identifier)
}
fn prepare_attributes(
&self,
attrs: <R::Dispatcher as Dispatch>::Attributes,
url: String,
label: L,
pending_labels: &[L],
) -> crate::Result<<R::Dispatcher as Dispatch>::Attributes> {
let is_init_global = self.inner.config.build.with_global_tauri;
let plugin_init = self
.inner
.plugins
.lock()
.expect("poisoned plugin store")
.initialization_script();
let mut attributes = attrs
.url(url)
.initialization_script(&self.initialization_script(&plugin_init, is_init_global))
.initialization_script(&format!(
r#"
window.__TAURI__.__windows = {window_labels_array}.map(function (label) {{ return {{ label: label }} }});
window.__TAURI__.__currentWindow = {{ label: {current_window_label} }}
"#,
window_labels_array = tags_to_javascript_array(pending_labels)?,
current_window_label = label.to_javascript()?,
));
if !attributes.has_icon() {
if let Some(default_window_icon) = &self.inner.default_window_icon {
let icon = Icon::Raw(default_window_icon.clone());
let icon = icon.try_into().expect("infallible icon convert failed");
attributes = attributes.icon(icon);
}
}
Ok(attributes)
}
fn prepare_rpc_handler(&self) -> WebviewRpcHandler<Self> {
let manager = self.clone();
Box::new(move |window, request| {
let window = manager.attach_window(window);
let command = request.command.clone();
let arg = request
.params
.unwrap()
.as_array_mut()
.unwrap()
.first_mut()
.unwrap_or(&mut JsonValue::Null)
.take();
match serde_json::from_value::<InvokePayload>(arg) {
Ok(message) => {
let _ = window.on_message(command, message);
}
Err(e) => {
let error: crate::Error = e.into();
let _ = window.eval(&format!(
r#"console.error({})"#,
JsonValue::String(error.to_string())
));
}
}
})
}
fn prepare_custom_protocol(&self) -> CustomProtocol {
let assets = self.inner.assets.clone();
let bundle_identifier = self.inner.config.tauri.bundle.identifier.clone();
CustomProtocol {
name: "tauri".into(),
handler: Box::new(move |path| {
let mut path = path
.split('?')
// ignore query string
.next()
.unwrap()
.to_string()
.replace(&format!("tauri://{}", bundle_identifier), "");
if path.ends_with('/') {
path.pop();
}
let path = if path.is_empty() {
// if the url is `tauri://${appId}`, we should load `index.html`
"index.html".to_string()
} else {
// skip leading `/`
path.chars().skip(1).collect::<String>()
};
let asset_response = assets
.get(&path)
.ok_or(crate::Error::AssetNotFound(path))
.map(Cow::into_owned);
match asset_response {
Ok(asset) => Ok(asset),
Err(e) => {
#[cfg(debug_assertions)]
eprintln!("{:?}", e); // TODO log::error!
Err(e)
}
}
}),
}
}
fn prepare_file_drop(&self) -> FileDropHandler<Self> {
let manager = self.clone();
Box::new(move |event, window| {
let manager = manager.clone();
crate::async_runtime::block_on(async move {
let window = manager.attach_window(window);
let _ = match event {
FileDropEvent::Hovered(paths) => {
window.emit_internal("tauri://file-drop".to_string(), Some(paths))
}
FileDropEvent::Dropped(paths) => {
window.emit_internal("tauri://file-drop-hover".to_string(), Some(paths))
}
FileDropEvent::Cancelled => {
window.emit_internal("tauri://file-drop-cancelled".to_string(), Some(()))
}
};
});
true
})
}
fn initialization_script(
&self,
plugin_initialization_script: &str,
with_global_tauri: bool,
) -> String {
format!(
r#"
{bundle_script}
{core_script}
{event_initialization_script}
if (window.rpc) {{
window.__TAURI__.invoke("__initialized", {{ url: window.location.href }})
}} else {{
window.addEventListener('DOMContentLoaded', function () {{
window.__TAURI__.invoke("__initialized", {{ url: window.location.href }})
}})
}}
{plugin_initialization_script}
"#,
core_script = include_str!("../../scripts/core.js"),
bundle_script = if with_global_tauri {
include_str!("../../scripts/bundle.js")
} else {
""
},
event_initialization_script = self.event_initialization_script(),
plugin_initialization_script = plugin_initialization_script
)
}
fn event_initialization_script(&self) -> String {
return format!(
"
window['{queue}'] = [];
window['{function}'] = function (eventData, salt, ignoreQueue) {{
const listeners = (window['{listeners}'] && window['{listeners}'][eventData.event]) || []
if (!ignoreQueue && listeners.length === 0) {{
window['{queue}'].push({{
eventData: eventData,
salt: salt
}})
}}
if (listeners.length > 0) {{
window.__TAURI__.invoke('tauri', {{
__tauriModule: 'Internal',
message: {{
cmd: 'validateSalt',
salt: salt
}}
}}).then(function (flag) {{
if (flag) {{
for (let i = listeners.length - 1; i >= 0; i--) {{
const listener = listeners[i]
eventData.id = listener.id
listener.handler(eventData)
}}
}}
}})
}}
}}
",
function = self.inner.listeners.function_name(),
queue = self.inner.listeners.queue_object_name(),
listeners = self.inner.listeners.listeners_object_name()
);
}
}
#[cfg(test)]
mod test {
use crate::{generate_context, runtime::flavor::wry::Wry};
use super::WindowManager;
#[test]
fn check_get_url() {
let context = generate_context!("test/fixture/src-tauri/tauri.conf.json", crate::Context);
let manager: WindowManager<String, String, _, Wry> =
WindowManager::with_handlers(context, Box::new(|_| ()), Box::new(|_, _| ()));
#[cfg(custom_protocol)]
assert_eq!(manager.get_url(), "tauri://studio.tauri.example");
#[cfg(dev)]
{
use crate::runtime::sealed::ParamsPrivate;
assert_eq!(manager.get_url(), manager.config().build.dev_path);
}
}
}
impl<E, L, A, R> ParamsPrivate<Self> for WindowManager<E, L, A, R>
where
E: Tag,
L: Tag,
A: Assets + 'static,
R: Runtime,
{
fn run_invoke_handler(&self, message: InvokeMessage<Self>) {
(self.inner.invoke_handler)(message);
}
fn run_on_page_load(&self, window: Window<Self>, payload: PageLoadPayload) {
(self.inner.on_page_load)(window.clone(), payload.clone());
self
.inner
.plugins
.lock()
.expect("poisoned plugin store")
.on_page_load(window, payload);
}
fn extend_api(&self, command: String, message: InvokeMessage<Self>) {
self
.inner
.plugins
.lock()
.expect("poisoned plugin store")
.extend_api(command, message);
}
fn initialize_plugins(&self) -> crate::Result<()> {
self
.inner
.plugins
.lock()
.expect("poisoned plugin store")
.initialize(&self.inner.config.plugins)
}
fn prepare_window(
&self,
mut pending: PendingWindow<Self>,
pending_labels: &[L],
) -> crate::Result<PendingWindow<Self>> {
let (is_local, url) = match &pending.url {
WindowUrl::App => (true, self.get_url()),
// todo: we should probably warn about how custom urls usually need to be valid urls
// e.g. cannot be relative without a base
WindowUrl::Custom(url) => (url.len() > 7 && &url[0..8] == "tauri://", url.clone()),
};
let attributes = pending.attributes.clone();
if is_local {
let label = pending.label.clone();
pending.attributes = self.prepare_attributes(attributes, url, label, pending_labels)?;
pending.rpc_handler = Some(self.prepare_rpc_handler());
pending.custom_protocol = Some(self.prepare_custom_protocol());
} else {
pending.attributes = attributes.url(url);
}
pending.file_drop_handler = Some(self.prepare_file_drop());
Ok(pending)
}
fn attach_window(&self, window: DetachedWindow<Self>) -> Window<Self> {
let window = Window::new(self.clone(), window);
// insert the window into our manager
{
self
.inner
.windows
.lock()
.expect("poisoned window manager")
.insert(window.clone());
}
// let plugins know that a new window has been added to the manager
{
self
.inner
.plugins
.lock()
.expect("poisoned plugin store")
.created(window.clone());
}
window
}
fn emit_filter_internal<S: Serialize + Clone, F: Fn(&Window<Self>) -> bool>(
&self,
event: String,
payload: Option<S>,
filter: F,
) -> crate::Result<()> {
self
.inner
.windows
.lock()
.expect("poisoned window manager")
.iter()
.filter(|&w| filter(w))
.try_for_each(|window| window.emit_internal(event.clone(), payload.clone()))
}
fn emit_filter<S: Serialize + Clone, F: Fn(&Window<Self>) -> bool>(
&self,
event: E,
payload: Option<S>,
filter: F,
) -> crate::Result<()> {
self
.inner
.windows
.lock()
.expect("poisoned window manager")
.iter()
.filter(|&w| filter(w))
.try_for_each(|window| window.emit(&event, payload.clone()))
}
fn labels(&self) -> HashSet<L> {
self
.inner
.windows
.lock()
.expect("poisoned window manager")
.iter()
.map(|w| w.label().clone())
.collect()
}
fn config(&self) -> &Config {
&self.inner.config
}
fn unlisten(&self, handler_id: EventHandler) {
self.inner.listeners.unlisten(handler_id)
}
fn trigger(&self, event: E, window: Option<L>, data: Option<String>) {
self.inner.listeners.trigger(event, window, data)
}
fn listen<F: Fn(Event) + Send + 'static>(
&self,
event: E,
window: Option<L>,
handler: F,
) -> EventHandler {
self.inner.listeners.listen(event, window, handler)
}
fn once<F: Fn(Event) + Send + 'static>(&self, event: E, window: Option<L>, handler: F) {
self.inner.listeners.once(event, window, handler)
}
fn event_listeners_object_name(&self) -> String {
self.inner.listeners.listeners_object_name()
}
fn event_queue_object_name(&self) -> String {
self.inner.listeners.queue_object_name()
}
fn event_emit_function_name(&self) -> String {
self.inner.listeners.function_name()
}
}
impl<E, L, A, R> Params for WindowManager<E, L, A, R>
where
E: Tag,
L: Tag,
A: Assets,
R: Runtime,
{
type Event = E;
type Label = L;
type Assets = A;
type Runtime = R;
}

328
tauri/src/runtime/mod.rs Normal file
View File

@ -0,0 +1,328 @@
use crate::{
api::{assets::Assets, config::Config},
event::{Event, EventHandler},
runtime::{
tag::Tag,
webview::{Attributes, AttributesPrivate, Icon, WindowConfig},
window::{DetachedWindow, PendingWindow, Window},
},
};
use serde::Serialize;
use std::convert::TryFrom;
pub(crate) mod app;
pub mod flavor;
pub(crate) mod manager;
pub(crate) mod tag;
pub(crate) mod webview;
pub(crate) mod window;
/// Important configurable items required by Tauri.
pub struct Context<A: Assets> {
/// The config the application was prepared with.
pub config: Config,
/// The assets to be served directly by Tauri.
pub assets: A,
/// The default window icon Tauri should use when creating windows.
pub default_window_icon: Option<Vec<u8>>,
}
/// The webview runtime interface.
pub trait Runtime: Sized + 'static {
/// The message dispatcher.
type Dispatcher: Dispatch<Runtime = Self>;
/// Creates a new webview runtime.
fn new() -> crate::Result<Self>;
/// Creates a new webview window.
fn create_window<M: Params<Runtime = Self>>(
&mut self,
pending: PendingWindow<M>,
) -> crate::Result<DetachedWindow<M>>;
/// Run the webview runtime.
fn run(self);
}
/// Webview dispatcher. A thread-safe handle to the webview API.
pub trait Dispatch: Clone + Send + Sized + 'static {
/// The runtime this [`Dispatch`] runs under.
type Runtime: Runtime;
/// Representation of a window icon.
type Icon: TryFrom<Icon, Error = crate::Error>;
/// The webview builder type.
type Attributes: Attributes<Icon = Self::Icon>
+ AttributesPrivate
+ From<WindowConfig>
+ Clone
+ Send;
/// Creates a new webview window.
fn create_window<M: Params<Runtime = Self::Runtime>>(
&mut self,
pending: PendingWindow<M>,
) -> crate::Result<DetachedWindow<M>>;
/// Updates the window resizable flag.
fn set_resizable(&self, resizable: bool) -> crate::Result<()>;
/// Updates the window title.
fn set_title<S: Into<String>>(&self, title: S) -> crate::Result<()>;
/// Maximizes the window.
fn maximize(&self) -> crate::Result<()>;
/// Unmaximizes the window.
fn unmaximize(&self) -> crate::Result<()>;
/// Minimizes the window.
fn minimize(&self) -> crate::Result<()>;
/// Unminimizes the window.
fn unminimize(&self) -> crate::Result<()>;
/// Shows the window.
fn show(&self) -> crate::Result<()>;
/// Hides the window.
fn hide(&self) -> crate::Result<()>;
/// Closes the window.
fn close(&self) -> crate::Result<()>;
/// Updates the hasDecorations flag.
fn set_decorations(&self, decorations: bool) -> crate::Result<()>;
/// Updates the window alwaysOnTop flag.
fn set_always_on_top(&self, always_on_top: bool) -> crate::Result<()>;
/// Updates the window width.
fn set_width(&self, width: f64) -> crate::Result<()>;
/// Updates the window height.
fn set_height(&self, height: f64) -> crate::Result<()>;
/// Resizes the window.
fn resize(&self, width: f64, height: f64) -> crate::Result<()>;
/// Updates the window min size.
fn set_min_size(&self, min_width: f64, min_height: f64) -> crate::Result<()>;
/// Updates the window max size.
fn set_max_size(&self, max_width: f64, max_height: f64) -> crate::Result<()>;
/// Updates the X position.
fn set_x(&self, x: f64) -> crate::Result<()>;
/// Updates the Y position.
fn set_y(&self, y: f64) -> crate::Result<()>;
/// Updates the window position.
fn set_position(&self, x: f64, y: f64) -> crate::Result<()>;
/// Updates the window fullscreen state.
fn set_fullscreen(&self, fullscreen: bool) -> crate::Result<()>;
/// Updates the window icon.
fn set_icon(&self, icon: Self::Icon) -> crate::Result<()>;
/// Executes javascript on the window this [`Dispatch`] represents.
fn eval_script<S: Into<String>>(&self, script: S) -> crate::Result<()>;
}
/// Prevent implementation details from leaking out of the [`Manager`] and [`Managed`] traits.
pub(crate) mod sealed {
use super::Params;
use crate::{
api::config::Config,
event::{Event, EventHandler},
hooks::{InvokeMessage, PageLoadPayload},
runtime::{
window::{DetachedWindow, PendingWindow, Window},
RuntimeOrDispatch,
},
};
use serde::Serialize;
use std::collections::HashSet;
/// private manager api
pub trait ParamsPrivate<M: Params>: Clone + Send + Sized + 'static {
/// Pass messages not handled by modules or plugins to the running application
fn run_invoke_handler(&self, message: InvokeMessage<M>);
/// Ran once for every window when the page is loaded.
fn run_on_page_load(&self, window: Window<M>, payload: PageLoadPayload);
/// Pass a message to be handled by a plugin that expects the command.
fn extend_api(&self, command: String, message: InvokeMessage<M>);
/// Initialize all the plugins attached to the [`Manager`].
fn initialize_plugins(&self) -> crate::Result<()>;
/// Prepare a [`PendingWindow`] to be created by the [`Runtime`].
///
/// The passed labels should represent either all the windows in the manager. If the application
/// has not yet been started, the passed labels should represent all windows that will be
/// created before starting.
fn prepare_window(
&self,
pending: PendingWindow<M>,
labels: &[M::Label],
) -> crate::Result<PendingWindow<M>>;
/// Attach a detached window to the manager.
fn attach_window(&self, window: DetachedWindow<M>) -> Window<M>;
/// Emit an event to javascript windows that pass the predicate.
fn emit_filter_internal<S: Serialize + Clone, F: Fn(&Window<Self>) -> bool>(
&self,
event: String,
payload: Option<S>,
filter: F,
) -> crate::Result<()>;
/// Emit an event to javascript windows that pass the predicate.
fn emit_filter<S: Serialize + Clone, F: Fn(&Window<M>) -> bool>(
&self,
event: M::Event,
payload: Option<S>,
predicate: F,
) -> crate::Result<()>;
/// All current window labels existing.
fn labels(&self) -> HashSet<M::Label>;
/// The configuration the [`Manager`] was built with.
fn config(&self) -> &Config;
/// Remove the specified event handler.
fn unlisten(&self, handler_id: EventHandler);
/// Trigger an event.
fn trigger(&self, event: M::Event, window: Option<M::Label>, data: Option<String>);
/// Set up a listener to an event.
fn listen<F: Fn(Event) + Send + 'static>(
&self,
event: M::Event,
window: Option<M::Label>,
handler: F,
) -> EventHandler;
/// Set up a listener to and event that is automatically removed after called once.
fn once<F: Fn(Event) + Send + 'static>(
&self,
event: M::Event,
window: Option<M::Label>,
handler: F,
);
fn event_listeners_object_name(&self) -> String;
fn event_queue_object_name(&self) -> String;
fn event_emit_function_name(&self) -> String;
}
/// Represents a managed handle to the application runner.
pub trait ManagerPrivate<M: Params> {
/// The manager behind the [`Managed`] item.
fn manager(&self) -> &M;
/// The runtime or runtime dispatcher of the [`Managed`] item.
fn runtime(&mut self) -> RuntimeOrDispatch<'_, M>;
}
}
/// Represents either a [`Runtime`] or its dispatcher.
pub enum RuntimeOrDispatch<'m, M: Params> {
/// Mutable reference to the [`Runtime`].
Runtime(&'m mut M::Runtime),
/// Copy of the [`Runtime`]'s dispatcher.
Dispatch(<M::Runtime as Runtime>::Dispatcher),
}
/// Represents a managed handle to the application runner
pub trait Manager<M: Params>: sealed::ManagerPrivate<M> {
/// The [`Config`] the manager was created with.
fn config(&self) -> &Config {
self.manager().config()
}
/// Emits a event to all windows.
fn emit_all<S: Serialize + Clone>(
&self,
event: M::Event,
payload: Option<S>,
) -> crate::Result<()> {
self.manager().emit_filter(event, payload, |_| true)
}
/// Emits an event to a window with the specified label.
fn emit_to<S: Serialize + Clone>(
&self,
label: &M::Label,
event: M::Event,
payload: Option<S>,
) -> crate::Result<()> {
self
.manager()
.emit_filter(event, payload, |w| w.label() == label)
}
/// Creates a new [`Window`] on the [`Runtime`] and attaches it to the [`Manager`].
fn create_window(&mut self, pending: PendingWindow<M>) -> crate::Result<Window<M>> {
let labels = self.manager().labels().into_iter().collect::<Vec<_>>();
let pending = self.manager().prepare_window(pending, &labels)?;
match self.runtime() {
RuntimeOrDispatch::Runtime(runtime) => runtime.create_window(pending),
RuntimeOrDispatch::Dispatch(mut dispatcher) => dispatcher.create_window(pending),
}
.map(|window| self.manager().attach_window(window))
}
/// Listen to a global event.
fn listen_global<F>(&self, event: M::Event, handler: F) -> EventHandler
where
F: Fn(Event) + Send + 'static,
{
self.manager().listen(event, None, handler)
}
/// Listen to a global event only once.
fn once_global<F>(&self, event: M::Event, handler: F)
where
F: Fn(Event) + Send + 'static,
{
self.manager().once(event, None, handler)
}
/// Trigger a global event.
fn trigger_global(&self, event: M::Event, data: Option<String>) {
self.manager().trigger(event, None, data)
}
/// Remove an event listener.
fn unlisten(&self, handler_id: EventHandler) {
self.manager().unlisten(handler_id)
}
}
/// Types that the manager needs to have passed in by the application.
pub trait Params: sealed::ParamsPrivate<Self> {
/// The event type used to create and listen to events.
type Event: Tag;
/// The type used to determine the name of windows.
type Label: Tag;
/// Assets that Tauri should serve from itself.
type Assets: Assets;
/// The underlying webview runtime used by the Tauri application.
type Runtime: Runtime;
}

48
tauri/src/runtime/tag.rs Normal file
View File

@ -0,0 +1,48 @@
//! Working with "string-able" types.
use std::{
fmt::{Debug, Display},
hash::Hash,
str::FromStr,
};
/// Represents a "string-able" type.
///
/// The type is required to be able to be represented as a string [`Display`], along with knowing
/// how to be parsed from the string representation [`FromStr`].
///
/// [`Clone`], [`Hash`], and [`Eq`] are needed so that it can represent un-hashable types.
///
/// [`Send`] and [`Sync`] and `'static` are current requirements due to how it is sometimes sent
/// across thread boundaries, although some of those constraints may relax in the future.
///
/// The simplest type that fits all these requirements is a [`String`](std::string::String).
pub trait Tag: Hash + Eq + FromStr + Display + Debug + Clone + Send + Sync + 'static {}
/// Automatically implement [`Tag`] for all types that fit the requirements.
impl<T> Tag for T where T: Hash + Eq + FromStr + Display + Debug + Clone + Send + Sync + 'static {}
/// Private helper to turn [`Tag`] related things into JavaScript, safely.
///
/// The main concern is string escaping, so we rely on [`serde_json`] to handle all serialization
/// of the [`Tag`] as a string. We do this instead of requiring [`serde::Serialize`] on [`Tag`]
/// because it really represents a string, not any serializable data structure.
///
/// We don't want downstream users to implement this trait so that [`Tag`]s cannot be turned into
/// invalid JavaScript - regardless of their content.
pub(crate) trait ToJavascript {
fn to_javascript(&self) -> crate::Result<String>;
}
impl<T: Tag> ToJavascript for T {
/// Turn any [`Tag`] into the JavaScript representation of a string.
fn to_javascript(&self) -> crate::Result<String> {
Ok(serde_json::to_string(&self.to_string())?)
}
}
/// Turn any collection of [`Tag`]s into a JavaScript array of strings.
pub(crate) fn tags_to_javascript_array(tags: &[impl Tag]) -> crate::Result<String> {
let tags: Vec<String> = tags.iter().map(ToString::to_string).collect();
Ok(serde_json::to_string(&tags)?)
}

View File

@ -0,0 +1,127 @@
use crate::runtime::window::DetachedWindow;
use serde_json::Value as JsonValue;
use std::{convert::TryFrom, path::PathBuf};
/// A icon definition.
pub enum Icon {
/// Icon from file path.
File(PathBuf),
/// Icon from raw bytes.
Raw(Vec<u8>),
}
pub struct WindowConfig(pub crate::api::config::WindowConfig);
pub trait AttributesPrivate: Sized {
/// Sets the webview url.
fn url(self, url: String) -> Self;
}
/// The webview builder.
pub trait Attributes: Sized {
/// Expected icon format.
type Icon: TryFrom<Icon, Error = crate::Error>;
/// Initializes a new webview builder.
fn new() -> Self;
/// Sets the init script.
fn initialization_script(self, init: &str) -> Self;
/// The horizontal position of the window's top left corner.
fn x(self, x: f64) -> Self;
/// The vertical position of the window's top left corner.
fn y(self, y: f64) -> Self;
/// Window width.
fn width(self, width: f64) -> Self;
/// Window height.
fn height(self, height: f64) -> Self;
/// Window min width.
fn min_width(self, min_width: f64) -> Self;
/// Window min height.
fn min_height(self, min_height: f64) -> Self;
/// Window max width.
fn max_width(self, max_width: f64) -> Self;
/// Window max height.
fn max_height(self, max_height: f64) -> 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<S: Into<String>>(self, title: S) -> Self;
/// Whether to start the window in fullscreen or not.
fn fullscreen(self, fullscreen: bool) -> 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;
/// Sets the window icon.
fn icon(self, icon: Self::Icon) -> Self;
/// Whether the icon was set or not.
fn has_icon(&self) -> bool;
/// User data path for the webview. Actually only supported on Windows.
fn user_data_path(self, user_data_path: Option<PathBuf>) -> Self;
/// The full attributes.
fn build(self) -> Self;
}
// TODO: should probably expand the following documentation
/// Rpc request.
pub struct RpcRequest {
/// RPC command.
pub command: String,
/// Params.
pub params: Option<JsonValue>,
}
/// Rpc handler.
pub type WebviewRpcHandler<M> = Box<dyn Fn(DetachedWindow<M>, RpcRequest) + Send>;
/// Uses a custom handler to resolve file requests
pub struct CustomProtocol {
/// Name of the protocol
pub name: String,
/// Handler for protocol
pub handler: Box<dyn Fn(&str) -> crate::Result<Vec<u8>> + Send>,
}
/// The file drop event payload.
#[derive(Debug, Clone)]
pub enum FileDropEvent {
/// The file(s) have been dragged onto the window, but have not been dropped yet.
Hovered(Vec<PathBuf>),
/// The file(s) have been dropped onto the window.
Dropped(Vec<PathBuf>),
/// The file drop was aborted.
Cancelled,
}
/// File drop handler callback
/// Return `true` in the callback to block the OS' default behavior of handling a file drop.
pub type FileDropHandler<M> = Box<dyn Fn(FileDropEvent, DetachedWindow<M>) -> bool + Send>;

364
tauri/src/runtime/window.rs Normal file
View File

@ -0,0 +1,364 @@
use crate::{
api::config::WindowUrl,
event::{Event, EventHandler},
hooks::{InvokeMessage, InvokePayload, PageLoadPayload},
runtime::{
sealed::ManagerPrivate,
tag::ToJavascript,
webview::{CustomProtocol, FileDropHandler, Icon, WebviewRpcHandler},
Dispatch, Manager, Params, Runtime, RuntimeOrDispatch,
},
};
use serde::Serialize;
use serde_json::Value as JsonValue;
use std::{
convert::TryInto,
hash::{Hash, Hasher},
};
/// A webview window that has yet to be built.
pub struct PendingWindow<M: Params> {
/// The label that the window will be named.
pub label: M::Label,
/// The url the window will open with.
pub url: WindowUrl,
/// The [`Attributes`] that the webview window be created with.
pub attributes: <<M::Runtime as Runtime>::Dispatcher as Dispatch>::Attributes,
/// How to handle RPC calls on the webview window.
pub rpc_handler: Option<WebviewRpcHandler<M>>,
/// How to handle custom protocols for the webview window.
pub custom_protocol: Option<CustomProtocol>,
/// How to handle a file dropping onto the webview window.
pub file_drop_handler: Option<FileDropHandler<M>>,
}
impl<M: Params> PendingWindow<M> {
pub fn new(
attributes: impl Into<<<M::Runtime as Runtime>::Dispatcher as Dispatch>::Attributes>,
label: M::Label,
url: WindowUrl,
) -> Self {
Self {
attributes: attributes.into(),
label,
url,
rpc_handler: None,
custom_protocol: None,
file_drop_handler: None,
}
}
}
/// A webview window that is not yet managed by Tauri.
pub struct DetachedWindow<M: Params> {
pub label: M::Label,
pub dispatcher: <M::Runtime as Runtime>::Dispatcher,
}
impl<M: Params> Clone for DetachedWindow<M> {
fn clone(&self) -> Self {
Self {
label: self.label.clone(),
dispatcher: self.dispatcher.clone(),
}
}
}
impl<M: Params> Hash for DetachedWindow<M> {
/// Only use the [`DetachedWindow`]'s label to represent its hash.
fn hash<H: Hasher>(&self, state: &mut H) {
self.label.hash(state)
}
}
impl<M: Params> Eq for DetachedWindow<M> {}
impl<M: Params> PartialEq for DetachedWindow<M> {
/// Only use the [`DetachedWindow`]'s label to compare equality.
fn eq(&self, other: &Self) -> bool {
self.label.eq(&other.label)
}
}
/// A webview window managed by Tarui.
///
/// TODO: expand these docs since this is a pretty important type
pub struct Window<M: Params> {
/// The webview window created by the runtime.
window: DetachedWindow<M>,
/// The manager to associate this webview window with.
manager: M,
}
impl<M: Params> Clone for Window<M> {
fn clone(&self) -> Self {
Self {
window: self.window.clone(),
manager: self.manager.clone(),
}
}
}
impl<M: Params> Hash for Window<M> {
/// Only use the [`Window`]'s label to represent its hash.
fn hash<H: Hasher>(&self, state: &mut H) {
self.window.label.hash(state)
}
}
impl<M: Params> Eq for Window<M> {}
impl<M: Params> PartialEq for Window<M> {
/// Only use the [`Window`]'s label to compare equality.
fn eq(&self, other: &Self) -> bool {
self.window.label.eq(&other.window.label)
}
}
impl<M: Params> Manager<M> for Window<M> {}
impl<M: Params> ManagerPrivate<M> for Window<M> {
fn manager(&self) -> &M {
&self.manager
}
fn runtime(&mut self) -> RuntimeOrDispatch<'_, M> {
RuntimeOrDispatch::Dispatch(self.dispatcher())
}
}
impl<M: Params> Window<M> {
/// Create a new window that is attached to the manager.
pub(crate) fn new(manager: M, window: DetachedWindow<M>) -> Self {
Self { manager, window }
}
/// The current window's dispatcher.
pub(crate) fn dispatcher(&self) -> <M::Runtime as Runtime>::Dispatcher {
self.window.dispatcher.clone()
}
/// How to handle this window receiving an [`InvokeMessage`].
pub(crate) fn on_message(self, command: String, payload: InvokePayload) -> crate::Result<()> {
let manager = self.manager.clone();
if &command == "__initialized" {
let payload: PageLoadPayload = serde_json::from_value(payload.inner)?;
manager.run_on_page_load(self, payload);
} else {
let message = InvokeMessage::new(self, command.to_string(), payload);
if let Some(module) = &message.payload.tauri_module {
let module = module.to_string();
crate::endpoints::handle(module, message, manager.config());
} else if command.starts_with("plugin:") {
manager.extend_api(command, message);
} else {
manager.run_invoke_handler(message);
}
}
Ok(())
}
/// The label of this window.
pub fn label(&self) -> &M::Label {
&self.window.label
}
pub(crate) fn emit_internal<E: ToJavascript, S: Serialize>(
&self,
event: E,
payload: Option<S>,
) -> crate::Result<()> {
let js_payload = match payload {
Some(payload_value) => serde_json::to_value(payload_value)?,
None => JsonValue::Null,
};
self.eval(&format!(
"window['{}']({{event: {}, payload: {}}}, '{}')",
self.manager.event_emit_function_name(),
event.to_javascript()?,
js_payload,
crate::salt::generate()
))?;
Ok(())
}
/// Emits an event to the current window.
pub fn emit<S: Serialize>(&self, event: &M::Event, payload: Option<S>) -> crate::Result<()> {
self.emit_internal(event.clone(), payload)
}
pub(crate) fn emit_others_internal<S: Serialize + Clone>(
&self,
event: String,
payload: Option<S>,
) -> crate::Result<()> {
self
.manager
.emit_filter_internal(event, payload, |w| w != self)
}
/// Emits an event on all windows except this one.
pub fn emit_others<S: Serialize + Clone>(
&self,
event: M::Event,
payload: Option<S>,
) -> crate::Result<()> {
self.manager.emit_filter(event, payload, |w| w != self)
}
/// Listen to an event on this window.
pub fn listen<F>(&self, event: M::Event, handler: F) -> EventHandler
where
F: Fn(Event) + Send + 'static,
{
let label = self.window.label.clone();
self.manager.listen(event, Some(label), handler)
}
/// Listen to a an event on this window a single time.
pub fn once<F>(&self, event: M::Event, handler: F)
where
F: Fn(Event) + Send + 'static,
{
let label = self.window.label.clone();
self.manager.once(event, Some(label), handler)
}
/// Triggers an event on this window.
pub(crate) fn trigger(&self, event: M::Event, data: Option<String>) {
let label = self.window.label.clone();
self.manager.trigger(event, Some(label), data)
}
/// Evaluates JavaScript on this window.
pub fn eval(&self, js: &str) -> crate::Result<()> {
self.window.dispatcher.eval_script(js)
}
/// Determines if this window should be resizable.
pub fn set_resizable(&self, resizable: bool) -> crate::Result<()> {
self.window.dispatcher.set_resizable(resizable)
}
/// Set this window's title.
pub fn set_title(&self, title: &str) -> crate::Result<()> {
self.window.dispatcher.set_title(title.to_string())
}
/// Maximizes this window.
pub fn maximize(&self) -> crate::Result<()> {
self.window.dispatcher.maximize()
}
/// Un-maximizes this window.
pub fn unmaximize(&self) -> crate::Result<()> {
self.window.dispatcher.unmaximize()
}
/// Minimizes this window.
pub fn minimize(&self) -> crate::Result<()> {
self.window.dispatcher.minimize()
}
/// Un-minimizes this window.
pub fn unminimize(&self) -> crate::Result<()> {
self.window.dispatcher.unminimize()
}
/// Show this window.
pub fn show(&self) -> crate::Result<()> {
self.window.dispatcher.show()
}
/// Hide this window.
pub fn hide(&self) -> crate::Result<()> {
self.window.dispatcher.hide()
}
/// Closes this window.
pub fn close(&self) -> crate::Result<()> {
self.window.dispatcher.close()
}
/// Determines if this window should be [decorated].
///
/// [decorated]: https://en.wikipedia.org/wiki/Window_(computing)#Window_decoration
pub fn set_decorations(&self, decorations: bool) -> crate::Result<()> {
self.window.dispatcher.set_decorations(decorations)
}
/// Determines if this window should always be on top of other windows.
pub fn set_always_on_top(&self, always_on_top: bool) -> crate::Result<()> {
self.window.dispatcher.set_always_on_top(always_on_top)
}
/// Sets this window's width.
pub fn set_width(&self, width: impl Into<f64>) -> crate::Result<()> {
self.window.dispatcher.set_width(width.into())
}
/// Sets this window's height.
pub fn set_height(&self, height: impl Into<f64>) -> crate::Result<()> {
self.window.dispatcher.set_height(height.into())
}
/// Resizes this window.
pub fn resize(&self, width: impl Into<f64>, height: impl Into<f64>) -> crate::Result<()> {
self.window.dispatcher.resize(width.into(), height.into())
}
/// Sets this window's minimum size.
pub fn set_min_size(
&self,
min_width: impl Into<f64>,
min_height: impl Into<f64>,
) -> crate::Result<()> {
self
.window
.dispatcher
.set_min_size(min_width.into(), min_height.into())
}
/// Sets this window's maximum size.
pub fn set_max_size(
&self,
max_width: impl Into<f64>,
max_height: impl Into<f64>,
) -> crate::Result<()> {
self
.window
.dispatcher
.set_max_size(max_width.into(), max_height.into())
}
/// Sets this window's x position.
pub fn set_x(&self, x: impl Into<f64>) -> crate::Result<()> {
self.window.dispatcher.set_x(x.into())
}
/// Sets this window's y position.
pub fn set_y(&self, y: impl Into<f64>) -> crate::Result<()> {
self.window.dispatcher.set_y(y.into())
}
/// Sets this window's position.
pub fn set_position(&self, x: impl Into<f64>, y: impl Into<f64>) -> crate::Result<()> {
self.window.dispatcher.set_position(x.into(), y.into())
}
/// Determines if this window should be fullscreen.
pub fn set_fullscreen(&self, fullscreen: bool) -> crate::Result<()> {
self.window.dispatcher.set_fullscreen(fullscreen)
}
/// Sets this window' icon.
pub fn set_icon(&self, icon: Icon) -> crate::Result<()> {
self.window.dispatcher.set_icon(icon.try_into()?)
}
}