@Michael-F-Bryan Thanks, I added the Send
constraint for dispatched closures.
I'm still not sure if all my lifetimes are correct, I would really appreciate it if you could tell me how to make the lifetimes better here: https://github.com/Boscop/web-view/blob/master/src/lib.rs
One thing that's bad is that when calling the run()
function, the ext_cb
(of type F: FnMut(&mut WebView<'a, T>, &str, &mut T) + 'a
) aka "external invoke callback" (which will be called from js as window.external.invoke()
can't be assigned to a var before calling run()
, it has to be passed directly to run()
, otherwise the compiler complains.
E.g. in the minimal example..
fn main() {
let size = (800, 600);
let resizable = true;
let debug = true;
let init_cb = |_| {};
let userdata = ();
run(
"Minimal webview example",
"https://en.m.wikipedia.org/wiki/Main_Page",
Some(size),
resizable,
debug,
init_cb,
/* frontend_cb: */ |_, _, _| {},
userdata
);
}
If I try to change it so that the frontend_cb
is assigned to a var before run()
and then pass that var (like with the other args, for naming/documentation purposes), I get this error:
error[E0631]: type mismatch in closure arguments
--> examples\minimal.rs:14:2
|
12 | let frontend_cb = |_, _, _| {};
| --------- found signature of `fn(_, _, _) -> _`
13 | let userdata = ();
14 | run(
| ^^^ expected signature of `for<'r, 's, 't0> fn(&'r mut web_view::WebVie
w<'_, _>, &'s str, &'t0 mut _) -> _`
|
= note: required by `web_view::run`
error[E0271]: type mismatch resolving `for<'r, 's, 't0> <[closure@examples\minim
al.rs:12:20: 12:32] as std::ops::FnOnce<(&'r mut web_view::WebView<'_, _>, &'s s
tr, &'t0 mut _)>>::Output == ()`
--> examples\minimal.rs:14:2
|
14 | run(
| ^^^ expected bound lifetime parameter, found concrete lifetime
|
= note: required by `web_view::run`
error: aborting due to 2 previous errors
How can I make this possible?
@ivanceras Yes, that's exactly the use case that this lib is supposed to fit: Standalone Desktop applications with a web frontend. Your JS code calls Rust through window.external.invoke()
, in the todo
example you can see how it uses serde_json
to auto deserialize a cmd sent from js into the Cmd
enum:
let (tasks, _) = run("Rust Todo App", &url, Some(size), resizable, debug, init_cb, |webview, arg, tasks: &mut Vec<Task>| {
use Cmd::*;
match serde_json::from_str(arg).unwrap() {
init => (),
log { text } => println!("{}", text),
addTask { name } => tasks.push(Task { name, done: false }),
markTask { index, done } => tasks[index].done = done,
clearDoneTasks => tasks.retain(|t| !t.done),
}
render(webview, tasks);
}, userdata);
// ...
#[derive(Deserialize)]
#[serde(tag = "cmd")]
pub enum Cmd {
init,
log { text: String },
addTask { name: String },
markTask { index: usize, done: bool },
clearDoneTasks,
}
Here is the JS side of it.
For the other direction: From the backend you can eval arbitrary JS in the frontend.
Btw, you don't have to do the backend/frontend communication via the two-way JS bindings. You can also do it by spawning a web server with hyper/iron/rocket/nickel or whatever, listening on an ephemeral port and exposing a normal REST API. Or even using server-sent events or websockets to push unrequested updates to the frontend. This is the easiest way to transition a previously server-based app to standalone, because you don't have to change your frontend code.
Check out this part of the original webview readme.
Btw, the todo-purescript
Rust example (the one whose screenshot is in the Readme) is 300kb (keep in mind that the rust compiler by default links statically to the rust runtime, unlike C++), and 153kb of that is the uncompressed included bundle.html
(the app.js is inlined into bundle.html by the build script and then the rust code embeds bundle.html
as a string (uncompressed) using include_str!()
).