extern crate boxfnonce;
extern crate urlencoding;
extern crate webview_sys as ffi;
mod color;
mod dialog;
mod error;
mod escape;
mod routing;
pub use color::Color;
pub use dialog::DialogBuilder;
pub use error::{CustomError, Error, WVResult};
pub use escape::escape;
pub use routing::Router;
pub use routing::messages::{Request, Response};
pub use serde_json::{json, Value};
use boxfnonce::SendBoxFnOnce;
use ffi::*;
use std::{
ffi::{CStr, CString},
marker::PhantomData,
mem,
os::raw::*,
sync::{Arc, RwLock, Weak},
};
use urlencoding::encode;
#[derive(Debug)]
pub enum Content<T> {
Url(T),
Html(T),
}
pub struct WebViewBuilder<'a, T: 'a, C> {
pub title: &'a str,
pub content: Option<Content<C>>,
pub width: i32,
pub height: i32,
pub resizable: bool,
pub debug: bool,
pub router: Option<Router<'a, T>>,
pub user_data: Option<T>,
}
impl<'a, T: 'a, C> Default for WebViewBuilder<'a, T, C>
where
C: AsRef<str>,
{
fn default() -> Self {
#[cfg(debug_assertions)]
let debug = true;
#[cfg(not(debug_assertions))]
let debug = false;
WebViewBuilder {
title: "Application",
content: None,
width: 800,
height: 600,
resizable: true,
debug,
router: None,
user_data: None,
}
}
}
impl<'a, T: 'a, C> WebViewBuilder<'a, T, C>
where
C: AsRef<str>,
{
pub fn new() -> Self {
WebViewBuilder::default()
}
pub fn title(mut self, title: &'a str) -> Self {
self.title = title;
self
}
pub fn content(mut self, content: Content<C>) -> Self {
self.content = Some(content);
self
}
pub fn size(mut self, width: i32, height: i32) -> Self {
self.width = width;
self.height = height;
self
}
pub fn resizable(mut self, resizable: bool) -> Self {
self.resizable = resizable;
self
}
pub fn debug(mut self, debug: bool) -> Self {
self.debug = debug;
self
}
pub fn router(mut self, router: Router<'a, T>) -> Self {
self.router = Some(router);
self
}
pub fn user_data(mut self, user_data: T) -> Self {
self.user_data = Some(user_data);
self
}
pub fn build(self) -> WVResult<WebView<'a, T>> {
macro_rules! require_field {
($name:ident) => {
self.$name
.ok_or_else(|| Error::UninitializedField(stringify!($name)))?
};
}
let title = CString::new(self.title)?;
let content = require_field!(content);
let url = match content {
Content::Url(url) => CString::new(url.as_ref())?,
Content::Html(html) => {
CString::new(format!("data:text/html,{}", encode(html.as_ref())))?
}
};
let user_data = require_field!(user_data);
let router = require_field!(router);
let invoke_handler = move |webview: &mut WebView<T>, arg: &str| -> WVResult {
router.resolve(webview, arg)
};
let mut webview = WebView::new(
&title,
&url,
self.width,
self.height,
self.resizable,
self.debug,
user_data,
invoke_handler,
)?;
webview.eval(include_str!("../dist/backend.js"))?;
Ok(webview)
}
pub fn run(self) -> WVResult<T> {
self.build()?.run()
}
}
pub fn builder<'a, T, C>() -> WebViewBuilder<'a, T, C>
where
C: AsRef<str>,
{
WebViewBuilder::new()
}
struct UserData<'a, T> {
inner: T,
live: Arc<RwLock<()>>,
invoke_handler: Box<FnMut(&mut WebView<T>, &str) -> WVResult + 'a>,
result: WVResult,
}
#[derive(Debug)]
pub struct WebView<'a, T: 'a> {
inner: *mut CWebView,
_phantom: PhantomData<&'a mut T>,
}
impl<'a, T> WebView<'a, T> {
#![cfg_attr(feature = "cargo-clippy", allow(clippy::too_many_arguments))]
fn new<I>(
title: &CStr,
url: &CStr,
width: i32,
height: i32,
resizable: bool,
debug: bool,
user_data: T,
invoke_handler: I,
) -> WVResult<WebView<'a, T>>
where
I: FnMut(&mut WebView<T>, &str) -> WVResult + 'a,
{
let user_data = Box::new(UserData {
inner: user_data,
live: Arc::new(RwLock::new(())),
invoke_handler: Box::new(invoke_handler),
result: Ok(()),
});
let user_data_ptr = Box::into_raw(user_data);
unsafe {
let inner = wrapper_webview_new(
title.as_ptr(),
url.as_ptr(),
width,
height,
resizable as _,
debug as _,
Some(ffi_invoke_handler::<T>),
user_data_ptr as _,
);
if inner.is_null() {
Box::<UserData<T>>::from_raw(user_data_ptr);
Err(Error::Initialization)
} else {
Ok(WebView::from_ptr(inner))
}
}
}
unsafe fn from_ptr(inner: *mut CWebView) -> WebView<'a, T> {
WebView {
inner,
_phantom: PhantomData,
}
}
pub fn handle(&self) -> Handle<T> {
Handle {
inner: self.inner,
live: Arc::downgrade(&self.user_data_wrapper().live),
_phantom: PhantomData,
}
}
fn user_data_wrapper_ptr(&self) -> *mut UserData<'a, T> {
unsafe { wrapper_webview_get_userdata(self.inner) as _ }
}
fn user_data_wrapper(&self) -> &UserData<'a, T> {
unsafe { &(*self.user_data_wrapper_ptr()) }
}
fn user_data_wrapper_mut(&mut self) -> &mut UserData<'a, T> {
unsafe { &mut (*self.user_data_wrapper_ptr()) }
}
pub fn user_data(&self) -> &T {
&self.user_data_wrapper().inner
}
pub fn user_data_mut(&mut self) -> &mut T {
&mut self.user_data_wrapper_mut().inner
}
pub fn terminate(&mut self) {
unsafe { webview_terminate(self.inner) }
}
pub fn eval(&mut self, js: &str) -> WVResult {
let js = CString::new(js)?;
let ret = unsafe { webview_eval(self.inner, js.as_ptr()) };
if ret != 0 {
Err(Error::JsEvaluation)
} else {
Ok(())
}
}
pub fn inject_css(&mut self, css: &str) -> WVResult {
let css = CString::new(css)?;
let ret = unsafe { webview_inject_css(self.inner, css.as_ptr()) };
if ret != 0 {
Err(Error::CssInjection)
} else {
Ok(())
}
}
pub fn set_color<C: Into<Color>>(&mut self, color: C) {
let color = color.into();
unsafe { webview_set_color(self.inner, color.r, color.g, color.b, color.a) }
}
pub fn set_title(&mut self, title: &str) -> WVResult {
let title = CString::new(title)?;
unsafe { webview_set_title(self.inner, title.as_ptr()) }
Ok(())
}
pub fn set_fullscreen(&mut self, fullscreen: bool) {
unsafe { webview_set_fullscreen(self.inner, fullscreen as _) };
}
pub fn dialog<'b>(&'b mut self) -> DialogBuilder<'a, 'b, T> {
DialogBuilder::new(self)
}
pub fn step(&mut self) -> Option<WVResult> {
unsafe {
match webview_loop(self.inner, 1) {
0 => {
let closure_result = &mut self.user_data_wrapper_mut().result;
match closure_result {
Ok(_) => Some(Ok(())),
e => Some(mem::replace(e, Ok(()))),
}
}
_ => None,
}
}
}
pub fn run(mut self) -> WVResult<T> {
loop {
match self.step() {
Some(Ok(_)) => (),
Some(e) => e?,
None => return Ok(self.into_inner()),
}
}
}
pub fn into_inner(mut self) -> T {
unsafe {
let user_data = self._into_inner();
mem::forget(self);
user_data
}
}
unsafe fn _into_inner(&mut self) -> T {
let _lock = self
.user_data_wrapper()
.live
.write()
.expect("A dispatch channel thread panicked while holding mutex to WebView.");
let user_data_ptr = self.user_data_wrapper_ptr();
webview_exit(self.inner);
wrapper_webview_free(self.inner);
let user_data = *Box::from_raw(user_data_ptr);
user_data.inner
}
}
impl<'a, T> Drop for WebView<'a, T> {
fn drop(&mut self) {
unsafe {
self._into_inner();
}
}
}
pub struct Handle<T> {
inner: *mut CWebView,
live: Weak<RwLock<()>>,
_phantom: PhantomData<T>,
}
impl<T> Handle<T> {
pub fn dispatch<F>(&self, f: F) -> WVResult
where
F: FnOnce(&mut WebView<T>) -> WVResult + Send + 'static,
{
let mutex = self.live.upgrade().ok_or(Error::Dispatch)?;
let closure = Box::new(SendBoxFnOnce::new(f));
let _lock = mutex.read().map_err(|_| Error::Dispatch)?;
unsafe {
webview_dispatch(
self.inner,
Some(ffi_dispatch_handler::<T> as _),
Box::into_raw(closure) as _,
)
}
Ok(())
}
}
unsafe impl<T> Send for Handle<T> {}
unsafe impl<T> Sync for Handle<T> {}
fn read_str(s: &[u8]) -> String {
let end = s.iter().position(|&b| b == 0).map_or(0, |i| i + 1);
match CStr::from_bytes_with_nul(&s[..end]) {
Ok(s) => s.to_string_lossy().into_owned(),
Err(_) => "".to_string(),
}
}
extern "system" fn ffi_dispatch_handler<T>(webview: *mut CWebView, arg: *mut c_void) {
unsafe {
let mut handle = mem::ManuallyDrop::new(WebView::<T>::from_ptr(webview));
let result = {
let callback =
Box::<SendBoxFnOnce<'static, (&mut WebView<T>,), WVResult>>::from_raw(arg as _);
callback.call(&mut handle)
};
handle.user_data_wrapper_mut().result = result;
}
}
extern "system" fn ffi_invoke_handler<T>(webview: *mut CWebView, arg: *const c_char) {
unsafe {
let arg = CStr::from_ptr(arg).to_string_lossy().to_string();
let mut handle = mem::ManuallyDrop::new(WebView::<T>::from_ptr(webview));
let result = ((*handle.user_data_wrapper_ptr()).invoke_handler)(&mut *handle, &arg);
handle.user_data_wrapper_mut().result = result;
}
}