feature: import official webview rust binding (#846)

Co-authored-by: Lucas Nogueira <lucas@tauri.studio>
This commit is contained in:
Ngo Iok Ui (Wu Yu Wei)
2020-07-18 19:04:22 +08:00
committed by GitHub
parent dac1db3983
commit cd5b401707
31 changed files with 739 additions and 505 deletions

View File

@@ -0,0 +1,8 @@
---
"tauri": minor
---
Moving the webview implementation to [webview](https://github.com/webview/webview), with the [official Rust binding](https://github.com/webview/webview_rust).
This is a breaking change.
IE support has been dropped, so the `edge` object on `tauri.conf.json > tauri` no longer exists and you need to remove it.
`webview.handle()` has been replaced with `webview.as_mut()`.

View File

@@ -1,6 +1,6 @@
declare global {
interface External {
invoke: (command: string) => void
interface Window {
__TAURI_INVOKE_HANDLER__: (command: string) => void
}
}
@@ -21,7 +21,7 @@ function uid(): string {
* @param args
*/
function invoke(args: any): void {
window.external.invoke(typeof args === 'object' ? JSON.stringify(args) : args)
window.__TAURI_INVOKE_HANDLER__(args)
}
function transformCallback(callback?: (response: any) => void, once = false): string {

View File

@@ -192,10 +192,6 @@ async function printAppInfo(tauriDir: string): Promise<void> {
key: ' CSP',
value: config.tauri.security ? config.tauri.security.csp : 'unset'
})
printInfo({
key: ' Windows',
value: config.tauri.edge?.active ? 'Edge' : 'MSHTML'
})
printInfo({
key: ' distDir',
value: config.build

View File

@@ -54,9 +54,6 @@ const getTauriConfig = (cfg: Partial<TauriConfig>): TauriConfig => {
security: {
csp: "default-src blob: data: filesystem: ws: http: https: 'unsafe-eval' 'unsafe-inline'"
},
edge: {
active: true
},
inliner: {
active: true
}

View File

@@ -117,7 +117,7 @@ class Runner {
self.__parseHtml(cfg, indexDir, false)
.then(({ html }) => {
const headers: { [key: string]: string } = {}
if(proxyRes.headers['content-type']) {
if (proxyRes.headers['content-type']) {
headers['content-type'] = proxyRes.headers['content-type']
} else {
const charsetMatch = /charset="(\S+)"/g.exec(bodyStr)
@@ -527,10 +527,6 @@ class Runner {
tomlFeatures.push(...whitelist.map(toKebabCase))
}
if (cfg.tauri.edge.active) {
tomlFeatures.push('edge')
}
if (cfg.tauri.cli) {
tomlFeatures.push('cli')
}

View File

@@ -45,9 +45,6 @@ export default {
security: {
csp: "default-src blob: data: filesystem: ws: http: https: 'unsafe-eval' 'unsafe-inline'"
},
edge: {
active: true
},
inliner: {
active: true
}

View File

@@ -391,17 +391,6 @@
"$ref": "#/definitions/CliConfig",
"description": "app's CLI definition"
},
"edge": {
"additionalProperties": false,
"defaultProperties": [
],
"properties": {
"active": {
"type": "boolean"
}
},
"type": "object"
},
"embeddedServer": {
"additionalProperties": false,
"defaultProperties": [
@@ -496,7 +485,6 @@
},
"required": [
"bundle",
"edge",
"embeddedServer",
"inliner",
"security",

View File

@@ -285,9 +285,6 @@ export interface TauriConfig {
security: {
csp?: string
}
edge: {
active?: boolean
}
inliner: {
active?: boolean
}

View File

@@ -401,17 +401,6 @@ export const TauriConfigSchema = {
"$ref": "#/definitions/CliConfig",
"description": "app's CLI definition"
},
"edge": {
"additionalProperties": false,
"defaultProperties": [
],
"properties": {
"active": {
"type": "boolean"
}
},
"type": "object"
},
"embeddedServer": {
"additionalProperties": false,
"defaultProperties": [
@@ -506,7 +495,6 @@ export const TauriConfigSchema = {
},
"required": [
"bundle",
"edge",
"embeddedServer",
"inliner",
"security",

View File

@@ -6,31 +6,29 @@ if (!String.prototype.startsWith) {
}
}
// makes the window.external.invoke API available after window.location.href changes
switch (navigator.platform) {
case "Macintosh":
case "MacPPC":
case "MacIntel":
case "Mac68K":
window.external = this
invoke = function (x) {
webkit.messageHandlers.invoke.postMessage(x);
}
break;
case "Windows":
case "WinCE":
case "Win32":
case "Win64":
break;
default:
window.external = this
invoke = function (x) {
window.webkit.messageHandlers.external.postMessage(x);
}
break;
}
(function () {
function webviewBind (name) {
var RPC = window._rpc = (window._rpc || { nextSeq: 1 });
window[name] = function () {
var seq = RPC.nextSeq++;
var promise = new Promise(function (resolve, reject) {
RPC[seq] = {
resolve: resolve,
reject: reject,
};
});
window.external.invoke(JSON.stringify({
id: seq,
method: name,
params: Array.prototype.slice.call(arguments),
}));
return promise;
}
}
if (!window.__TAURI_INVOKE_HANDLER__) {
webviewBind('__TAURI_INVOKE_HANDLER__')
}
function s4() {
return Math.floor((1 + Math.random()) * 0x10000)
.toString(16)
@@ -89,9 +87,6 @@ switch (navigator.platform) {
if (!window.__TAURI__) {
window.__TAURI__ = {}
}
window.__TAURI__.invoke = function invoke(args) {
window.external.invoke(JSON.stringify(args))
}
window.__TAURI__.transformCallback = function transformCallback(callback) {
var once = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false
@@ -109,10 +104,10 @@ switch (navigator.platform) {
}
window.__TAURI__.promisified = function promisified(args) {
var _this = this;
var _this = this
return new Promise(function (resolve, reject) {
_this.invoke(_objectSpread({
window.__TAURI_INVOKE_HANDLER__(_objectSpread({
callback: _this.transformCallback(resolve),
error: _this.transformCallback(reject)
}, args))
@@ -127,19 +122,6 @@ switch (navigator.platform) {
})
}
// init tauri API
try {
window.__TAURI__.invoke({
cmd: 'init'
})
} catch (e) {
window.addEventListener('DOMContentLoaded', function () {
window.__TAURI__.invoke({
cmd: 'init'
})
}, true)
}
document.addEventListener('error', function (e) {
var target = e.target
while (target != null) {
@@ -161,7 +143,7 @@ switch (navigator.platform) {
while (target != null) {
if (target.matches ? target.matches('a') : target.msMatchesSelector('a')) {
if (target.href && target.href.startsWith('http') && target.target === '_blank') {
window.__TAURI__.invoke({
window.__TAURI_INVOKE_HANDLER__({
cmd: 'open',
uri: target.href
})

View File

@@ -20,8 +20,7 @@ features = [ "all-api" ]
[dependencies]
serde_json = "1.0"
serde = { version = "1.0", features = [ "derive" ] }
tauri-webview-sys = "0.5.0"
tauri-web-view = "0.6.2"
webview_rust = { version = "0.1", git = "https://github.com/webview/webview_rust.git", branch = "dev" }
tauri_includedir = "0.6.0"
phf = "0.8.0"
base64 = "0.12.3"
@@ -35,6 +34,7 @@ thiserror = "1.0.20"
envmnt = "0.8.3"
once_cell = "1.4.0"
tauri-api = { version = "0.7", path = "../tauri-api" }
urlencoding = "1.1.1"
[target."cfg(target_os = \"windows\")".dependencies]
runas = "0.2"
@@ -46,12 +46,11 @@ cfg_aliases = "0.1.0"
[dev-dependencies]
proptest = "0.10.0"
serde_json = "1.0"
tauri = { path = ".", features = [ "all-api", "edge" ] }
tauri = { path = ".", features = [ "all-api" ] }
serde = { version = "1.0", features = [ "derive" ] }
[features]
cli = [ "tauri-api/cli" ]
edge = [ "tauri-web-view/edge" ]
embedded-server = [ "tiny_http" ]
no-server = [ ]
all-api = [ "tauri-api/notification" ]

View File

@@ -1,5 +1,5 @@
document.getElementById('log').addEventListener('click', function () {
window.__TAURI__.invoke({
window.__TAURI__.tauri.invoke({
cmd: 'logOperation',
event: 'tauri-click',
payload: 'this payload is optional because we used Option in Rust'
@@ -7,7 +7,7 @@ document.getElementById('log').addEventListener('click', function () {
})
document.getElementById('request').addEventListener('click', function () {
window.__TAURI__.promisified({
window.__TAURI__.tauri.promisified({
cmd: 'performRequest',
endpoint: 'dummy endpoint arg',
body: {

File diff suppressed because one or more lines are too long

View File

@@ -24,7 +24,7 @@ icon = [
[dependencies]
serde_json = "1.0"
serde = { version = "1.0", features = [ "derive" ] }
tauri = { path = "../../..", features = [ "all-api", "edge", "cli" ] }
tauri = { path = "../../..", features = [ "all-api", "cli" ] }
[target."cfg(windows)".build-dependencies]
winres = "0.1"

View File

@@ -15,14 +15,14 @@ struct Reply {
fn main() {
tauri::AppBuilder::new()
.setup(|webview, _source| {
let handle = webview.handle();
let mut webview = webview.as_mut();
tauri::event::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(&handle, String::from("rust-event"), Some(reply))
tauri::event::emit(&mut webview, String::from("rust-event"), Some(reply))
.expect("failed to emit");
});
})

View File

@@ -55,9 +55,6 @@
"security": {
"csp": "default-src blob: data: filesystem: ws: http: https: 'unsafe-eval' 'unsafe-inline'"
},
"edge": {
"active": true
},
"inliner": {
"active": true
}

View File

@@ -1,9 +1,9 @@
use web_view::WebView;
use webview_rust_sys::Webview;
mod runner;
type InvokeHandler = Box<dyn FnMut(&mut WebView<'_, ()>, &str) -> Result<(), String>>;
type Setup = Box<dyn FnMut(&mut WebView<'_, ()>, String)>;
type InvokeHandler = Box<dyn FnMut(&mut Webview, &str) -> Result<(), String>>;
type Setup = Box<dyn FnMut(&mut Webview, String)>;
/// The application runner.
pub struct App {
@@ -26,7 +26,7 @@ impl App {
/// The message is considered consumed if the handler exists and returns an Ok Result.
pub(crate) fn run_invoke_handler(
&mut self,
webview: &mut WebView<'_, ()>,
webview: &mut Webview,
arg: &str,
) -> Result<bool, String> {
if let Some(ref mut invoke_handler) = self.invoke_handler {
@@ -37,7 +37,7 @@ impl App {
}
/// Runs the setup callback if defined.
pub(crate) fn run_setup(&mut self, webview: &mut WebView<'_, ()>, source: String) {
pub(crate) fn run_setup(&mut self, webview: &mut Webview, source: String) {
if let Some(ref mut setup) = self.setup {
setup(webview, source);
}
@@ -71,7 +71,7 @@ impl AppBuilder {
}
/// Defines the JS message handler callback.
pub fn invoke_handler<F: FnMut(&mut WebView<'_, ()>, &str) -> Result<(), String> + 'static>(
pub fn invoke_handler<F: FnMut(&mut Webview, &str) -> Result<(), String> + 'static>(
mut self,
invoke_handler: F,
) -> Self {
@@ -80,7 +80,7 @@ impl AppBuilder {
}
/// Defines the setup callback.
pub fn setup<F: FnMut(&mut WebView<'_, ()>, String) + 'static>(mut self, setup: F) -> Self {
pub fn setup<F: FnMut(&mut Webview, String) + 'static>(mut self, setup: F) -> Self {
self.setup = Some(Box::new(setup));
self
}

View File

@@ -7,13 +7,19 @@ use std::{
thread::spawn,
};
use web_view::{builder, Content, WebView};
use webview_rust_sys::{SizeHint, Webview, WebviewBuilder};
use super::App;
#[cfg(embedded_server)]
use crate::api::tcp::{get_available_port, port_is_available};
use tauri_api::config::get;
#[allow(dead_code)]
enum Content<T> {
Html(T),
Url(T),
}
/// Main entry point for running the Webview
pub(crate) fn run(application: &mut App) -> crate::Result<()> {
// setup the content using the config struct depending on the compile target
@@ -56,7 +62,7 @@ pub(crate) fn run(application: &mut App) -> crate::Result<()> {
spawn_updater()?;
// run the webview
webview.run()?;
webview.run();
Ok(())
}
@@ -99,7 +105,10 @@ fn setup_content() -> crate::Result<Content<String>> {
dev_dir
);
}
Ok(Content::Html(read_to_string(dev_path)?))
Ok(Content::Html(format!(
"data:text/html,{}",
urlencoding::encode(&read_to_string(dev_path)?)
)))
}
}
@@ -121,7 +130,10 @@ fn setup_content() -> crate::Result<Content<String>> {
#[cfg(no_server)]
fn setup_content() -> crate::Result<Content<String>> {
let html = include_str!(concat!(env!("OUT_DIR"), "/index.tauri.html"));
Ok(Content::Html(html.to_string()))
Ok(Content::Html(format!(
"data:text/html,{}",
urlencoding::encode(html)
)))
}
// get the port for the embedded server
@@ -182,12 +194,49 @@ fn spawn_updater() -> crate::Result<()> {
Ok(())
}
pub fn init() -> String {
#[cfg(not(event))]
return String::from("");
#[cfg(event)]
return format!(
"
window['{queue}'] = [];
window['{fn}'] = function (payload, salt, ignoreQueue) {{
const listeners = (window['{listeners}'] && window['{listeners}'][payload.type]) || []
if (!ignoreQueue && listeners.length === 0) {{
window['{queue}'].push({{
payload: payload,
salt: salt
}})
}}
if (listeners.length > 0) {{
window.__TAURI__.promisified({{
cmd: 'validateSalt',
salt: salt
}}).then(function () {{
for (let i = listeners.length - 1; i >= 0; i--) {{
const listener = listeners[i]
if (listener.once)
listeners.splice(i, 1)
listener.handler(payload)
}}
}})
}}
}}
",
fn = crate::event::emit_function_name(),
queue = crate::event::event_queue_object_name(),
listeners = crate::event::event_listeners_object_name()
);
}
// build the webview struct
fn build_webview(
application: &mut App,
content: Content<String>,
splashscreen_content: Option<Content<String>>,
) -> crate::Result<WebView<'_, ()>> {
) -> crate::Result<Webview> {
let config = get()?;
let content_clone = match content {
Content::Html(ref html) => Content::Html(html.clone()),
@@ -197,107 +246,123 @@ fn build_webview(
// get properties from config struct
let width = config.tauri.window.width;
let height = config.tauri.window.height;
let resizable = config.tauri.window.resizable;
let fullscreen = config.tauri.window.fullscreen;
let resizable = if config.tauri.window.resizable {
SizeHint::NONE
} else {
SizeHint::FIXED
};
// let fullscreen = config.tauri.window.fullscreen;
let title = config.tauri.window.title.clone().into_boxed_str();
let has_splashscreen = splashscreen_content.is_some();
let mut initialized_splashscreen = false;
let url = match splashscreen_content {
Some(Content::Html(s)) => s,
_ => match content {
Content::Html(s) => s,
Content::Url(s) => s,
},
};
let mut webview = builder()
let mut webview = WebviewBuilder::new()
.init(&format!(
r#"
{event_init}
if (window.__TAURI_INVOKE_HANDLER__) {{
window.__TAURI_INVOKE_HANDLER__({{ cmd: "__initialized" }})
}} else {{
window.addEventListener('DOMContentLoaded', function () {{
window.__TAURI_INVOKE_HANDLER__({{ cmd: "__initialized" }})
}})
}}
"#,
event_init = init()
))
.title(Box::leak(title))
.size(width, height)
.resizable(resizable)
.width(width as usize)
.height(height as usize)
.resize(resizable)
.debug(debug)
.user_data(())
.invoke_handler(move |webview, arg| {
if arg == r#"{"cmd":"__initialized"}"# {
let source = if has_splashscreen && !initialized_splashscreen {
initialized_splashscreen = true;
"splashscreen"
} else {
"window-1"
};
application.run_setup(webview, source.to_string());
if source == "window-1" {
let handle = webview.handle();
handle
.dispatch(|webview| {
crate::plugin::ready(webview);
Ok(())
})
.expect("failed to invoke ready hook");
}
} else if arg == r#"{"cmd":"closeSplashscreen"}"# {
let content_href = match content_clone {
Content::Html(ref html) => html,
Content::Url(ref url) => url,
};
webview.eval(&format!(r#"window.location.href = "{}""#, content_href))?;
} else {
let endpoint_handle = crate::endpoints::handle(webview, arg)
.map_err(|tauri_handle_error| {
let tauri_handle_error_str = tauri_handle_error.to_string();
if tauri_handle_error_str.contains("unknown variant") {
match application.run_invoke_handler(webview, arg) {
Ok(handled) => {
if handled {
String::from("")
} else {
tauri_handle_error_str
}
}
Err(e) => e,
}
} else {
tauri_handle_error_str
}
})
.map_err(|app_handle_error| {
if app_handle_error.contains("unknown variant") {
match crate::plugin::extend_api(webview, arg) {
Ok(handled) => {
if handled {
String::from("")
} else {
app_handle_error
}
}
Err(e) => e,
}
} else {
app_handle_error
}
})
.map_err(|e| e.replace("'", "\\'"));
if let Err(handler_error_message) = endpoint_handle {
if handler_error_message != "" {
webview.eval(&get_api_error_message(arg, handler_error_message))?;
}
}
}
Ok(())
})
.content(if splashscreen_content.is_some() {
splashscreen_content.expect("failed to get splashscreen content")
} else {
content
})
.build()?;
webview.set_fullscreen(fullscreen);
.url(&url)
.build();
// TODO waiting for webview window API
// webview.set_fullscreen(fullscreen);
if has_splashscreen {
let env_var = envmnt::get_or("TAURI_DIR", "../dist");
let path = Path::new(&env_var);
let contents = fs::read_to_string(path.join("/tauri.js"))?;
// inject the tauri.js entry point
webview
.handle()
.dispatch(move |_webview| _webview.eval(&contents))?;
webview.dispatch(move |_webview| _webview.eval(&contents));
}
let mut w = webview.clone();
webview.bind("__TAURI_INVOKE_HANDLER__", move |_, arg| {
// transform `[payload]` to `payload`
let arg = arg.chars().skip(1).take(arg.len() - 2).collect::<String>();
if arg == r#"{"cmd":"__initialized"}"# {
let source = if has_splashscreen && !initialized_splashscreen {
initialized_splashscreen = true;
"splashscreen"
} else {
"window-1"
};
application.run_setup(&mut w, source.to_string());
if source == "window-1" {
w.dispatch(|w| {
crate::plugin::ready(w);
});
}
} else if arg == r#"{"cmd":"closeSplashscreen"}"# {
let content_href = match content_clone {
Content::Html(ref html) => html,
Content::Url(ref url) => url,
};
w.eval(&format!(r#"window.location.href = "{}""#, content_href));
} else {
let endpoint_handle = crate::endpoints::handle(&mut w, &arg)
.map_err(|tauri_handle_error| {
let tauri_handle_error_str = tauri_handle_error.to_string();
if tauri_handle_error_str.contains("unknown variant") {
match application.run_invoke_handler(&mut w, &arg) {
Ok(handled) => {
if handled {
String::from("")
} else {
tauri_handle_error_str
}
}
Err(e) => e,
}
} else {
tauri_handle_error_str
}
})
.map_err(|app_handle_error| {
if app_handle_error.contains("unknown variant") {
match crate::plugin::extend_api(&mut w, &arg) {
Ok(handled) => {
if handled {
String::from("")
} else {
app_handle_error
}
}
Err(e) => e,
}
} else {
app_handle_error
}
})
.map_err(|e| e.replace("'", "\\'"));
if let Err(handler_error_message) = endpoint_handle {
if handler_error_message != "" {
w.eval(&get_api_error_message(&arg, handler_error_message));
}
}
}
});
Ok(webview)
}
@@ -312,9 +377,9 @@ fn get_api_error_message(arg: &str, handler_error_message: String) -> String {
#[cfg(test)]
mod test {
use super::Content;
use proptest::prelude::*;
use std::env;
use web_view::Content;
#[cfg(not(feature = "embedded-server"))]
use std::{fs::read_to_string, path::Path};
@@ -351,7 +416,12 @@ mod test {
};
assert_eq!(
s,
read_to_string(Path::new(&dist_dir).join("index.tauri.html")).unwrap()
format!(
"data:text/html,{}",
urlencoding::encode(
&read_to_string(Path::new(&dist_dir).join("index.tauri.html")).unwrap()
)
)
);
}
_ => panic!("setup content failed"),
@@ -367,7 +437,10 @@ mod test {
let dev_path = Path::new(dev_dir).join("index.tauri.html");
assert_eq!(
s,
read_to_string(dev_path).expect("failed to read dev path")
format!(
"data:text/html,{}",
urlencoding::encode(&read_to_string(dev_path).expect("failed to read dev path"))
)
);
}
_ => panic!("setup content failed"),

View File

@@ -1,11 +1,8 @@
mod cmd;
#[allow(unused_imports)]
mod file_system;
mod init;
mod salt;
use init::init;
#[cfg(assets)]
mod asset;
#[cfg(open)]
@@ -19,24 +16,15 @@ mod http;
#[cfg(notification)]
mod notification;
use web_view::WebView;
use webview_rust_sys::Webview;
#[allow(unused_variables)]
pub(crate) fn handle<T: 'static>(webview: &mut WebView<'_, T>, arg: &str) -> crate::Result<()> {
pub(crate) fn handle(webview: &mut Webview, arg: &str) -> crate::Result<()> {
use cmd::Cmd::*;
match serde_json::from_str(arg) {
Err(e) => Err(e.into()),
Ok(command) => {
match command {
Init {} => {
let event_init = init()?;
webview.eval(&format!(
r#"{event_init}
window.external.invoke('{{"cmd":"__initialized"}}')
"#,
event_init = event_init
))?;
}
ReadTextFile {
path,
options,
@@ -153,7 +141,7 @@ pub(crate) fn handle<T: 'static>(webview: &mut WebView<'_, T>, arg: &str) -> cra
}
SetTitle { title } => {
#[cfg(set_title)]
webview.set_title(&title)?;
webview.set_title(&title);
#[cfg(not(set_title))]
throw_whitelist_error(webview, "title");
}
@@ -189,7 +177,7 @@ pub(crate) fn handle<T: 'static>(webview: &mut WebView<'_, T>, arg: &str) -> cra
#[cfg(event)]
{
let js_string = event::listen_fn(event, handler, once)?;
webview.eval(&js_string)?;
webview.eval(&js_string);
}
#[cfg(not(event))]
throw_whitelist_error(webview, "event");
@@ -286,19 +274,13 @@ pub(crate) fn handle<T: 'static>(webview: &mut WebView<'_, T>, arg: &str) -> cra
}
#[allow(dead_code)]
fn api_error<T: 'static>(webview: &mut WebView<'_, T>, error_fn: String, message: &str) {
fn api_error(webview: &mut Webview, error_fn: String, message: &str) {
let reject_code = tauri_api::rpc::format_callback(error_fn, message);
webview
.eval(&reject_code)
.expect("failed to eval api error")
webview.eval(&reject_code)
}
#[allow(dead_code)]
fn whitelist_error<T: 'static>(
webview: &mut WebView<'_, T>,
error_fn: String,
whitelist_key: &str,
) {
fn whitelist_error(webview: &mut Webview, error_fn: String, whitelist_key: &str) {
api_error(
webview,
error_fn,
@@ -310,35 +292,15 @@ fn whitelist_error<T: 'static>(
}
#[allow(dead_code)]
fn throw_whitelist_error<T: 'static>(webview: &mut WebView<'_, T>, whitelist_key: &str) {
fn throw_whitelist_error(webview: &mut Webview, whitelist_key: &str) {
let reject_code = format!(r#"throw new Error("'{}' not whitelisted")"#, whitelist_key);
webview
.eval(&reject_code)
.expect("failed to eval whitelist error")
webview.eval(&reject_code)
}
#[cfg(test)]
mod test {
use proptest::prelude::*;
#[test]
// test to see if check init produces a string or not.
fn check_init() {
if cfg!(not(event)) {
let res = super::init();
match res {
Ok(s) => assert_eq!(s, ""),
Err(e) => panic!("init Err {:?}", e.to_string()),
}
} else if cfg!(event) {
let res = super::init();
match res {
Ok(s) => assert!(s.contains("window.__TAURI__.promisified")),
Err(e) => panic!("init Err {:?}", e.to_string()),
}
}
}
// check the listen_fn for various usecases.
proptest! {
#[cfg(event)]

View File

@@ -1,14 +1,14 @@
use std::path::PathBuf;
use web_view::WebView;
use webview_rust_sys::Webview;
pub fn load<T: 'static>(
webview: &mut WebView<'_, T>,
pub fn load(
webview: &mut Webview,
asset: String,
asset_type: String,
callback: String,
error: String,
) {
let handle = webview.handle();
let mut webview_mut = webview.as_mut();
crate::execute_promise(
webview,
move || {
@@ -58,19 +58,30 @@ pub fn load<T: 'static>(
base64::encode(&read_asset.expect("Failed to read asset type").into_owned())
))
} else {
handle
.dispatch(move |_webview| {
let asset_bytes = &read_asset.expect("Failed to read asset type").into_owned();
let asset_str =
&std::str::from_utf8(asset_bytes).expect("failed to convert asset bytes to u8 slice");
if asset_type == "stylesheet" {
_webview.inject_css(asset_str)
} else {
_webview.eval(asset_str)
}
})
.map_err(|err| err.into())
.map(|_| "Asset loaded successfully".to_string())
let asset_bytes = read_asset.expect("Failed to read asset type");
webview_mut.dispatch(move |webview_ref| {
let asset_str =
std::str::from_utf8(&asset_bytes).expect("failed to convert asset bytes to u8 slice");
if asset_type == "stylesheet" {
webview_ref.eval(&format!(
r#"
(function () {{
var css = document.createElement('style')
css.type = 'text/css'
if (css.styleSheet)
css.styleSheet.cssText = {css}
else
css.appendChild(document.createTextNode({css}))
document.getElementsByTagName("head")[0].appendChild(css);
}})()
"#,
css = asset_str
));
} else {
webview_ref.eval(asset_str);
}
})?;
Ok("Asset loaded successfully".to_string())
}
},
callback,

View File

@@ -64,8 +64,6 @@ pub struct NotificationOptions {
#[derive(Deserialize)]
#[serde(tag = "cmd", rename_all = "camelCase")]
pub enum Cmd {
/// The init command
Init {},
/// The read text file API.
ReadTextFile {
path: PathBuf,

View File

@@ -1,7 +1,7 @@
use super::cmd::{OpenDialogOptions, SaveDialogOptions};
use crate::api::dialog::{pick_folder, save_file, select, select_multiple, Response};
use serde_json::Value as JsonValue;
use web_view::WebView;
use webview_rust_sys::Webview;
/// maps a dialog response to a JS value to eval
fn map_response(response: Response) -> JsonValue {
@@ -14,8 +14,8 @@ fn map_response(response: Response) -> JsonValue {
/// Shows an open dialog.
#[cfg(open_dialog)]
pub fn open<T: 'static>(
webview: &mut WebView<'_, T>,
pub fn open(
webview: &mut Webview,
options: OpenDialogOptions,
callback: String,
error: String,
@@ -40,8 +40,8 @@ pub fn open<T: 'static>(
/// Shows a save dialog.
#[cfg(save_dialog)]
pub fn save<T: 'static>(
webview: &mut WebView<'_, T>,
pub fn save(
webview: &mut Webview,
options: SaveDialogOptions,
callback: String,
error: String,

View File

@@ -1,4 +1,4 @@
use web_view::WebView;
use webview_rust_sys::Webview;
use tauri_api::dir;
use tauri_api::file;
@@ -13,8 +13,8 @@ use super::cmd::{DirOperationOptions, FileOperationOptions};
/// Reads a directory.
#[cfg(read_dir)]
pub fn read_dir<T: 'static>(
webview: &mut WebView<'_, T>,
pub fn read_dir(
webview: &mut Webview,
path: PathBuf,
options: Option<DirOperationOptions>,
callback: String,
@@ -37,8 +37,8 @@ pub fn read_dir<T: 'static>(
/// Copies a file.
#[cfg(copy_file)]
pub fn copy_file<T: 'static>(
webview: &mut WebView<'_, T>,
pub fn copy_file(
webview: &mut Webview,
source: PathBuf,
destination: PathBuf,
options: Option<FileOperationOptions>,
@@ -64,8 +64,8 @@ pub fn copy_file<T: 'static>(
/// Creates a directory.
#[cfg(create_dir)]
pub fn create_dir<T: 'static>(
webview: &mut WebView<'_, T>,
pub fn create_dir(
webview: &mut Webview,
path: PathBuf,
options: Option<DirOperationOptions>,
callback: String,
@@ -95,8 +95,8 @@ pub fn create_dir<T: 'static>(
/// Removes a directory.
#[cfg(remove_dir)]
pub fn remove_dir<T: 'static>(
webview: &mut WebView<'_, T>,
pub fn remove_dir(
webview: &mut Webview,
path: PathBuf,
options: Option<DirOperationOptions>,
callback: String,
@@ -126,8 +126,8 @@ pub fn remove_dir<T: 'static>(
/// Removes a file
#[cfg(remove_file)]
pub fn remove_file<T: 'static>(
webview: &mut WebView<'_, T>,
pub fn remove_file(
webview: &mut Webview,
path: PathBuf,
options: Option<FileOperationOptions>,
callback: String,
@@ -146,8 +146,8 @@ pub fn remove_file<T: 'static>(
/// Renames a file.
#[cfg(rename_file)]
pub fn rename_file<T: 'static>(
webview: &mut WebView<'_, T>,
pub fn rename_file(
webview: &mut Webview,
old_path: PathBuf,
new_path: PathBuf,
options: Option<FileOperationOptions>,
@@ -173,8 +173,8 @@ pub fn rename_file<T: 'static>(
/// Writes a text file.
#[cfg(write_file)]
pub fn write_file<T: 'static>(
webview: &mut WebView<'_, T>,
pub fn write_file(
webview: &mut Webview,
path: PathBuf,
contents: String,
options: Option<FileOperationOptions>,
@@ -195,8 +195,8 @@ pub fn write_file<T: 'static>(
/// Writes a binary file.
#[cfg(write_binary_file)]
pub fn write_binary_file<T: 'static>(
webview: &mut WebView<'_, T>,
pub fn write_binary_file(
webview: &mut Webview,
path: PathBuf,
contents: String,
options: Option<FileOperationOptions>,
@@ -221,8 +221,8 @@ pub fn write_binary_file<T: 'static>(
/// Reads a text file.
#[cfg(read_text_file)]
pub fn read_text_file<T: 'static>(
webview: &mut WebView<'_, T>,
pub fn read_text_file(
webview: &mut Webview,
path: PathBuf,
options: Option<FileOperationOptions>,
callback: String,
@@ -238,8 +238,8 @@ pub fn read_text_file<T: 'static>(
/// Reads a binary file.
#[cfg(read_binary_file)]
pub fn read_binary_file<T: 'static>(
webview: &mut WebView<'_, T>,
pub fn read_binary_file(
webview: &mut Webview,
path: PathBuf,
options: Option<FileOperationOptions>,
callback: String,

View File

@@ -1,9 +1,9 @@
use tauri_api::http::{make_request as request, HttpRequestOptions};
use web_view::WebView;
use webview_rust_sys::Webview;
/// Makes an HTTP request and resolves the response to the webview
pub fn make_request<T: 'static>(
webview: &mut WebView<'_, T>,
pub fn make_request(
webview: &mut Webview,
options: HttpRequestOptions,
callback: String,
error: String,

View File

@@ -1,36 +0,0 @@
pub fn init() -> crate::Result<String> {
#[cfg(not(event))]
return Ok(String::from(""));
#[cfg(event)]
return Ok(format!(
"
window['{queue}'] = [];
window['{fn}'] = function (payload, salt, ignoreQueue) {{
const listeners = (window['{listeners}'] && window['{listeners}'][payload.type]) || []
if (!ignoreQueue && listeners.length === 0) {{
window['{queue}'].push({{
payload: payload,
salt: salt
}})
}}
if (listeners.length > 0) {{
window.__TAURI__.promisified({{
cmd: 'validateSalt',
salt: salt
}}).then(function () {{
for (let i = listeners.length - 1; i >= 0; i--) {{
const listener = listeners[i]
if (listener.once)
listeners.splice(i, 1)
listener.handler(payload)
}}
}})
}}
}}
",
fn = crate::event::emit_function_name(),
queue = crate::event::event_queue_object_name(),
listeners = crate::event::event_listeners_object_name()
));
}

View File

@@ -1,13 +1,8 @@
use super::cmd::NotificationOptions;
use serde_json::Value as JsonValue;
use web_view::WebView;
use webview_rust_sys::Webview;
pub fn send<T: 'static>(
webview: &mut WebView<'_, T>,
options: NotificationOptions,
callback: String,
error: String,
) {
pub fn send(webview: &mut Webview, options: NotificationOptions, callback: String, error: String) {
crate::execute_promise(
webview,
move || {
@@ -26,11 +21,7 @@ pub fn send<T: 'static>(
);
}
pub fn is_permission_granted<T: 'static>(
webview: &mut WebView<'_, T>,
callback: String,
error: String,
) {
pub fn is_permission_granted(webview: &mut Webview, callback: String, error: String) {
crate::execute_promise(
webview,
move || {
@@ -46,8 +37,8 @@ pub fn is_permission_granted<T: 'static>(
);
}
pub fn request_permission<T: 'static>(
webview: &mut WebView<'_, T>,
pub fn request_permission(
webview: &mut Webview,
callback: String,
error: String,
) -> crate::Result<()> {

View File

@@ -1,8 +1,8 @@
use web_view::WebView;
use webview_rust_sys::Webview;
/// Validates a salt.
pub fn validate<T: 'static>(
webview: &mut WebView<'_, T>,
pub fn validate(
webview: &mut Webview,
salt: String,
callback: String,
error: String,
@@ -13,6 +13,6 @@ pub fn validate<T: 'static>(
Err("Invalid salt")
};
let callback_string = crate::api::rpc::format_callback_result(response, callback, error)?;
webview.eval(callback_string.as_str())?;
webview.eval(callback_string.as_str());
Ok(())
}

View File

@@ -6,7 +6,7 @@ use lazy_static::lazy_static;
use once_cell::sync::Lazy;
use serde::Serialize;
use serde_json::Value as JsonValue;
use web_view::Handle;
use webview_rust_sys::WebviewMut;
/// An event handler.
struct EventHandler {
@@ -57,8 +57,8 @@ pub fn listen<F: FnMut(Option<String>) + Send + 'static>(id: impl Into<String>,
}
/// Emits an event to JS.
pub fn emit<T: 'static, S: Serialize>(
webview_handle: &Handle<T>,
pub fn emit<S: Serialize>(
webview: &mut WebviewMut,
event: impl AsRef<str> + Send + 'static,
payload: Option<S>,
) -> crate::Result<()> {
@@ -70,17 +70,15 @@ pub fn emit<T: 'static, S: Serialize>(
JsonValue::Null
};
webview_handle
.dispatch(move |_webview| {
_webview.eval(&format!(
"window['{}']({{type: '{}', payload: {}}}, '{}')",
emit_function_name(),
event.as_ref(),
js_payload,
salt
))
})
.expect("Failed to dispatch JS from emit");
webview.dispatch(move |webview_ref| {
webview_ref.eval(&format!(
"window['{}']({{type: '{}', payload: {}}}, '{}')",
emit_function_name(),
event.as_ref(),
js_payload,
salt
))
})?;
Ok(())
}

View File

@@ -39,8 +39,7 @@ mod salt;
pub use anyhow::Result;
pub use app::*;
pub use tauri_api as api;
pub use web_view::Handle;
pub use web_view::WebView;
pub use webview_rust_sys::{Webview, WebviewMut};
use std::process::Stdio;
@@ -61,20 +60,15 @@ pub fn spawn<F: FnOnce() -> () + Send + 'static>(task: F) {
/// Synchronously executes the given task
/// and evaluates its Result to the JS promise described by the `callback` and `error` function names.
pub fn execute_promise_sync<
T: 'static,
R: Serialize,
F: FnOnce() -> crate::Result<R> + Send + 'static,
>(
webview: &mut WebView<'_, T>,
pub fn execute_promise_sync<R: Serialize, F: FnOnce() -> crate::Result<R> + Send + 'static>(
webview: &mut Webview,
task: F,
callback: String,
error: String,
) -> crate::Result<()> {
let handle = webview.handle();
let callback_string =
format_callback_result(task().map_err(|err| err.to_string()), callback, error)?;
handle.dispatch(move |_webview| _webview.eval(callback_string.as_str()))?;
webview.dispatch(move |w| w.eval(callback_string.as_str()));
Ok(())
}
@@ -83,17 +77,13 @@ pub fn execute_promise_sync<
///
/// If the Result `is_ok()`, the callback will be the `success_callback` function name and the argument will be the Ok value.
/// If the Result `is_err()`, the callback will be the `error_callback` function name and the argument will be the Err value.
pub fn execute_promise<
T: 'static,
R: Serialize,
F: FnOnce() -> crate::Result<R> + Send + 'static,
>(
webview: &mut WebView<'_, T>,
pub fn execute_promise<R: Serialize, F: FnOnce() -> crate::Result<R> + Send + 'static>(
webview: &mut Webview,
task: F,
success_callback: String,
error_callback: String,
) {
let handle = webview.handle();
let mut webview = webview.as_mut();
POOL.with(|thread| {
thread.execute(move || {
let callback_string = match format_callback_result(
@@ -104,16 +94,16 @@ pub fn execute_promise<
Ok(callback_string) => callback_string,
Err(e) => format_callback(error_callback, e.to_string()),
};
handle
.dispatch(move |_webview| _webview.eval(callback_string.as_str()))
.expect("Failed to dispatch promise callback")
webview
.dispatch(move |webview_ref| webview_ref.eval(callback_string.as_str()))
.expect("Failed to dispatch promise callback");
});
});
}
/// Calls the given command and evaluates its output to the JS promise described by the `callback` and `error` function names.
pub fn call<T: 'static>(
webview: &mut WebView<'_, T>,
pub fn call(
webview: &mut Webview,
command: String,
args: Vec<String>,
callback: String,
@@ -128,11 +118,10 @@ pub fn call<T: 'static>(
}
/// Closes the splashscreen.
pub fn close_splashscreen<T: 'static>(webview_handle: &Handle<T>) -> crate::Result<()> {
webview_handle.dispatch(|webview| {
// send a signal to the runner so it knows that it should redirect to the main app content
webview.eval(r#"window.external.invoke(JSON.stringify({ cmd: "closeSplashscreen" }))"#)
})?;
pub fn close_splashscreen(webview: &mut Webview) -> crate::Result<()> {
// send a signal to the runner so it knows that it should redirect to the main app content
webview.eval(r#"window.__TAURI_INVOKE_HANDLER__({ cmd: "closeSplashscreen" })"#);
Ok(())
}

View File

@@ -1,19 +1,19 @@
use std::sync::{Arc, Mutex};
use web_view::WebView;
use webview_rust_sys::Webview;
/// The plugin interface.
pub trait Plugin {
/// Callback invoked when the webview is created.
#[allow(unused_variables)]
fn created(&self, webview: &mut WebView<'_, ()>) {}
fn created(&self, webview: &mut Webview) {}
/// Callback invoked when the webview is ready.
#[allow(unused_variables)]
fn ready(&self, webview: &mut WebView<'_, ()>) {}
fn ready(&self, webview: &mut Webview) {}
/// Add invoke_handler API extension commands.
#[allow(unused_variables)]
fn extend_api(&self, webview: &mut WebView<'_, ()>, payload: &str) -> Result<bool, String> {
fn extend_api(&self, webview: &mut Webview, payload: &str) -> Result<bool, String> {
Err("unknown variant".to_string())
}
}
@@ -37,19 +37,19 @@ fn run_plugin<T: FnMut(&Box<dyn Plugin>)>(mut callback: T) {
});
}
pub(crate) fn created(webview: &mut WebView<'_, ()>) {
pub(crate) fn created(webview: &mut Webview) {
run_plugin(|ext| {
ext.created(webview);
});
}
pub(crate) fn ready(webview: &mut WebView<'_, ()>) {
pub(crate) fn ready(webview: &mut Webview) {
run_plugin(|ext| {
ext.ready(webview);
});
}
pub(crate) fn extend_api(webview: &mut WebView<'_, ()>, arg: &str) -> Result<bool, String> {
pub(crate) fn extend_api(webview: &mut Webview, arg: &str) -> Result<bool, String> {
PLUGINS.with(|plugins| {
let exts = plugins.lock().unwrap();
for ext in exts.iter() {

View File

@@ -19,7 +19,9 @@
},
"security": {
"csp": "default-src blob: data: filesystem: ws: http: https: 'unsafe-eval' 'unsafe-inline'"
},
"inliner": {
"active": true
}
},
"edge": true
}
}