#![allow(deprecated)]
extern crate boxfnonce;
extern crate tinyfiledialogs as tfd;
extern crate urlencoding;
extern crate webview_sys as ffi;
mod color;
mod dialog;
mod error;
mod escape;
pub use color::Color;
pub use dialog::DialogBuilder;
pub use error::{CustomError, Error, WVResult};
pub use escape::escape;
use boxfnonce::SendBoxFnOnce;
use ffi::*;
use std::{
ffi::{CStr, CString},
marker::PhantomData,
mem,
os::raw::*,
sync::{Arc, RwLock, Weak},
};
use urlencoding::encode;
const CSS_INJECT_FUNCTION: &str = "(function(e){var \
t=document.createElement('style'),d=document.head||document.\
getElementsByTagName('head')[0];t.setAttribute('type','text/\
css'),t.styleSheet?t.styleSheet.cssText=e:t.appendChild(document.\
createTextNode(e)),d.appendChild(t)})";
#[derive(Debug)]
pub enum Content<T> {
Url(T),
Html(T),
}
pub struct WebViewBuilder<'a, T: 'a, I, C> {
pub title: &'a str,
pub content: Option<Content<C>>,
pub width: i32,
pub height: i32,
pub resizable: bool,
pub debug: bool,
pub invoke_handler: Option<I>,
pub user_data: Option<T>,
pub frameless: bool,
}
impl<'a, T: 'a, I, C> Default for WebViewBuilder<'a, T, I, C>
where
I: FnMut(&mut WebView<T>, &str) -> WVResult + 'a,
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,
invoke_handler: None,
user_data: None,
frameless: false,
}
}
}
impl<'a, T: 'a, I, C> WebViewBuilder<'a, T, I, C>
where
I: FnMut(&mut WebView<T>, &str) -> WVResult + 'a,
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 frameless(mut self, frameless: bool) -> Self {
self.frameless = frameless;
self
}
pub fn invoke_handler(mut self, invoke_handler: I) -> Self {
self.invoke_handler = Some(invoke_handler);
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 invoke_handler = require_field!(invoke_handler);
WebView::new(
&title,
&url,
self.width,
self.height,
self.resizable,
self.debug,
self.frameless,
user_data,
invoke_handler,
)
}
pub fn run(self) -> WVResult<T> {
self.build()?.run()
}
}
pub fn builder<'a, T, I, C>() -> WebViewBuilder<'a, T, I, C>
where
I: FnMut(&mut WebView<T>, &str) -> WVResult + 'a,
C: AsRef<str>,
{
WebViewBuilder::new()
}
struct UserData<'a, T> {
inner: T,
live: Arc<RwLock<()>>,
invoke_handler: Box<dyn FnMut(&mut WebView<T>, &str) -> WVResult + 'a>,
result: WVResult,
}
#[derive(Debug)]
pub struct WebView<'a, T: 'a> {
inner: Option<*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,
frameless: 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 = webview_new(
title.as_ptr(),
url.as_ptr(),
width,
height,
resizable as _,
debug as _,
frameless 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: Some(inner),
_phantom: PhantomData,
}
}
pub fn handle(&self) -> Handle<T> {
Handle {
inner: self.inner.unwrap(),
live: Arc::downgrade(&self.user_data_wrapper().live),
_phantom: PhantomData,
}
}
fn user_data_wrapper_ptr(&self) -> *mut UserData<'a, T> {
unsafe { webview_get_user_data(self.inner.unwrap()) 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
}
#[deprecated(note = "Please use exit instead")]
pub fn terminate(&mut self) {
self.exit();
}
pub fn exit(&mut self) {
unsafe { webview_exit(self.inner.unwrap()) }
}
pub fn eval(&mut self, js: &str) -> WVResult {
let js = CString::new(js)?;
let ret = unsafe { webview_eval(self.inner.unwrap(), js.as_ptr()) };
if ret != 0 {
Err(Error::JsEvaluation)
} else {
Ok(())
}
}
pub fn inject_css(&mut self, css: &str) -> WVResult {
let inject_func = format!("{}({})", CSS_INJECT_FUNCTION, escape(css));
self.eval(&inject_func).map_err(|_| Error::CssInjection)
}
pub fn set_color<C: Into<Color>>(&mut self, color: C) {
let color = color.into();
unsafe { webview_set_color(self.inner.unwrap(), 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.unwrap(), title.as_ptr()) }
Ok(())
}
pub fn set_fullscreen(&mut self, fullscreen: bool) {
unsafe { webview_set_fullscreen(self.inner.unwrap(), fullscreen as _) };
}
#[deprecated(
note = "Please use crates like 'tinyfiledialogs' for dialog handling, see example in examples/dialog.rs"
)]
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.unwrap(), 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.unwrap());
webview_free(self.inner.unwrap());
let user_data = *Box::from_raw(user_data_ptr);
std::mem::drop(lock);
user_data.inner
}
}
impl<'a, T> Drop for WebView<'a, T> {
fn drop(&mut self) {
if self.inner.is_some() {
unsafe {
self._into_inner();
}
self.inner = None;
}
}
}
pub struct Handle<T> {
inner: *mut CWebView,
live: Weak<RwLock<()>>,
_phantom: PhantomData<T>,
}
impl<T> Clone for Handle<T> {
fn clone(&self) -> Self {
Handle {
inner: self.inner,
live: self.live.clone(),
_phantom: PhantomData,
}
}
}
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> {}
extern "C" fn ffi_dispatch_handler<T>(webview: *mut CWebView, arg: *mut c_void) {
unsafe {
let mut handle = 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;
handle.inner = None;
}
}
extern "C" 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 = WebView::<T>::from_ptr(webview);
let result = ((*handle.user_data_wrapper_ptr()).invoke_handler)(&mut handle, &arg);
handle.user_data_wrapper_mut().result = result;
handle.inner = None;
}
}