feat(core): add mult-window support (#1217)

This commit is contained in:
Lucas Fernandes Nogueira
2021-02-11 21:50:39 -03:00
committed by GitHub
parent 763d027dbb
commit 07208dff6c
63 changed files with 1334 additions and 509 deletions

5
.changes/event.md Normal file
View File

@@ -0,0 +1,5 @@
---
"tauri": minor
---
The `tauri::event` module has been moved to a Webview manager API.

5
.changes/multiwindow.md Normal file
View File

@@ -0,0 +1,5 @@
---
"tauri": minor
---
Added support to multiple windows.

View File

@@ -104,7 +104,7 @@ If you are interested in making a tauri-app, please visit the [documentation web
| Multithreading | Yes | No |
| Bytecode Delivery | Yes | No |
| Can Render PDF | Yes | No |
| Multiple Windows | Soon | Yes |
| Multiple Windows | Yes | Yes |
| Auto Updater | Soon | Yes (2) |
| Cross Platform | Yes | Yes |
| Custom App Icon | Yes | Yes |
@@ -116,7 +116,6 @@ If you are interested in making a tauri-app, please visit the [documentation web
| Localhost Server | Yes | Yes |
| No localhost option | Yes | No |
| Desktop Tray | Soon | Yes |
| Splashscreen | Yes | Yes |
| Sidecar Binaries | Yes | No |
#### Notes

View File

@@ -21,53 +21,6 @@ fn config_handle() -> &'static ConfigHandle {
&CONFING_HANDLE
}
/// The window configuration object.
#[derive(PartialEq, Clone, Deserialize, Serialize, Debug)]
#[serde(tag = "window", rename_all = "camelCase")]
pub struct WindowConfig {
/// The window width.
#[serde(default = "default_width")]
pub width: i32,
/// The window height.
#[serde(default = "default_height")]
pub height: i32,
/// Whether the window is resizable or not.
#[serde(default = "default_resizable")]
pub resizable: bool,
/// The window title.
#[serde(default = "default_title")]
pub title: String,
/// Whether the window starts as fullscreen or not.
#[serde(default)]
pub fullscreen: bool,
}
fn default_width() -> i32 {
800
}
fn default_height() -> i32 {
600
}
fn default_resizable() -> bool {
true
}
fn default_title() -> String {
"Tauri App".to_string()
}
fn default_window() -> WindowConfig {
WindowConfig {
width: default_width(),
height: default_height(),
resizable: default_resizable(),
title: default_title(),
fullscreen: false,
}
}
/// The embedded server port.
#[derive(PartialEq, Clone, Debug, Deserialize, Serialize)]
pub enum Port {
@@ -312,9 +265,6 @@ fn default_bundle() -> BundleConfig {
#[derive(PartialEq, Clone, Deserialize, Serialize, Debug)]
#[serde(tag = "tauri", rename_all = "camelCase")]
pub struct TauriConfig {
/// The window configuration.
#[serde(default = "default_window")]
pub window: WindowConfig,
/// The embeddedServer configuration.
#[serde(default = "default_embedded_server")]
pub embedded_server: EmbeddedServerConfig,
@@ -370,7 +320,6 @@ pub struct Config {
fn default_tauri() -> TauriConfig {
TauriConfig {
window: default_window(),
embedded_server: default_embedded_server(),
cli: None,
bundle: default_bundle(),

View File

@@ -420,36 +420,64 @@
},
"type": "object"
},
"window": {
"additionalProperties": false,
"defaultProperties": [],
"properties": {
"fullscreen": {
"type": "boolean"
},
"height": {
"type": "number"
},
"resizable": {
"type": "boolean"
},
"title": {
"type": "string"
},
"width": {
"type": "number"
}
"windows": {
"additionalItems": {
"anyOf": [
{
"additionalProperties": false,
"defaultProperties": [],
"properties": {
"fullscreen": {
"type": "boolean"
},
"height": {
"type": "number"
},
"resizable": {
"type": "boolean"
},
"title": {
"type": "string"
},
"width": {
"type": "number"
}
},
"required": ["title"],
"type": "object"
}
]
},
"required": ["title"],
"type": "object"
"items": [
{
"additionalProperties": false,
"defaultProperties": [],
"properties": {
"fullscreen": {
"type": "boolean"
},
"height": {
"type": "number"
},
"resizable": {
"type": "boolean"
},
"title": {
"type": "string"
},
"width": {
"type": "number"
}
},
"required": ["title"],
"type": "object"
}
],
"minItems": 1,
"type": "array"
}
},
"required": [
"allowlist",
"bundle",
"security",
"window"
],
"required": ["allowlist", "bundle", "security", "windows"],
"type": "object"
},
"verbose": {

View File

@@ -277,13 +277,15 @@ export interface TauriConfig {
all: boolean
[index: string]: boolean
}
window: {
title: string
width?: number
height?: number
resizable?: boolean
fullscreen?: boolean
}
windows: [
{
title: string
width?: number
height?: number
resizable?: boolean
fullscreen?: boolean
}
]
security: {
csp?: string
}

View File

@@ -472,31 +472,64 @@ export const TauriConfigSchema = {
},
type: 'object'
},
window: {
additionalProperties: false,
defaultProperties: [],
properties: {
fullscreen: {
type: 'boolean'
},
height: {
type: 'number'
},
resizable: {
type: 'boolean'
},
title: {
type: 'string'
},
width: {
type: 'number'
}
windows: {
additionalItems: {
anyOf: [
{
additionalProperties: false,
defaultProperties: [],
properties: {
fullscreen: {
type: 'boolean'
},
height: {
type: 'number'
},
resizable: {
type: 'boolean'
},
title: {
type: 'string'
},
width: {
type: 'number'
}
},
required: ['title'],
type: 'object'
}
]
},
required: ['title'],
type: 'object'
items: [
{
additionalProperties: false,
defaultProperties: [],
properties: {
fullscreen: {
type: 'boolean'
},
height: {
type: 'number'
},
resizable: {
type: 'boolean'
},
title: {
type: 'string'
},
width: {
type: 'number'
}
},
required: ['title'],
type: 'object'
}
],
minItems: 1,
type: 'array'
}
},
required: ['allowlist', 'bundle', 'security', 'window'],
required: ['allowlist', 'bundle', 'security', 'windows'],
type: 'object'
},
verbose: {

View File

@@ -7,19 +7,21 @@ struct Context;
fn main() {
tauri::AppBuilder::<tauri::flavors::Wry, Context>::new()
.setup(|dispatcher, _| async move {
let mut dispatcher_ = dispatcher.clone();
.setup(|webview_manager| async move {
let mut webview_manager_ = webview_manager.clone();
tauri::event::listen(String::from("hello"), move |_| {
tauri::event::emit(
&mut dispatcher_,
&webview_manager_,
String::from("reply"),
Some("{ msg: 'TEST' }".to_string()),
)
.unwrap();
});
dispatcher.eval("window.onTauriInit && window.onTauriInit()");
webview_manager
.current_webview()
.eval("window.onTauriInit && window.onTauriInit()");
})
.invoke_handler(|dispatcher, arg| async move {
.invoke_handler(|webview_manager, arg| async move {
use cmd::Cmd::*;
match serde_json::from_str(&arg) {
Err(e) => Err(e.to_string()),

View File

@@ -17,8 +17,10 @@
],
"identifier": "fixture.app"
},
"window": {
"title": "Fixture"
}
"windows": [
{
"title": "Fixture"
}
]
}
}

View File

@@ -6,16 +6,75 @@ use serde_json::Value as JsonValue;
use std::collections::HashMap;
/// The window webview URL options.
#[derive(PartialEq, Debug, Clone)]
pub enum WindowUrl {
/// The app's index URL.
App,
/// A custom URL.
Custom(String),
}
impl Default for WindowUrl {
fn default() -> Self {
Self::App
}
}
impl<'de> Deserialize<'de> for WindowUrl {
fn deserialize<D>(deserializer: D) -> Result<WindowUrl, D::Error>
where
D: Deserializer<'de>,
{
struct StringVisitor;
impl<'de> Visitor<'de> for StringVisitor {
type Value = WindowUrl;
fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
formatter.write_str("a string representing an url")
}
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
if v.to_lowercase() == "app" {
Ok(WindowUrl::App)
} else {
Ok(WindowUrl::Custom(v.to_string()))
}
}
}
deserializer.deserialize_str(StringVisitor)
}
}
/// The window configuration object.
#[derive(PartialEq, Deserialize, Debug)]
#[serde(tag = "window", rename_all = "camelCase")]
#[derive(PartialEq, Deserialize, Debug, Clone)]
pub struct WindowConfig {
#[serde(default = "default_window_label")]
/// The window identifier.
pub label: String,
/// The window webview URL.
#[serde(default)]
pub url: WindowUrl,
/// The horizontal position of the window's top left corner
pub x: Option<f64>,
/// The vertical position of the window's top left corner
pub y: Option<f64>,
/// The window width.
#[serde(default = "default_width")]
pub width: i32,
pub width: f64,
/// The window height.
#[serde(default = "default_height")]
pub height: i32,
pub height: f64,
/// The min window width.
pub min_width: Option<f64>,
/// The min window height.
pub min_height: Option<f64>,
/// The max window width.
pub max_width: Option<f64>,
/// The max window height.
pub max_height: Option<f64>,
/// Whether the window is resizable or not.
#[serde(default = "default_resizable")]
pub resizable: bool,
@@ -25,20 +84,47 @@ pub struct WindowConfig {
/// Whether the window starts as fullscreen or not.
#[serde(default)]
pub fullscreen: bool,
/// Whether the window is transparent or not.
#[serde(default)]
pub transparent: bool,
/// Whether the window is maximized or not.
#[serde(default)]
pub maximized: bool,
/// Whether the window is visible or not.
#[serde(default = "default_visible")]
pub visible: bool,
/// Whether the window should have borders and bars.
#[serde(default = "default_decorations")]
pub decorations: bool,
/// Whether the window should always be on top of other windows.
#[serde(default)]
pub always_on_top: bool,
}
fn default_width() -> i32 {
800
fn default_window_label() -> String {
"main".to_string()
}
fn default_height() -> i32 {
600
fn default_width() -> f64 {
800f64
}
fn default_height() -> f64 {
600f64
}
fn default_resizable() -> bool {
true
}
fn default_visible() -> bool {
true
}
fn default_decorations() -> bool {
true
}
fn default_title() -> String {
"Tauri App".to_string()
}
@@ -46,11 +132,24 @@ fn default_title() -> String {
impl Default for WindowConfig {
fn default() -> Self {
Self {
label: default_window_label(),
url: WindowUrl::App,
x: None,
y: None,
width: default_width(),
height: default_height(),
min_width: None,
min_height: None,
max_width: None,
max_height: None,
resizable: default_resizable(),
title: default_title(),
fullscreen: false,
transparent: false,
maximized: false,
visible: default_visible(),
decorations: default_decorations(),
always_on_top: false,
}
}
}
@@ -335,13 +434,17 @@ impl Default for BundleConfig {
}
}
fn default_window_config() -> Vec<WindowConfig> {
vec![Default::default()]
}
/// The Tauri configuration object.
#[derive(PartialEq, Deserialize, Debug)]
#[serde(tag = "tauri", rename_all = "camelCase")]
pub struct TauriConfig {
/// The window configuration.
#[serde(default)]
pub window: WindowConfig,
#[serde(default = "default_window_config")]
pub windows: Vec<WindowConfig>,
/// The embeddedServer configuration.
#[serde(default)]
pub embedded_server: EmbeddedServerConfig,
@@ -356,7 +459,7 @@ pub struct TauriConfig {
impl Default for TauriConfig {
fn default() -> Self {
Self {
window: WindowConfig::default(),
windows: default_window_config(),
embedded_server: EmbeddedServerConfig::default(),
cli: None,
bundle: BundleConfig::default(),
@@ -441,7 +544,7 @@ mod test {
// get default embedded server
let de_server = EmbeddedServerConfig::default();
// get default window
let d_window = WindowConfig::default();
let d_windows = default_window_config();
// get default title
let d_title = default_title();
// get default bundle
@@ -449,13 +552,26 @@ mod test {
// create a tauri config.
let tauri = TauriConfig {
window: WindowConfig {
width: 800,
height: 600,
windows: vec![WindowConfig {
label: "main".to_string(),
url: WindowUrl::App,
x: None,
y: None,
width: 800f64,
height: 600f64,
min_width: None,
min_height: None,
max_width: None,
max_height: None,
resizable: true,
title: String::from("Tauri App"),
fullscreen: false,
},
transparent: false,
maximized: false,
visible: true,
decorations: true,
always_on_top: false,
}],
embedded_server: EmbeddedServerConfig {
host: String::from("http://127.0.0.1"),
port: Port::Random,
@@ -479,7 +595,7 @@ mod test {
assert_eq!(de_server, tauri.embedded_server);
assert_eq!(d_bundle, tauri.bundle);
assert_eq!(d_path, String::from("http://localhost:8080"));
assert_eq!(d_title, tauri.window.title);
assert_eq!(d_window, tauri.window);
assert_eq!(d_title, tauri.windows[0].title);
assert_eq!(d_windows, tauri.windows);
}
}

View File

@@ -32,7 +32,7 @@ thiserror = "1.0.23"
once_cell = "1.5.2"
tauri-api = { version = "0.7.5", path = "../tauri-api" }
tauri-macros = { version = "0.1", path = "../tauri-macros" }
wry = { git = "https://github.com/tauri-apps/wry", rev = "42f4f2133f7921ed5adc47908787094da8abeac5" }
wry = { git = "https://github.com/tauri-apps/wry", rev = "f4edf89de5dc40b77a94f6b94fcaf76fdac6bbf4" }
[target."cfg(target_os = \"windows\")".dependencies]
runas = "0.2"
@@ -74,3 +74,7 @@ notification = [ "tauri-api/notification" ]
[[example]]
name = "communication"
path = "examples/communication/src-tauri/src/main.rs"
[[example]]
name = "multiwindow"
path = "examples/multiwindow/src-tauri/src/main.rs"

View File

@@ -9,7 +9,6 @@
<link rel='icon' type='image/png' href='favicon.png'>
<link rel='stylesheet' href='global.css'>
<!-- <link rel='stylesheet' href='build/bundle.css'> -->
<script defer src='build/bundle.js'></script>
</head>

View File

@@ -3094,11 +3094,12 @@ dependencies = [
[[package]]
name = "wry"
version = "0.4.1"
source = "git+https://github.com/tauri-apps/wry?rev=42f4f2133f7921ed5adc47908787094da8abeac5#42f4f2133f7921ed5adc47908787094da8abeac5"
source = "git+https://github.com/tauri-apps/wry?rev=f4edf89de5dc40b77a94f6b94fcaf76fdac6bbf4#f4edf89de5dc40b77a94f6b94fcaf76fdac6bbf4"
dependencies = [
"cc",
"cocoa",
"core-graphics 0.22.2",
"gdk",
"gio",
"glib",
"gtk",

View File

@@ -17,19 +17,21 @@ struct Context;
fn main() {
tauri::AppBuilder::<tauri::flavors::Wry, Context>::new()
.setup(|dispatcher, _source| async move {
let mut dispatcher = dispatcher.clone();
tauri::event::listen(String::from("js-event"), move |msg| {
.setup(|webview_manager| async move {
let dispatcher = webview_manager.current_webview().unwrap();
let dispatcher_ = dispatcher.clone();
dispatcher.listen("js-event", move |msg| {
println!("got js-event with message '{:?}'", msg);
let reply = Reply {
data: "something else".to_string(),
};
tauri::event::emit(&mut dispatcher, String::from("rust-event"), Some(reply))
dispatcher_
.emit("rust-event", Some(reply))
.expect("failed to emit");
});
})
.invoke_handler(|mut dispatcher, arg| async move {
.invoke_handler(|webview_manager, arg| async move {
use cmd::Cmd::*;
match serde_json::from_str(&arg) {
Err(e) => Err(e.to_string()),
@@ -47,7 +49,7 @@ fn main() {
// tauri::execute_promise is a helper for APIs that uses the tauri.promisified JS function
// so you can easily communicate between JS and Rust with promises
tauri::execute_promise(
&mut dispatcher,
&webview_manager,
async move {
println!("{} {:?}", endpoint, body);
// perform an async operation here

View File

@@ -1,7 +1,7 @@
{
"build": {
"distDir": "../public",
"devPath": "../public",
"devPath": "http://localhost:5000",
"withGlobalTauri": true,
"beforeBuildCommand": "yarn build",
"beforeDevCommand": "yarn dev"
@@ -57,9 +57,9 @@
"allowlist": {
"all": true
},
"window": {
"windows": [{
"title": "Tauri API Validation"
},
}],
"security": {
"csp": "default-src blob: data: filesystem: ws: http: https: 'unsafe-eval' 'unsafe-inline'"
}

View File

@@ -2,18 +2,19 @@
import { setTitle, open } from "@tauri-apps/api/window";
let urlValue = "https://tauri.studio";
let windowTitle = 'Awesome Tauri Example!';
function openUrl() {
open(urlValue);
}
function setWindowTitle() {
setTitle(urlValue);
setTitle(windowTitle);
}
</script>
<form style="margin-top: 24px" on:submit|preventDefault={setWindowTitle}>
<input id="title" value="Awesome Tauri Example!" />
<input id="title" bind:value={windowTitle} />
<button class="button" type="submit">Set title</button>
</form>
<form style="margin-top: 24px" on:submit|preventDefault={openUrl}>

View File

@@ -1,4 +1,5 @@
document.getElementById("log").addEventListener("click", function () {
console.log('log')
window.__TAURI__.tauri.invoke({
cmd: "logOperation",
event: "tauri-click",

View File

@@ -18,19 +18,21 @@ struct Context;
fn main() {
tauri::AppBuilder::<tauri::flavors::Wry, Context>::new()
.setup(|dispatcher, _source| async move {
let mut dispatcher = dispatcher.clone();
tauri::event::listen(String::from("js-event"), move |msg| {
.setup(|webview_manager| async move {
let current_webview = webview_manager.current_webview().unwrap().clone();
let current_webview_ = current_webview.clone();
current_webview.listen(String::from("js-event"), move |msg| {
println!("got js-event with message '{:?}'", msg);
let reply = Reply {
data: "something else".to_string(),
};
tauri::event::emit(&mut dispatcher, String::from("rust-event"), Some(reply))
current_webview_
.emit(String::from("rust-event"), Some(reply))
.expect("failed to emit");
});
})
.invoke_handler(|mut dispatcher, arg| async move {
.invoke_handler(|webview_manager, arg| async move {
use cmd::Cmd::*;
match serde_json::from_str(&arg) {
Err(e) => Err(e.to_string()),
@@ -48,7 +50,7 @@ fn main() {
// tauri::execute_promise is a helper for APIs that uses the tauri.promisified JS function
// so you can easily communicate between JS and Rust with promises
tauri::execute_promise(
&mut dispatcher,
&webview_manager,
async move {
println!("{} {:?}", endpoint, body);
// perform an async operation here

View File

@@ -1,7 +1,7 @@
{
"build": {
"distDir": "../dist",
"devPath": "../dist",
"devPath": "http://localhost:4000",
"withGlobalTauri": true
},
"ctx": {},
@@ -56,9 +56,10 @@
"allowlist": {
"all": true
},
"window": {
"title": "Tauri API Validation"
},
"windows": [{
"title": "Tauri API Validation",
"resizable": false
}],
"security": {
"csp": "default-src blob: data: filesystem: ws: http: https: 'unsafe-eval' 'unsafe-inline'"
}

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,36 @@
<!DOCTYPE html>
<html>
<body>
<div id="window-label"></div>
<div id="container"></div>
<div id="response"></div>
<script>
var windowLabel = window.__TAURI__.currentWindow.label
var windowLabelContainer = document.getElementById('window-label')
windowLabelContainer.innerHTML = 'This is the ' + windowLabel + ' window.'
var responseContainer = document.getElementById('response')
window.__TAURI__.event.listen('window://' + windowLabel, function (event) {
responseContainer.innerHTML = 'Got ' + JSON.stringify(event)
})
var container = document.getElementById('container')
for (var index in window.__TAURI__.windows) {
const label = window.__TAURI__.windows[index].label
if (label === windowLabel) {
continue;
}
const button = document.createElement('button')
button.innerHTML = 'Send message to ' + label
button.addEventListener('click', function () {
window.__TAURI__.event.emit('window://' + label, 'message from ' + windowLabel)
})
container.appendChild(button)
}
</script>
</body>
</html>

View File

@@ -0,0 +1,11 @@
{
"name": "communication-example",
"version": "1.0.0",
"description": "A Tauri example showcasing the JS-Rust communication",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build": "node ../../../cli/tauri.js/bin/tauri build"
},
"private": true
}

View File

@@ -0,0 +1,10 @@
# Generated by Cargo
# will have compiled files and executables
/target/
WixTools
# These are backup files generated by rustfmt
**/*.rs.bk
config.json
bundle.json

View File

@@ -0,0 +1,42 @@
workspace = { }
[package]
name = "app"
version = "0.1.0"
description = "A Tauri Multiwindow App"
authors = [ "you" ]
license = ""
repository = ""
default-run = "app"
edition = "2018"
build = "src/build.rs"
[package.metadata.bundle]
identifier = "com.tauri.dev"
icon = [
"icons/32x32.png",
"icons/128x128.png",
"icons/128x128@2x.png",
"icons/icon.icns",
"icons/icon.ico"
]
[dependencies]
tauri = { path = "../../..", features =["all-api", "cli"]}
[target."cfg(windows)".build-dependencies]
winres = "0.1"
[features]
embedded-server = [ "tauri/embedded-server" ]
[[bin]]
name = "app"
path = "src/main.rs"
[profile.release]
panic = "abort"
codegen-units = 1
lto = true
incremental = false
opt-level = "z"

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 88 KiB

View File

@@ -0,0 +1,14 @@
max_width = 100
hard_tabs = false
tab_spaces = 2
newline_style = "Auto"
use_small_heuristics = "Default"
reorder_imports = true
reorder_modules = true
remove_nested_parens = true
edition = "2018"
merge_derives = true
use_try_shorthand = false
use_field_init_shorthand = false
force_explicit_abi = true
imports_granularity = "Crate"

View File

@@ -0,0 +1,16 @@
#[cfg(windows)]
extern crate winres;
#[cfg(windows)]
fn main() {
if std::path::Path::new("icons/icon.ico").exists() {
let mut res = winres::WindowsResource::new();
res.set_icon_with_id("icons/icon.ico", "32512");
res.compile().expect("Unable to find visual studio tools");
} else {
panic!("No Icon.ico found. Please add one or check the path");
}
}
#[cfg(not(windows))]
fn main() {}

View File

@@ -0,0 +1,25 @@
#![cfg_attr(
all(not(debug_assertions), target_os = "windows"),
windows_subsystem = "windows"
)]
#[derive(tauri::FromTauriContext)]
#[config_path = "examples/multiwindow/src-tauri/tauri.conf.json"]
struct Context;
fn main() {
tauri::AppBuilder::<tauri::flavors::Wry, Context>::new()
.setup(|webview_manager| async move {
let current_webview = webview_manager.current_webview().unwrap().clone();
let current_webview_ = current_webview.clone();
let event_name = format!("window://{}", webview_manager.current_window_label());
current_webview.listen(event_name.clone(), move |msg| {
current_webview_
.emit(&event_name, msg)
.expect("failed to emit");
});
})
.build()
.unwrap()
.run();
}

View File

@@ -0,0 +1,44 @@
{
"build": {
"distDir": "../dist",
"devPath": "../dist",
"withGlobalTauri": true
},
"tauri": {
"bundle": {
"active": true,
"targets": "all",
"identifier": "com.tauri.dev",
"icon": [
"icons/32x32.png",
"icons/128x128.png",
"icons/128x128@2x.png",
"icons/icon.icns",
"icons/icon.ico"
],
"resources": [],
"externalBin": [],
"copyright": "",
"category": "DeveloperTool"
},
"allowlist": {
"all": true
},
"windows": [{
"label": "Main",
"url": "app",
"title": "Tauri - Main",
"width": 800,
"height": 600
}, {
"label": "Secondary",
"url": "app",
"title": "Tauri - Secondary",
"width": 600,
"height": 400
}],
"security": {
"csp": "default-src blob: data: filesystem: ws: http: https: 'unsafe-eval' 'unsafe-inline'"
}
}
}

View File

@@ -0,0 +1,4 @@
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1

View File

@@ -3,10 +3,15 @@ use futures::future::BoxFuture;
use std::marker::PhantomData;
use tauri_api::{config::Config, private::AsTauriContext};
pub(crate) mod event;
mod runner;
mod webview_manager;
type InvokeHandler<W> = dyn Fn(W, String) -> BoxFuture<'static, Result<(), String>> + Send + Sync;
type Setup<W> = dyn Fn(W, String) -> BoxFuture<'static, ()> + Send + Sync;
pub use webview_manager::{WebviewDispatcher, WebviewManager};
type InvokeHandler<D> =
dyn Fn(WebviewManager<D>, String) -> BoxFuture<'static, Result<(), String>> + Send + Sync;
type Setup<D> = dyn Fn(WebviewManager<D>) -> BoxFuture<'static, ()> + Send + Sync;
/// `App` runtime information.
pub struct Context {
@@ -31,8 +36,6 @@ pub struct App<A: ApplicationExt> {
invoke_handler: Option<Box<InvokeHandler<A::Dispatcher>>>,
/// The setup callback, invoked when the webview is ready.
setup: Option<Box<Setup<A::Dispatcher>>>,
/// The HTML of the splashscreen to render.
splashscreen_html: Option<String>,
/// The context the App was created with
pub(crate) context: Context,
}
@@ -48,7 +51,7 @@ impl<A: ApplicationExt + 'static> App<A> {
/// The message is considered consumed if the handler exists and returns an Ok Result.
pub(crate) async fn run_invoke_handler(
&self,
dispatcher: &mut A::Dispatcher,
dispatcher: &WebviewManager<A::Dispatcher>,
arg: &str,
) -> Result<bool, String> {
if let Some(ref invoke_handler) = self.invoke_handler {
@@ -60,17 +63,12 @@ impl<A: ApplicationExt + 'static> App<A> {
}
/// Runs the setup callback if defined.
pub(crate) async fn run_setup(&self, dispatcher: &mut A::Dispatcher, source: String) {
pub(crate) async fn run_setup(&self, dispatcher: &WebviewManager<A::Dispatcher>) {
if let Some(ref setup) = self.setup {
let fut = setup(dispatcher.clone(), source);
let fut = setup(dispatcher.clone());
fut.await;
}
}
/// Returns the splashscreen HTML.
pub fn splashscreen_html(&self) -> Option<&String> {
self.splashscreen_html.as_ref()
}
}
/// The App builder.
@@ -80,8 +78,6 @@ pub struct AppBuilder<A: ApplicationExt, C: AsTauriContext> {
invoke_handler: Option<Box<InvokeHandler<A::Dispatcher>>>,
/// The setup callback, invoked when the webview is ready.
setup: Option<Box<Setup<A::Dispatcher>>>,
/// The HTML of the splashscreen to render.
splashscreen_html: Option<String>,
/// The configuration used
config: PhantomData<C>,
}
@@ -92,7 +88,6 @@ impl<A: ApplicationExt + 'static, C: AsTauriContext> AppBuilder<A, C> {
Self {
invoke_handler: None,
setup: None,
splashscreen_html: None,
config: Default::default(),
}
}
@@ -100,13 +95,13 @@ impl<A: ApplicationExt + 'static, C: AsTauriContext> AppBuilder<A, C> {
/// Defines the JS message handler callback.
pub fn invoke_handler<
T: futures::Future<Output = Result<(), String>> + Send + Sync + 'static,
F: Fn(A::Dispatcher, String) -> T + Send + Sync + 'static,
F: Fn(WebviewManager<A::Dispatcher>, String) -> T + Send + Sync + 'static,
>(
mut self,
invoke_handler: F,
) -> Self {
self.invoke_handler = Some(Box::new(move |dispatcher, arg| {
Box::pin(invoke_handler(dispatcher, arg))
self.invoke_handler = Some(Box::new(move |webview_manager, arg| {
Box::pin(invoke_handler(webview_manager, arg))
}));
self
}
@@ -114,23 +109,17 @@ impl<A: ApplicationExt + 'static, C: AsTauriContext> AppBuilder<A, C> {
/// Defines the setup callback.
pub fn setup<
T: futures::Future<Output = ()> + Send + Sync + 'static,
F: Fn(A::Dispatcher, String) -> T + Send + Sync + 'static,
F: Fn(WebviewManager<A::Dispatcher>) -> T + Send + Sync + 'static,
>(
mut self,
setup: F,
) -> Self {
self.setup = Some(Box::new(move |dispatcher, source| {
Box::pin(setup(dispatcher, source))
self.setup = Some(Box::new(move |webview_manager| {
Box::pin(setup(webview_manager))
}));
self
}
/// Defines the splashscreen HTML to render.
pub fn splashscreen_html(mut self, html: &str) -> Self {
self.splashscreen_html = Some(html.to_string());
self
}
/// Adds a plugin to the runtime.
pub fn plugin(
self,
@@ -145,7 +134,6 @@ impl<A: ApplicationExt + 'static, C: AsTauriContext> AppBuilder<A, C> {
Ok(App {
invoke_handler: self.invoke_handler,
setup: self.setup,
splashscreen_html: self.splashscreen_html,
context: Context::new::<C>()?,
})
}

View File

@@ -16,7 +16,7 @@ struct EventHandler {
on_event: Box<dyn FnMut(Option<String>) + Send>,
}
type Listeners = Arc<Mutex<HashMap<String, EventHandler>>>;
type Listeners = Arc<Mutex<HashMap<String, Vec<EventHandler>>>>;
lazy_static! {
static ref EMIT_FUNCTION_NAME: String = uuid::Uuid::new_v4().to_string();
@@ -46,22 +46,24 @@ pub fn event_queue_object_name() -> String {
}
/// Adds an event listener for JS events.
pub fn listen<F: FnMut(Option<String>) + Send + 'static>(id: impl Into<String>, handler: F) {
pub fn listen<F: FnMut(Option<String>) + Send + 'static>(id: impl AsRef<str>, handler: F) {
let mut l = listeners()
.lock()
.expect("Failed to lock listeners: listen()");
l.insert(
id.into(),
EventHandler {
on_event: Box::new(handler),
},
);
let handler = EventHandler {
on_event: Box::new(handler),
};
if let Some(listeners) = l.get_mut(id.as_ref()) {
listeners.push(handler);
} else {
l.insert(id.as_ref().to_string(), vec![handler]);
}
}
/// Emits an event to JS.
pub fn emit<D: ApplicationDispatcherExt, S: Serialize>(
dispatcher: &mut D,
event: impl AsRef<str> + Send + 'static,
webview_dispatcher: &crate::WebviewDispatcher<D>,
event: impl AsRef<str>,
payload: Option<S>,
) -> crate::Result<()> {
let salt = crate::salt::generate();
@@ -72,7 +74,7 @@ pub fn emit<D: ApplicationDispatcherExt, S: Serialize>(
JsonValue::Null
};
dispatcher.eval(&format!(
webview_dispatcher.eval(&format!(
"window['{}']({{type: '{}', payload: {}}}, '{}')",
emit_function_name(),
event.as_ref(),
@@ -90,14 +92,16 @@ pub fn on_event(event: String, data: Option<String>) {
.expect("Failed to lock listeners: on_event()");
if l.contains_key(&event) {
let handler = l.get_mut(&event).expect("Failed to get mutable handler");
(handler.on_event)(data);
let listeners = l.get_mut(&event).expect("Failed to get mutable handler");
for handler in listeners {
(handler.on_event)(data.clone());
}
}
}
#[cfg(test)]
mod test {
use crate::event::*;
use super::*;
use proptest::prelude::*;
// dummy event handler function

View File

@@ -1,16 +1,13 @@
#[cfg(dev)]
use std::io::Read;
use std::sync::{
atomic::{AtomicBool, Ordering},
Arc,
};
use std::{collections::HashMap, sync::Arc};
#[cfg(dev)]
use crate::api::assets::{AssetFetch, Assets};
use crate::{ApplicationDispatcherExt, ApplicationExt, WebviewBuilderExt, WindowBuilderExt};
use crate::{api::config::WindowUrl, ApplicationExt, WebviewBuilderExt};
use super::App;
use super::{App, WebviewDispatcher, WebviewManager};
#[cfg(embedded_server)]
use crate::api::tcp::{get_available_port, port_is_available};
use crate::app::Context;
@@ -45,24 +42,8 @@ pub(crate) fn run<A: ApplicationExt + 'static>(application: App<A>) -> crate::Re
spawn_server(server_url, &application.context);
}
let splashscreen_content = if application.splashscreen_html().is_some() {
Some(Content::Html(
application
.splashscreen_html()
.expect("failed to get splashscreen_html")
.to_string(),
))
} else {
None
};
// build the webview
let (webview_application, mut dispatcher) =
build_webview(application, main_content, splashscreen_content)?;
crate::async_runtime::spawn(async move {
crate::plugin::created(A::plugin_store(), &mut dispatcher).await
});
let webview_application = build_webview(application, main_content)?;
// spin up the updater process
#[cfg(feature = "updater")]
@@ -250,28 +231,12 @@ pub fn event_initialization_script() -> String {
fn build_webview<A: ApplicationExt + 'static>(
application: App<A>,
content: Content<String>,
splashscreen_content: Option<Content<String>>,
) -> crate::Result<(A, A::Dispatcher)> {
let config = &application.context.config;
) -> crate::Result<A> {
// TODO let debug = cfg!(debug_assertions);
// get properties from config struct
// TODO let width = config.tauri.window.width;
// TODO let height = config.tauri.window.height;
let resizable = config.tauri.window.resizable;
// let fullscreen = config.tauri.window.fullscreen;
let title = config.tauri.window.title.clone();
let has_splashscreen = splashscreen_content.is_some();
let initialized_splashscreen = Arc::new(AtomicBool::new(false));
let content_url = match content {
Content::Html(s) => s,
Content::Url(s) => s,
};
let url = match splashscreen_content {
Some(Content::Html(s)) => s,
_ => content_url.to_string(),
};
let initialization_script = format!(
r#"
@@ -296,90 +261,102 @@ fn build_webview<A: ApplicationExt + 'static>(
let mut webview_application = A::new()?;
let main_window =
webview_application.create_window(A::WindowBuilder::new().resizable(resizable).title(title))?;
let mut window_refs = Vec::new();
let mut dispatchers = HashMap::new();
let dispatcher = webview_application.dispatcher(&main_window);
for window_config in application.context.config.tauri.windows.clone() {
let window = crate::webview::WindowBuilder::from(&window_config);
let window = webview_application.create_window(window.get())?;
let dispatcher = webview_application.dispatcher(&window);
dispatchers.insert(
window_config.label.to_string(),
WebviewDispatcher::new(dispatcher),
);
window_refs.push((window_config, window));
}
let tauri_invoke_handler = crate::Callback::<A::Dispatcher> {
name: "__TAURI_INVOKE_HANDLER__".to_string(),
function: Box::new(move |dispatcher, _, arg| {
let arg = arg.into_iter().next().unwrap_or_else(String::new);
let application = application.clone();
let mut dispatcher = dispatcher.clone();
let content_url = content_url.to_string();
let initialized_splashscreen = initialized_splashscreen.clone();
for (window_config, window) in window_refs {
let webview_manager = WebviewManager::new(dispatchers.clone(), window_config.label.to_string());
crate::async_runtime::spawn(async move {
if arg == r#"{"cmd":"__initialized"}"# {
let source = if has_splashscreen && !initialized_splashscreen.load(Ordering::Relaxed) {
initialized_splashscreen.swap(true, Ordering::Relaxed);
"splashscreen"
let application = application.clone();
let content_url = content_url.to_string();
let webview_manager_ = webview_manager.clone();
let tauri_invoke_handler = crate::Callback::<A::Dispatcher> {
name: "__TAURI_INVOKE_HANDLER__".to_string(),
function: Box::new(move |_, _, arg| {
let arg = arg.into_iter().next().unwrap_or_else(String::new);
let application = application.clone();
let webview_manager = webview_manager_.clone();
crate::async_runtime::spawn(async move {
if arg == r#"{"cmd":"__initialized"}"# {
application.run_setup(&webview_manager).await;
crate::plugin::ready(A::plugin_store(), &webview_manager).await;
} else {
"window-1"
};
application
.run_setup(&mut dispatcher, source.to_string())
.await;
if source == "window-1" {
crate::plugin::ready(A::plugin_store(), &mut dispatcher).await;
}
} else if arg == r#"{"cmd":"closeSplashscreen"}"# {
dispatcher.eval(&format!(r#"window.location.href = "{}""#, content_url));
} else {
let mut endpoint_handle =
crate::endpoints::handle(&mut dispatcher, &arg, &application.context)
.await
.map_err(|e| e.to_string());
if let Err(ref tauri_handle_error) = endpoint_handle {
if tauri_handle_error.contains("unknown variant") {
let error = match application.run_invoke_handler(&mut dispatcher, &arg).await {
let mut endpoint_handle =
crate::endpoints::handle(&webview_manager, &arg, &application.context).await;
if let Err(crate::Error::UnknownApi) = endpoint_handle {
let response = match application.run_invoke_handler(&webview_manager, &arg).await {
Ok(handled) => {
if handled {
String::from("")
Ok(())
} else {
tauri_handle_error.to_string()
endpoint_handle
}
}
Err(e) => e,
Err(_) => Err(crate::Error::UnknownApi),
};
endpoint_handle = Err(error);
endpoint_handle = response;
}
if let Err(crate::Error::UnknownApi) = endpoint_handle {
endpoint_handle =
crate::plugin::extend_api(A::plugin_store(), &webview_manager, &arg)
.await
.map(|_| ());
}
if let Err(handler_error_message) = endpoint_handle {
let handler_error_message = handler_error_message.to_string().replace("'", "\\'");
if !handler_error_message.is_empty() {
if let Ok(dispatcher) = webview_manager.current_webview() {
dispatcher.eval(&get_api_error_message(&arg, handler_error_message));
}
}
}
}
if let Err(ref app_handle_error) = endpoint_handle {
if app_handle_error.contains("unknown variant") {
let error =
match crate::plugin::extend_api(A::plugin_store(), &mut dispatcher, &arg).await {
Ok(_) => String::from(""),
Err(e) => e.to_string(),
};
endpoint_handle = Err(error);
}
}
endpoint_handle = endpoint_handle.map_err(|e| e.replace("'", "\\'"));
if let Err(handler_error_message) = endpoint_handle {
if !handler_error_message.is_empty() {
dispatcher.eval(&get_api_error_message(&arg, handler_error_message));
}
}
}
});
0
}),
};
});
0
}),
};
webview_application.create_webview(
A::WebviewBuilder::new()
.url(url)
.initialization_script(&initialization_script),
main_window,
vec![tauri_invoke_handler],
)?;
let webview_url = match &window_config.url {
WindowUrl::App => content_url.to_string(),
WindowUrl::Custom(url) => url.to_string(),
};
// TODO waiting for webview window API
// webview.set_fullscreen(fullscreen);
webview_application.create_webview(
A::WebviewBuilder::new()
.url(webview_url)
.initialization_script(&initialization_script)
.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(&dispatchers.keys().collect::<Vec<&String>>()).unwrap(),
current_window_label = window_config.label,
)),
window,
vec![tauri_invoke_handler],
)?;
Ok((webview_application, dispatcher))
crate::async_runtime::spawn(async move {
crate::plugin::created(A::plugin_store(), &webview_manager).await
});
}
Ok(webview_application)
}
// Formats an invoke handler error message to print to console.error

View File

@@ -0,0 +1,90 @@
use std::collections::HashMap;
use crate::{
webview::{Event, Message},
ApplicationDispatcherExt,
};
use serde::Serialize;
/// The webview dispatcher.
#[derive(Clone)]
pub struct WebviewDispatcher<A: Clone>(A);
impl<A: ApplicationDispatcherExt> WebviewDispatcher<A> {
pub(crate) fn new(dispatcher: A) -> Self {
Self(dispatcher)
}
pub(crate) fn send_event(&self, event: Event) {
self.0.send_message(Message::Event(event))
}
/// Listen to an event.
pub fn listen<F: FnMut(Option<String>) + Send + 'static>(
&self,
event: impl AsRef<str>,
handler: F,
) {
super::event::listen(event, handler)
}
/// Emits an event.
pub fn emit<S: Serialize>(
&self,
event: impl AsRef<str>,
payload: Option<S>,
) -> crate::Result<()> {
super::event::emit(&self, event, payload)
}
pub(crate) fn on_event(&self, event: String, data: Option<String>) {
super::event::on_event(event, data)
}
/// Evaluates a JS script.
pub fn eval(&self, js: &str) {
self.0.send_message(Message::EvalScript(js.to_string()))
}
/// Updates the window title.
pub fn set_title(&self, title: &str) {
self
.0
.send_message(Message::SetWindowTitle(title.to_string()))
}
}
/// The webview manager.
#[derive(Clone)]
pub struct WebviewManager<A: Clone> {
dispatchers: HashMap<String, WebviewDispatcher<A>>,
current_webview_window_label: String,
}
impl<A: ApplicationDispatcherExt> WebviewManager<A> {
pub(crate) fn new(dispatchers: HashMap<String, WebviewDispatcher<A>>, label: String) -> Self {
Self {
dispatchers,
current_webview_window_label: label,
}
}
/// 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>> {
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>> {
self
.dispatchers
.get(window_label)
.ok_or(crate::Error::WebviewNotFound)
}
}

View File

@@ -14,11 +14,11 @@ mod http;
#[cfg(notification)]
mod notification;
use crate::{app::Context, ApplicationDispatcherExt, Event};
use crate::{app::Context, webview::Event, ApplicationDispatcherExt};
#[allow(unused_variables)]
pub(crate) async fn handle<D: ApplicationDispatcherExt + 'static>(
dispatcher: &mut D,
webview_manager: &crate::WebviewManager<D>,
arg: &str,
context: &Context,
) -> crate::Result<()> {
@@ -34,9 +34,9 @@ pub(crate) async fn handle<D: ApplicationDispatcherExt + 'static>(
error,
} => {
#[cfg(read_text_file)]
file_system::read_text_file(dispatcher, path, options, callback, error).await;
file_system::read_text_file(webview_manager, path, options, callback, error).await;
#[cfg(not(read_text_file))]
allowlist_error(dispatcher, error, "readTextFile");
allowlist_error(webview_manager, error, "readTextFile");
}
ReadBinaryFile {
path,
@@ -45,9 +45,9 @@ pub(crate) async fn handle<D: ApplicationDispatcherExt + 'static>(
error,
} => {
#[cfg(read_binary_file)]
file_system::read_binary_file(dispatcher, path, options, callback, error).await;
file_system::read_binary_file(webview_manager, path, options, callback, error).await;
#[cfg(not(read_binary_file))]
allowlist_error(dispatcher, error, "readBinaryFile");
allowlist_error(webview_manager, error, "readBinaryFile");
}
WriteFile {
path,
@@ -57,9 +57,9 @@ pub(crate) async fn handle<D: ApplicationDispatcherExt + 'static>(
error,
} => {
#[cfg(write_file)]
file_system::write_file(dispatcher, path, contents, options, callback, error).await;
file_system::write_file(webview_manager, path, contents, options, callback, error).await;
#[cfg(not(write_file))]
allowlist_error(dispatcher, error, "writeFile");
allowlist_error(webview_manager, error, "writeFile");
}
WriteBinaryFile {
path,
@@ -69,10 +69,10 @@ pub(crate) async fn handle<D: ApplicationDispatcherExt + 'static>(
error,
} => {
#[cfg(write_binary_file)]
file_system::write_binary_file(dispatcher, path, contents, options, callback, error)
file_system::write_binary_file(webview_manager, path, contents, options, callback, error)
.await;
#[cfg(not(write_binary_file))]
allowlist_error(dispatcher, error, "writeBinaryFile");
allowlist_error(webview_manager, error, "writeBinaryFile");
}
ReadDir {
path,
@@ -81,9 +81,9 @@ pub(crate) async fn handle<D: ApplicationDispatcherExt + 'static>(
error,
} => {
#[cfg(read_dir)]
file_system::read_dir(dispatcher, path, options, callback, error).await;
file_system::read_dir(webview_manager, path, options, callback, error).await;
#[cfg(not(read_dir))]
allowlist_error(dispatcher, error, "readDir");
allowlist_error(webview_manager, error, "readDir");
}
CopyFile {
source,
@@ -93,9 +93,17 @@ pub(crate) async fn handle<D: ApplicationDispatcherExt + 'static>(
error,
} => {
#[cfg(copy_file)]
file_system::copy_file(dispatcher, source, destination, options, callback, error).await;
file_system::copy_file(
webview_manager,
source,
destination,
options,
callback,
error,
)
.await;
#[cfg(not(copy_file))]
allowlist_error(dispatcher, error, "copyFile");
allowlist_error(webview_manager, error, "copyFile");
}
CreateDir {
path,
@@ -104,9 +112,9 @@ pub(crate) async fn handle<D: ApplicationDispatcherExt + 'static>(
error,
} => {
#[cfg(create_dir)]
file_system::create_dir(dispatcher, path, options, callback, error).await;
file_system::create_dir(webview_manager, path, options, callback, error).await;
#[cfg(not(create_dir))]
allowlist_error(dispatcher, error, "createDir");
allowlist_error(webview_manager, error, "createDir");
}
RemoveDir {
path,
@@ -115,9 +123,9 @@ pub(crate) async fn handle<D: ApplicationDispatcherExt + 'static>(
error,
} => {
#[cfg(remove_dir)]
file_system::remove_dir(dispatcher, path, options, callback, error).await;
file_system::remove_dir(webview_manager, path, options, callback, error).await;
#[cfg(not(remove_dir))]
allowlist_error(dispatcher, error, "removeDir");
allowlist_error(webview_manager, error, "removeDir");
}
RemoveFile {
path,
@@ -126,9 +134,9 @@ pub(crate) async fn handle<D: ApplicationDispatcherExt + 'static>(
error,
} => {
#[cfg(remove_file)]
file_system::remove_file(dispatcher, path, options, callback, error).await;
file_system::remove_file(webview_manager, path, options, callback, error).await;
#[cfg(not(remove_file))]
allowlist_error(dispatcher, error, "removeFile");
allowlist_error(webview_manager, error, "removeFile");
}
RenameFile {
old_path,
@@ -138,9 +146,17 @@ pub(crate) async fn handle<D: ApplicationDispatcherExt + 'static>(
error,
} => {
#[cfg(rename_file)]
file_system::rename_file(dispatcher, old_path, new_path, options, callback, error).await;
file_system::rename_file(
webview_manager,
old_path,
new_path,
options,
callback,
error,
)
.await;
#[cfg(not(rename_file))]
allowlist_error(dispatcher, error, "renameFile");
allowlist_error(webview_manager, error, "renameFile");
}
ResolvePath {
path,
@@ -149,18 +165,14 @@ pub(crate) async fn handle<D: ApplicationDispatcherExt + 'static>(
error,
} => {
#[cfg(path_api)]
path::resolve_path(dispatcher, path, directory, callback, error).await;
path::resolve_path(webview_manager, path, directory, callback, error).await;
#[cfg(not(path_api))]
allowlist_error(dispatcher, error, "pathApi");
allowlist_error(webview_manager, error, "pathApi");
}
SetTitle { title } => {
// TODO
/*#[cfg(set_title)]
webview.dispatch(move |w| {
w.set_title(&title);
});*/
webview_manager.current_webview()?.set_title(&title);
#[cfg(not(set_title))]
throw_allowlist_error(dispatcher, "title");
throw_allowlist_error(webview_manager, "title");
}
Execute {
command,
@@ -169,22 +181,22 @@ pub(crate) async fn handle<D: ApplicationDispatcherExt + 'static>(
error,
} => {
#[cfg(execute)]
crate::call(dispatcher, command, args, callback, error).await;
crate::call(webview_manager, command, args, callback, error).await;
#[cfg(not(execute))]
throw_allowlist_error(dispatcher, "execute");
throw_allowlist_error(webview_manager, "execute");
}
Open { uri } => {
#[cfg(open)]
browser::open(uri);
#[cfg(not(open))]
throw_allowlist_error(dispatcher, "open");
throw_allowlist_error(webview_manager, "open");
}
ValidateSalt {
salt,
callback,
error,
} => {
salt::validate(dispatcher, salt, callback, error)?;
salt::validate(webview_manager, salt, callback, error)?;
}
Listen {
event,
@@ -194,16 +206,17 @@ pub(crate) async fn handle<D: ApplicationDispatcherExt + 'static>(
#[cfg(event)]
{
let js_string = event::listen_fn(event, handler, once)?;
dispatcher.eval(&js_string);
webview_manager.current_webview()?.eval(&js_string);
}
#[cfg(not(event))]
throw_allowlist_error(dispatcher, "event");
throw_allowlist_error(webview_manager, "event");
}
Emit { event, payload } => {
// TODO emit to optional window
#[cfg(event)]
crate::event::on_event(event, payload);
webview_manager.current_webview()?.on_event(event, payload);
#[cfg(not(event))]
throw_allowlist_error(dispatcher, "event");
throw_allowlist_error(webview_manager, "event");
}
OpenDialog {
options,
@@ -211,9 +224,9 @@ pub(crate) async fn handle<D: ApplicationDispatcherExt + 'static>(
error,
} => {
#[cfg(open_dialog)]
dialog::open(dispatcher, options, callback, error)?;
dialog::open(webview_manager, options, callback, error)?;
#[cfg(not(open_dialog))]
allowlist_error(dispatcher, error, "title");
allowlist_error(webview_manager, error, "title");
}
SaveDialog {
options,
@@ -221,9 +234,9 @@ pub(crate) async fn handle<D: ApplicationDispatcherExt + 'static>(
error,
} => {
#[cfg(save_dialog)]
dialog::save(dispatcher, options, callback, error)?;
dialog::save(webview_manager, options, callback, error)?;
#[cfg(not(save_dialog))]
throw_allowlist_error(dispatcher, "saveDialog");
throw_allowlist_error(webview_manager, "saveDialog");
}
MessageDialog { message } => {
let exe = std::env::current_exe()?;
@@ -233,9 +246,11 @@ pub(crate) async fn handle<D: ApplicationDispatcherExt + 'static>(
.expect("failed to get exe filename")
.to_string_lossy()
.to_string();
dispatcher.send_event(Event::Run(Box::new(move || {
dialog::message(app_name, message);
})));
webview_manager
.current_webview()?
.send_event(Event::Run(Box::new(move || {
dialog::message(app_name, message);
})));
}
AskDialog {
title,
@@ -245,7 +260,7 @@ pub(crate) async fn handle<D: ApplicationDispatcherExt + 'static>(
} => {
let exe = std::env::current_exe()?;
dialog::ask(
dispatcher,
webview_manager,
title.unwrap_or_else(|| {
let exe_dir = exe.parent().expect("failed to get exe directory");
exe
@@ -265,19 +280,19 @@ pub(crate) async fn handle<D: ApplicationDispatcherExt + 'static>(
error,
} => {
#[cfg(http_request)]
http::make_request(dispatcher, *options, callback, error).await;
http::make_request(webview_manager, *options, callback, error).await;
#[cfg(not(http_request))]
allowlist_error(dispatcher, error, "httpRequest");
allowlist_error(webview_manager, error, "httpRequest");
}
CliMatches { callback, error } => {
#[cfg(cli)]
{
let matches = tauri_api::cli::get_matches(&context.config).map_err(|e| e.into());
crate::execute_promise(dispatcher, async move { matches }, callback, error).await;
crate::execute_promise(webview_manager, async move { matches }, callback, error).await;
}
#[cfg(not(cli))]
api_error(
dispatcher,
webview_manager,
error,
"CLI definition not set under tauri.conf.json > tauri > cli (https://tauri.studio/docs/api/config#tauri.cli)",
);
@@ -288,21 +303,21 @@ pub(crate) async fn handle<D: ApplicationDispatcherExt + 'static>(
error,
} => {
#[cfg(notification)]
notification::send(dispatcher, options, callback, error, &context.config).await;
notification::send(webview_manager, options, callback, error, &context.config).await;
#[cfg(not(notification))]
allowlist_error(dispatcher, error, "notification");
allowlist_error(webview_manager, error, "notification");
}
IsNotificationPermissionGranted { callback, error } => {
#[cfg(notification)]
notification::is_permission_granted(dispatcher, callback, error).await;
notification::is_permission_granted(webview_manager, callback, error).await;
#[cfg(not(notification))]
allowlist_error(dispatcher, error, "notification");
allowlist_error(webview_manager, error, "notification");
}
RequestNotificationPermission { callback, error } => {
#[cfg(notification)]
notification::request_permission(dispatcher, callback, error)?;
notification::request_permission(webview_manager, callback, error)?;
#[cfg(not(notification))]
allowlist_error(dispatcher, error, "notification");
allowlist_error(webview_manager, error, "notification");
}
}
Ok(())
@@ -311,19 +326,25 @@ pub(crate) async fn handle<D: ApplicationDispatcherExt + 'static>(
}
#[allow(dead_code)]
fn api_error<D: ApplicationDispatcherExt>(dispatcher: &mut D, error_fn: String, message: &str) {
fn api_error<D: ApplicationDispatcherExt>(
webview_manager: &crate::WebviewManager<D>,
error_fn: String,
message: &str,
) {
let reject_code = tauri_api::rpc::format_callback(error_fn, message);
let _ = dispatcher.eval(&reject_code);
if let Ok(dispatcher) = webview_manager.current_webview() {
dispatcher.eval(&reject_code);
}
}
#[allow(dead_code)]
fn allowlist_error<D: ApplicationDispatcherExt>(
dispatcher: &mut D,
webview_manager: &crate::WebviewManager<D>,
error_fn: String,
allowlist_key: &str,
) {
api_error(
dispatcher,
webview_manager,
error_fn,
&format!(
"{}' not on the allowlist (https://tauri.studio/docs/api/config#tauri.allowlist)",
@@ -333,12 +354,17 @@ fn allowlist_error<D: ApplicationDispatcherExt>(
}
#[allow(dead_code)]
fn throw_allowlist_error<D: ApplicationDispatcherExt>(dispatcher: &mut D, allowlist_key: &str) {
fn throw_allowlist_error<D: ApplicationDispatcherExt>(
webview_manager: &crate::WebviewManager<D>,
allowlist_key: &str,
) {
let reject_code = format!(
r#"throw new Error("'{}' not on the allowlist")"#,
allowlist_key
);
let _ = dispatcher.eval(&reject_code);
if let Ok(dispatcher) = webview_manager.current_webview() {
dispatcher.eval(&reject_code);
}
}
#[cfg(test)]

View File

@@ -21,13 +21,13 @@ fn map_response(response: Response) -> JsonValue {
/// Shows an open dialog.
#[cfg(open_dialog)]
pub fn open<D: ApplicationDispatcherExt + 'static>(
dispatcher: &mut D,
webview_manager: &crate::WebviewManager<D>,
options: OpenDialogOptions,
callback: String,
error: String,
) -> crate::Result<()> {
crate::execute_promise_sync(
dispatcher,
webview_manager,
move || {
let response = if options.multiple {
select_multiple(options.filter, options.default_path)
@@ -48,13 +48,13 @@ pub fn open<D: ApplicationDispatcherExt + 'static>(
/// Shows a save dialog.
#[cfg(save_dialog)]
pub fn save<D: ApplicationDispatcherExt + 'static>(
dispatcher: &mut D,
webview_manager: &crate::WebviewManager<D>,
options: SaveDialogOptions,
callback: String,
error: String,
) -> crate::Result<()> {
crate::execute_promise_sync(
dispatcher,
webview_manager,
move || {
save_file(options.filter, options.default_path)
.map(map_response)
@@ -73,14 +73,14 @@ pub fn message(title: String, message: String) {
/// Shows a dialog with a yes/no question.
pub fn ask<D: ApplicationDispatcherExt + 'static>(
dispatcher: &mut D,
webview_manager: &crate::WebviewManager<D>,
title: String,
message: String,
callback: String,
error: String,
) -> crate::Result<()> {
crate::execute_promise_sync(
dispatcher,
webview_manager,
move || match ask_dialog(message, title) {
DialogSelection::Yes => crate::Result::Ok(true),
_ => crate::Result::Ok(false),

View File

@@ -17,9 +17,9 @@ pub fn listen_fn(event: String, handler: String, once: bool) -> crate::Result<St
window['{emit}'](e.payload, e.salt, true)
}}
",
listeners = crate::event::event_listeners_object_name(),
queue = crate::event::event_queue_object_name(),
emit = crate::event::emit_function_name(),
listeners = crate::app::event::event_listeners_object_name(),
queue = crate::app::event::event_queue_object_name(),
emit = crate::app::event::emit_function_name(),
evt = event,
handler = handler,
once_flag = if once { "true" } else { "false" }

View File

@@ -9,14 +9,14 @@ use super::cmd::{DirOperationOptions, FileOperationOptions};
/// Reads a directory.
#[cfg(read_dir)]
pub async fn read_dir<D: ApplicationDispatcherExt>(
dispatcher: &mut D,
webview_manager: &crate::WebviewManager<D>,
path: PathBuf,
options: Option<DirOperationOptions>,
callback: String,
error: String,
) {
crate::execute_promise(
dispatcher,
webview_manager,
async move {
let (recursive, dir) = if let Some(options_value) = options {
(options_value.recursive, options_value.dir)
@@ -34,7 +34,7 @@ pub async fn read_dir<D: ApplicationDispatcherExt>(
/// Copies a file.
#[cfg(copy_file)]
pub async fn copy_file<D: ApplicationDispatcherExt>(
dispatcher: &mut D,
webview_manager: &crate::WebviewManager<D>,
source: PathBuf,
destination: PathBuf,
options: Option<FileOperationOptions>,
@@ -42,7 +42,7 @@ pub async fn copy_file<D: ApplicationDispatcherExt>(
error: String,
) {
crate::execute_promise(
dispatcher,
webview_manager,
async move {
let (src, dest) = match options.and_then(|o| o.dir) {
Some(dir) => (
@@ -63,14 +63,14 @@ pub async fn copy_file<D: ApplicationDispatcherExt>(
/// Creates a directory.
#[cfg(create_dir)]
pub async fn create_dir<D: ApplicationDispatcherExt>(
dispatcher: &mut D,
webview_manager: &crate::WebviewManager<D>,
path: PathBuf,
options: Option<DirOperationOptions>,
callback: String,
error: String,
) {
crate::execute_promise(
dispatcher,
webview_manager,
async move {
let (recursive, dir) = if let Some(options_value) = options {
(options_value.recursive, options_value.dir)
@@ -95,14 +95,14 @@ pub async fn create_dir<D: ApplicationDispatcherExt>(
/// Removes a directory.
#[cfg(remove_dir)]
pub async fn remove_dir<D: ApplicationDispatcherExt>(
dispatcher: &mut D,
webview_manager: &crate::WebviewManager<D>,
path: PathBuf,
options: Option<DirOperationOptions>,
callback: String,
error: String,
) {
crate::execute_promise(
dispatcher,
webview_manager,
async move {
let (recursive, dir) = if let Some(options_value) = options {
(options_value.recursive, options_value.dir)
@@ -127,14 +127,14 @@ pub async fn remove_dir<D: ApplicationDispatcherExt>(
/// Removes a file
#[cfg(remove_file)]
pub async fn remove_file<D: ApplicationDispatcherExt>(
dispatcher: &mut D,
webview_manager: &crate::WebviewManager<D>,
path: PathBuf,
options: Option<FileOperationOptions>,
callback: String,
error: String,
) {
crate::execute_promise(
dispatcher,
webview_manager,
async move {
let resolved_path = resolve_path(path, options.and_then(|o| o.dir))?;
fs::remove_file(resolved_path)?;
@@ -149,7 +149,7 @@ pub async fn remove_file<D: ApplicationDispatcherExt>(
/// Renames a file.
#[cfg(rename_file)]
pub async fn rename_file<D: ApplicationDispatcherExt>(
dispatcher: &mut D,
webview_manager: &crate::WebviewManager<D>,
old_path: PathBuf,
new_path: PathBuf,
options: Option<FileOperationOptions>,
@@ -157,7 +157,7 @@ pub async fn rename_file<D: ApplicationDispatcherExt>(
error: String,
) {
crate::execute_promise(
dispatcher,
webview_manager,
async move {
let (old, new) = match options.and_then(|o| o.dir) {
Some(dir) => (
@@ -177,7 +177,7 @@ pub async fn rename_file<D: ApplicationDispatcherExt>(
/// Writes a text file.
#[cfg(write_file)]
pub async fn write_file<D: ApplicationDispatcherExt>(
dispatcher: &mut D,
webview_manager: &crate::WebviewManager<D>,
path: PathBuf,
contents: String,
options: Option<FileOperationOptions>,
@@ -185,7 +185,7 @@ pub async fn write_file<D: ApplicationDispatcherExt>(
error: String,
) {
crate::execute_promise(
dispatcher,
webview_manager,
async move {
File::create(resolve_path(path, options.and_then(|o| o.dir))?)
.map_err(crate::Error::Io)
@@ -201,7 +201,7 @@ pub async fn write_file<D: ApplicationDispatcherExt>(
/// Writes a binary file.
#[cfg(write_binary_file)]
pub async fn write_binary_file<D: ApplicationDispatcherExt>(
dispatcher: &mut D,
webview_manager: &crate::WebviewManager<D>,
path: PathBuf,
contents: String,
options: Option<FileOperationOptions>,
@@ -209,7 +209,7 @@ pub async fn write_binary_file<D: ApplicationDispatcherExt>(
error: String,
) {
crate::execute_promise(
dispatcher,
webview_manager,
async move {
base64::decode(contents)
.map_err(crate::Error::Base64Decode)
@@ -229,14 +229,14 @@ pub async fn write_binary_file<D: ApplicationDispatcherExt>(
/// Reads a text file.
#[cfg(read_text_file)]
pub async fn read_text_file<D: ApplicationDispatcherExt>(
dispatcher: &mut D,
webview_manager: &crate::WebviewManager<D>,
path: PathBuf,
options: Option<FileOperationOptions>,
callback: String,
error: String,
) {
crate::execute_promise(
dispatcher,
webview_manager,
async move {
file::read_string(resolve_path(path, options.and_then(|o| o.dir))?)
.map_err(crate::Error::FailedToExecuteApi)
@@ -250,14 +250,14 @@ pub async fn read_text_file<D: ApplicationDispatcherExt>(
/// Reads a binary file.
#[cfg(read_binary_file)]
pub async fn read_binary_file<D: ApplicationDispatcherExt>(
dispatcher: &mut D,
webview_manager: &crate::WebviewManager<D>,
path: PathBuf,
options: Option<FileOperationOptions>,
callback: String,
error: String,
) {
crate::execute_promise(
dispatcher,
webview_manager,
async move {
file::read_binary(resolve_path(path, options.and_then(|o| o.dir))?)
.map_err(crate::Error::FailedToExecuteApi)
@@ -312,7 +312,7 @@ mod test {
//call write file with the path and contents.
write_file(
&mut dispatcher,
&webview_manager,
path.clone(),
contents.clone(),
String::from(""),

View File

@@ -3,13 +3,13 @@ use tauri_api::http::{make_request as request, HttpRequestOptions};
/// Makes an HTTP request and resolves the response to the webview
pub async fn make_request<D: ApplicationDispatcherExt>(
dispatcher: &mut D,
webview_manager: &crate::WebviewManager<D>,
options: HttpRequestOptions,
callback: String,
error: String,
) {
crate::execute_promise(
dispatcher,
webview_manager,
async move { request(options).map_err(|e| e.into()) },
callback,
error,

View File

@@ -4,7 +4,7 @@ use serde_json::Value as JsonValue;
use tauri_api::{config::Config, notification::Notification};
pub async fn send<D: ApplicationDispatcherExt>(
dispatcher: &mut D,
webview_manager: &crate::WebviewManager<D>,
options: NotificationOptions,
callback: String,
error: String,
@@ -13,7 +13,7 @@ pub async fn send<D: ApplicationDispatcherExt>(
let identifier = config.tauri.bundle.identifier.clone();
crate::execute_promise(
dispatcher,
webview_manager,
async move {
let mut notification = Notification::new(identifier).title(options.title);
if let Some(body) = options.body {
@@ -32,12 +32,12 @@ pub async fn send<D: ApplicationDispatcherExt>(
}
pub async fn is_permission_granted<D: ApplicationDispatcherExt>(
dispatcher: &mut D,
webview_manager: &crate::WebviewManager<D>,
callback: String,
error: String,
) {
crate::execute_promise(
dispatcher,
webview_manager,
async move {
let settings = crate::settings::read_settings()?;
if let Some(allow_notification) = settings.allow_notification {
@@ -53,12 +53,12 @@ pub async fn is_permission_granted<D: ApplicationDispatcherExt>(
}
pub fn request_permission<D: ApplicationDispatcherExt + 'static>(
dispatcher: &mut D,
webview_manager: &crate::WebviewManager<D>,
callback: String,
error: String,
) -> crate::Result<()> {
crate::execute_promise_sync(
dispatcher,
webview_manager,
move || {
let mut settings = crate::settings::read_settings()?;
let granted = "granted".to_string();

View File

@@ -3,14 +3,14 @@ use crate::ApplicationDispatcherExt;
use tauri_api::{path, path::BaseDirectory};
pub async fn resolve_path<D: ApplicationDispatcherExt>(
dispatcher: &mut D,
webview_manager: &crate::WebviewManager<D>,
path: String,
directory: Option<BaseDirectory>,
callback: String,
error: String,
) {
crate::execute_promise(
dispatcher,
webview_manager,
async move { path::resolve_path(path, directory).map_err(|e| e.into()) },
callback,
error,

View File

@@ -2,7 +2,7 @@ use crate::ApplicationDispatcherExt;
/// Validates a salt.
pub fn validate<D: ApplicationDispatcherExt>(
dispatcher: &mut D,
webview_manager: &crate::WebviewManager<D>,
salt: String,
callback: String,
error: String,
@@ -13,6 +13,8 @@ pub fn validate<D: ApplicationDispatcherExt>(
Err("Invalid salt")
};
let callback_string = crate::api::rpc::format_callback_result(response, callback, error)?;
dispatcher.eval(callback_string.as_str());
webview_manager
.current_webview()?
.eval(callback_string.as_str());
Ok(())
}

View File

@@ -7,6 +7,9 @@ pub enum Error {
/// Failed to create window.
#[error("failed to create window")]
CreateWindow,
/// Can't access webview dispatcher because the webview was closed or not found.
#[error("webview not found: invalid label or it was closed")]
WebviewNotFound,
/// Embedded asset not found.
#[error("asset not found: {0}")]
AssetNotFound(String),

View File

@@ -6,8 +6,6 @@
//! 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 event system module.
pub mod event;
/// The embedded server helpers.
#[cfg(embedded_server)]
pub mod server;
@@ -40,7 +38,7 @@ pub use app::*;
pub use tauri_api as api;
pub use tauri_macros::FromTauriContext;
pub use webview::{
ApplicationDispatcherExt, ApplicationExt, Callback, Event, WebviewBuilderExt, WindowBuilderExt,
ApplicationDispatcherExt, ApplicationExt, Callback, WebviewBuilderExt, WindowBuilderExt,
};
/// The Tauri webview implementations.
@@ -60,25 +58,29 @@ pub fn execute_promise_sync<
R: Serialize,
F: FnOnce() -> Result<R> + Send + 'static,
>(
dispatcher: &mut D,
webview_manager: &crate::WebviewManager<D>,
task: F,
callback: String,
error: String,
) {
let mut dispatcher_ = dispatcher.clone();
dispatcher.send_event(Event::Run(Box::new(move || {
let callback_string =
match format_callback_result(task().map_err(|err| err.to_string()), &callback, &error) {
Ok(js) => js,
Err(e) => format_callback_result(
std::result::Result::<(), String>::Err(e.to_string()),
&callback,
&error,
)
.unwrap(),
};
dispatcher_.eval(callback_string.as_str());
})));
let webview_manager_ = webview_manager.clone();
if let Ok(dispatcher) = webview_manager.current_webview() {
dispatcher.send_event(webview::Event::Run(Box::new(move || {
let callback_string =
match format_callback_result(task().map_err(|err| err.to_string()), &callback, &error) {
Ok(js) => js,
Err(e) => format_callback_result(
std::result::Result::<(), String>::Err(e.to_string()),
&callback,
&error,
)
.unwrap(),
};
if let Ok(dispatcher) = webview_manager_.current_webview() {
dispatcher.eval(callback_string.as_str());
}
})));
}
}
/// Asynchronously executes the given task
@@ -91,7 +93,7 @@ pub async fn execute_promise<
R: Serialize,
F: futures::Future<Output = Result<R>> + Send + 'static,
>(
dispatcher: &mut D,
webview_manager: &crate::WebviewManager<D>,
task: F,
success_callback: String,
error_callback: String,
@@ -104,19 +106,21 @@ pub async fn execute_promise<
Ok(callback_string) => callback_string,
Err(e) => format_callback(error_callback, e.to_string()),
};
dispatcher.eval(callback_string.as_str());
if let Ok(dispatcher) = webview_manager.current_webview() {
dispatcher.eval(callback_string.as_str());
}
}
/// Calls the given command and evaluates its output to the JS promise described by the `callback` and `error` function names.
pub async fn call<D: ApplicationDispatcherExt>(
dispatcher: &mut D,
webview_manager: &crate::WebviewManager<D>,
command: String,
args: Vec<String>,
callback: String,
error: String,
) {
execute_promise(
dispatcher,
webview_manager,
async move { api::command::get_output(command, args, Stdio::piped()).map_err(|e| e.into()) },
callback,
error,
@@ -124,15 +128,6 @@ pub async fn call<D: ApplicationDispatcherExt>(
.await;
}
/// Closes the splashscreen.
pub fn close_splashscreen<D: ApplicationDispatcherExt>(dispatcher: &mut D) -> crate::Result<()> {
// send a signal to the runner so it knows that it should redirect to the main app content
dispatcher
.eval(r#"window.__TAURI_INVOKE_HANDLER__(JSON.stringify({ cmd: "closeSplashscreen" }))"#);
Ok(())
}
#[cfg(test)]
mod test {
use proptest::prelude::*;

View File

@@ -1,4 +1,6 @@
use crate::{api::config::PluginConfig, async_runtime::Mutex, ApplicationDispatcherExt};
use crate::{
api::config::PluginConfig, async_runtime::Mutex, ApplicationDispatcherExt, WebviewManager,
};
use futures::future::join_all;
@@ -27,15 +29,19 @@ pub trait Plugin<D: ApplicationDispatcherExt + 'static>: Send + Sync {
/// Callback invoked when the webview is created.
#[allow(unused_variables)]
async fn created(&mut self, dispatcher: D) {}
async fn created(&mut self, webview_manager: WebviewManager<D>) {}
/// Callback invoked when the webview is ready.
#[allow(unused_variables)]
async fn ready(&mut self, dispatcher: D) {}
async fn ready(&mut self, webview_manager: WebviewManager<D>) {}
/// Add invoke_handler API extension commands.
#[allow(unused_variables)]
async fn extend_api(&mut self, dispatcher: D, payload: &str) -> crate::Result<()> {
async fn extend_api(
&mut self,
webview_manager: WebviewManager<D>,
payload: &str,
) -> crate::Result<()> {
Err(crate::Error::UnknownApi)
}
}
@@ -93,36 +99,36 @@ pub(crate) async fn initialization_script<D: ApplicationDispatcherExt + 'static>
pub(crate) async fn created<D: ApplicationDispatcherExt + 'static>(
store: &PluginStore<D>,
dispatcher: &mut D,
webview_manager: &crate::WebviewManager<D>,
) {
let mut plugins = store.lock().await;
let mut futures = Vec::new();
for plugin in plugins.iter_mut() {
futures.push(plugin.created(dispatcher.clone()));
futures.push(plugin.created(webview_manager.clone()));
}
join_all(futures).await;
}
pub(crate) async fn ready<D: ApplicationDispatcherExt + 'static>(
store: &PluginStore<D>,
dispatcher: &mut D,
webview_manager: &crate::WebviewManager<D>,
) {
let mut plugins = store.lock().await;
let mut futures = Vec::new();
for plugin in plugins.iter_mut() {
futures.push(plugin.ready(dispatcher.clone()));
futures.push(plugin.ready(webview_manager.clone()));
}
join_all(futures).await;
}
pub(crate) async fn extend_api<D: ApplicationDispatcherExt + 'static>(
store: &PluginStore<D>,
dispatcher: &mut D,
webview_manager: &crate::WebviewManager<D>,
arg: &str,
) -> crate::Result<bool> {
let mut plugins = store.lock().await;
for ext in plugins.iter_mut() {
match ext.extend_api(dispatcher.clone(), arg).await {
match ext.extend_api(webview_manager.clone(), arg).await {
Ok(_) => {
return Ok(true);
}

View File

@@ -1,6 +1,6 @@
pub(crate) mod wry;
pub use crate::plugin::PluginStore;
pub use crate::{api::config::WindowConfig, plugin::PluginStore};
/// An event to be posted to the webview event loop.
pub enum Event {
@@ -8,6 +8,12 @@ pub enum Event {
Run(crate::SyncTask),
}
pub enum Message {
EvalScript(String),
SetWindowTitle(String),
Event(Event),
}
/// The window builder.
pub trait WindowBuilderExt: Sized {
/// The window type.
@@ -16,12 +22,39 @@ pub trait WindowBuilderExt: Sized {
/// Initializes a new window builder.
fn new() -> 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(self, title: String) -> 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;
@@ -42,6 +75,50 @@ pub trait WindowBuilderExt: Sized {
fn finish(self) -> crate::Result<Self::Window>;
}
pub struct WindowBuilder<T>(T);
impl<T> WindowBuilder<T> {
pub fn get(self) -> T {
self.0
}
}
impl<T: WindowBuilderExt> From<&WindowConfig> for WindowBuilder<T> {
fn from(config: &WindowConfig) -> Self {
let mut window = T::new()
.title(config.title.to_string())
.width(config.width)
.height(config.height)
.visible(config.visible)
.resizable(config.resizable)
.decorations(config.decorations)
.maximized(config.maximized)
.fullscreen(config.fullscreen)
.transparent(config.transparent)
.always_on_top(config.always_on_top);
if let Some(min_width) = config.min_width {
window = window.min_width(min_width);
}
if let Some(min_height) = config.min_height {
window = window.min_height(min_height);
}
if let Some(max_width) = config.max_width {
window = window.max_width(max_width);
}
if let Some(max_height) = config.max_height {
window = window.max_height(max_height);
}
if let Some(x) = config.x {
window = window.x(x);
}
if let Some(y) = config.y {
window = window.y(y);
}
Self(window)
}
}
/// The webview builder.
pub trait WebviewBuilderExt: Sized {
/// The webview object that this builder creates.
@@ -70,12 +147,8 @@ pub struct Callback<D> {
/// Webview dispatcher. A thread-safe handle to the webview API.
pub trait ApplicationDispatcherExt: Clone + Send + Sync + Sized {
/// Eval a JS string on the current webview.
fn eval(&mut self, js: &str);
/// Eval a JS string on the webview associated with the given window.
fn eval_on_window(&mut self, window_id: &str, js: &str);
/// Sends a event to the webview.
fn send_event(&self, event: Event);
/// Sends a message to the window.
fn send_message(&self, message: Message);
}
/// The application interface.

View File

@@ -1,17 +1,15 @@
use super::{
ApplicationDispatcherExt, ApplicationExt, Callback, Event, WebviewBuilderExt, WindowBuilderExt,
ApplicationDispatcherExt, ApplicationExt, Callback, Event, Message, WebviewBuilderExt,
WindowBuilderExt,
};
use wry::{ApplicationDispatcher, ApplicationExt as _, WindowExt};
use wry::{ApplicationDispatcher, ApplicationExt as _, WebviewMessage, WindowExt, WindowMessage};
use once_cell::sync::Lazy;
use crate::plugin::PluginStore;
use std::{
collections::HashMap,
sync::{Arc, Mutex},
};
use std::sync::{Arc, Mutex};
impl WindowBuilderExt for wry::AppWindowAttributes {
type Window = Self;
@@ -20,6 +18,46 @@ impl WindowBuilderExt for wry::AppWindowAttributes {
Default::default()
}
fn x(mut self, x: f64) -> Self {
self.x = Some(x);
self
}
fn y(mut self, y: f64) -> Self {
self.y = Some(y);
self
}
fn width(mut self, width: f64) -> Self {
self.width = width;
self
}
fn height(mut self, height: f64) -> Self {
self.height = height;
self
}
fn min_width(mut self, min_width: f64) -> Self {
self.min_width = Some(min_width);
self
}
fn min_height(mut self, min_height: f64) -> Self {
self.min_height = Some(min_height);
self
}
fn max_width(mut self, max_width: f64) -> Self {
self.max_width = Some(max_width);
self
}
fn max_height(mut self, max_height: f64) -> Self {
self.max_height = Some(max_height);
self
}
fn resizable(mut self, resizable: bool) -> Self {
self.resizable = resizable;
self
@@ -30,6 +68,11 @@ impl WindowBuilderExt for wry::AppWindowAttributes {
self
}
fn fullscreen(mut self, fullscreen: bool) -> Self {
self.fullscreen = fullscreen;
self
}
fn maximized(mut self, maximized: bool) -> Self {
self.maximized = maximized;
self
@@ -50,7 +93,6 @@ impl WindowBuilderExt for wry::AppWindowAttributes {
self
}
/// Whether the window should always be on top of other windows.
fn always_on_top(mut self, always_on_top: bool) -> Self {
self.always_on_top = always_on_top;
self
@@ -90,45 +132,29 @@ impl WebviewBuilderExt for wry::WebViewAttributes {
pub struct WryDispatcher {
inner: Arc<Mutex<wry::AppDispatcher<Event>>>,
current_window: wry::WindowId,
windows: Arc<Mutex<HashMap<String, wry::WindowId>>>,
}
struct WryMessage(wry::Message<wry::WindowId, Event>);
impl From<(wry::WindowId, Message)> for WryMessage {
fn from((id, message): (wry::WindowId, Message)) -> Self {
let message = match message {
Message::EvalScript(js) => wry::Message::Webview(id, WebviewMessage::EvalScript(js)),
Message::SetWindowTitle(title) => wry::Message::Window(id, WindowMessage::SetTitle(title)),
Message::Event(event) => wry::Message::Custom(event),
};
WryMessage(message)
}
}
impl ApplicationDispatcherExt for WryDispatcher {
fn eval(&mut self, js: &str) {
#[cfg(target_os = "linux")]
let window_id = self.current_window;
#[cfg(not(target_os = "linux"))]
let window_id = self.current_window.clone();
fn send_message(&self, message: Message) {
let message: WryMessage = (self.current_window, message).into();
self
.inner
.lock()
.unwrap()
.dispatch_message(wry::Message::Script(window_id, js.to_string()))
.unwrap();
}
fn eval_on_window(&mut self, window_id: &str, js: &str) {
if let Some(window_id) = self.windows.lock().unwrap().get(window_id) {
#[cfg(target_os = "linux")]
let window_id = *window_id;
#[cfg(not(target_os = "linux"))]
let window_id = window_id.clone();
self
.inner
.lock()
.unwrap()
.dispatch_message(wry::Message::Script(window_id, js.to_string()))
.unwrap();
}
}
fn send_event(&self, event: Event) {
self
.inner
.lock()
.unwrap()
.dispatch_message(wry::Message::Custom(event))
.dispatch_message(message.0)
.unwrap();
}
}
@@ -136,7 +162,6 @@ impl ApplicationDispatcherExt for WryDispatcher {
/// A wrapper around the wry Application interface.
pub struct WryApplication {
inner: wry::Application<Event>,
windows: Arc<Mutex<HashMap<String, wry::WindowId>>>,
dispatcher_handle: Arc<Mutex<wry::AppDispatcher<Event>>>,
}
@@ -154,11 +179,9 @@ impl ApplicationExt for WryApplication {
fn new() -> crate::Result<Self> {
let app = wry::Application::new().map_err(|_| crate::Error::CreateWebview)?;
let dispatcher = app.dispatcher();
let windows = Arc::new(Mutex::new(HashMap::new()));
Ok(Self {
inner: app,
windows,
dispatcher_handle: Arc::new(Mutex::new(dispatcher)),
})
}
@@ -166,7 +189,6 @@ impl ApplicationExt for WryApplication {
fn dispatcher(&self, window: &Self::Window) -> Self::Dispatcher {
WryDispatcher {
inner: self.dispatcher_handle.clone(),
windows: self.windows.clone(),
current_window: window.id(),
}
}
@@ -188,7 +210,6 @@ impl ApplicationExt for WryApplication {
let mut wry_callbacks = Vec::new();
for mut callback in callbacks {
let dispatcher_handle = self.dispatcher_handle.clone();
let windows = self.windows.clone();
let window_id = window.id();
let callback = wry::Callback {
@@ -197,7 +218,6 @@ impl ApplicationExt for WryApplication {
(callback.function)(
&WryDispatcher {
inner: dispatcher_handle.clone(),
windows: windows.clone(),
current_window: window_id,
},
seq,

View File

@@ -12,9 +12,9 @@
"allowlist": {
"all": true
},
"window": {
"windows": [{
"title": "Tauri App"
},
}],
"security": {
"csp": "default-src blob: data: filesystem: ws: http: https: 'unsafe-eval' 'unsafe-inline'"
}