1#![allow(deprecated)] extern crate boxfnonce;
27extern crate tinyfiledialogs as tfd;
28extern crate urlencoding;
29extern crate webview_sys as ffi;
30
31mod color;
32mod dialog;
33mod error;
34mod escape;
35
36pub use color::Color;
37pub use dialog::DialogBuilder;
38pub use error::{CustomError, Error, WVResult};
39pub use escape::escape;
40
41use boxfnonce::SendBoxFnOnce;
42use ffi::*;
43use std::{
44 ffi::{CStr, CString},
45 marker::PhantomData,
46 mem,
47 os::raw::*,
48 sync::{Arc, RwLock, Weak},
49};
50use urlencoding::encode;
51
52const CSS_INJECT_FUNCTION: &str = "(function(e){var \
58 t=document.createElement('style'),d=document.head||document.\
59 getElementsByTagName('head')[0];t.setAttribute('type','text/\
60 css'),t.styleSheet?t.styleSheet.cssText=e:t.appendChild(document.\
61 createTextNode(e)),d.appendChild(t)})";
62
63#[derive(Debug)]
72pub enum Content<T> {
73 Url(T),
74 Html(T),
75}
76
77pub struct WebViewBuilder<'a, T: 'a, I, C> {
104 pub title: &'a str,
105 pub content: Option<Content<C>>,
106 pub width: i32,
107 pub height: i32,
108 pub resizable: bool,
109 pub debug: bool,
110 pub invoke_handler: Option<I>,
111 pub user_data: Option<T>,
112 pub frameless: bool,
113}
114
115impl<'a, T: 'a, I, C> Default for WebViewBuilder<'a, T, I, C>
116where
117 I: FnMut(&mut WebView<T>, &str) -> WVResult + 'a,
118 C: AsRef<str>,
119{
120 fn default() -> Self {
121 #[cfg(debug_assertions)]
122 let debug = true;
123 #[cfg(not(debug_assertions))]
124 let debug = false;
125
126 WebViewBuilder {
127 title: "Application",
128 content: None,
129 width: 800,
130 height: 600,
131 resizable: true,
132 debug,
133 invoke_handler: None,
134 user_data: None,
135 frameless: false,
136 }
137 }
138}
139
140impl<'a, T: 'a, I, C> WebViewBuilder<'a, T, I, C>
141where
142 I: FnMut(&mut WebView<T>, &str) -> WVResult + 'a,
143 C: AsRef<str>,
144{
145 pub fn new() -> Self {
149 WebViewBuilder::default()
150 }
151
152 pub fn title(mut self, title: &'a str) -> Self {
156 self.title = title;
157 self
158 }
159
160 pub fn content(mut self, content: Content<C>) -> Self {
162 self.content = Some(content);
163 self
164 }
165
166 pub fn size(mut self, width: i32, height: i32) -> Self {
170 self.width = width;
171 self.height = height;
172 self
173 }
174
175 pub fn resizable(mut self, resizable: bool) -> Self {
179 self.resizable = resizable;
180 self
181 }
182
183 pub fn debug(mut self, debug: bool) -> Self {
187 self.debug = debug;
188 self
189 }
190 pub fn frameless(mut self, frameless: bool) -> Self {
194 self.frameless = frameless;
195 self
196 }
197
198 pub fn invoke_handler(mut self, invoke_handler: I) -> Self {
207 self.invoke_handler = Some(invoke_handler);
208 self
209 }
210
211 pub fn user_data(mut self, user_data: T) -> Self {
214 self.user_data = Some(user_data);
215 self
216 }
217
218 pub fn build(self) -> WVResult<WebView<'a, T>> {
220 macro_rules! require_field {
221 ($name:ident) => {
222 self.$name
223 .ok_or_else(|| Error::UninitializedField(stringify!($name)))?
224 };
225 }
226
227 let title = CString::new(self.title)?;
228 let content = require_field!(content);
229 let url = match content {
230 Content::Url(url) => CString::new(url.as_ref())?,
231 Content::Html(html) => {
232 CString::new(format!("data:text/html,{}", encode(html.as_ref())))?
233 }
234 };
235 let user_data = require_field!(user_data);
236 let invoke_handler = require_field!(invoke_handler);
237
238 WebView::new(
239 &title,
240 &url,
241 self.width,
242 self.height,
243 self.resizable,
244 self.debug,
245 self.frameless,
246 user_data,
247 invoke_handler,
248 )
249 }
250
251 pub fn run(self) -> WVResult<T> {
255 self.build()?.run()
256 }
257}
258
259pub fn builder<'a, T, I, C>() -> WebViewBuilder<'a, T, I, C>
266where
267 I: FnMut(&mut WebView<T>, &str) -> WVResult + 'a,
268 C: AsRef<str>,
269{
270 WebViewBuilder::new()
271}
272
273struct UserData<'a, T> {
274 inner: T,
275 live: Arc<RwLock<()>>,
276 invoke_handler: Box<dyn FnMut(&mut WebView<T>, &str) -> WVResult + 'a>,
277 result: WVResult,
278}
279
280#[derive(Debug)]
286pub struct WebView<'a, T: 'a> {
287 inner: Option<*mut CWebView>,
288 _phantom: PhantomData<&'a mut T>,
289}
290
291impl<'a, T> WebView<'a, T> {
292 #![cfg_attr(feature = "cargo-clippy", allow(clippy::too_many_arguments))]
293 fn new<I>(
294 title: &CStr,
295 url: &CStr,
296 width: i32,
297 height: i32,
298 resizable: bool,
299 debug: bool,
300 frameless: bool,
301 user_data: T,
302 invoke_handler: I,
303 ) -> WVResult<WebView<'a, T>>
304 where
305 I: FnMut(&mut WebView<T>, &str) -> WVResult + 'a,
306 {
307 let user_data = Box::new(UserData {
308 inner: user_data,
309 live: Arc::new(RwLock::new(())),
310 invoke_handler: Box::new(invoke_handler),
311 result: Ok(()),
312 });
313 let user_data_ptr = Box::into_raw(user_data);
314
315 unsafe {
316 let inner = webview_new(
317 title.as_ptr(),
318 url.as_ptr(),
319 width,
320 height,
321 resizable as _,
322 debug as _,
323 frameless as _,
324 Some(ffi_invoke_handler::<T>),
325 user_data_ptr as _,
326 );
327
328 if inner.is_null() {
329 Box::<UserData<T>>::from_raw(user_data_ptr);
330 Err(Error::Initialization)
331 } else {
332 Ok(WebView::from_ptr(inner))
333 }
334 }
335 }
336
337 unsafe fn from_ptr(inner: *mut CWebView) -> WebView<'a, T> {
338 WebView {
339 inner: Some(inner),
340 _phantom: PhantomData,
341 }
342 }
343
344 pub fn handle(&self) -> Handle<T> {
348 Handle {
349 inner: self.inner.unwrap(),
350 live: Arc::downgrade(&self.user_data_wrapper().live),
351 _phantom: PhantomData,
352 }
353 }
354
355 fn user_data_wrapper_ptr(&self) -> *mut UserData<'a, T> {
356 unsafe { webview_get_user_data(self.inner.unwrap()) as _ }
357 }
358
359 fn user_data_wrapper(&self) -> &UserData<'a, T> {
360 unsafe { &(*self.user_data_wrapper_ptr()) }
361 }
362
363 fn user_data_wrapper_mut(&mut self) -> &mut UserData<'a, T> {
364 unsafe { &mut (*self.user_data_wrapper_ptr()) }
365 }
366
367 pub fn user_data(&self) -> &T {
369 &self.user_data_wrapper().inner
370 }
371
372 pub fn user_data_mut(&mut self) -> &mut T {
374 &mut self.user_data_wrapper_mut().inner
375 }
376
377 #[deprecated(note = "Please use exit instead")]
378 pub fn terminate(&mut self) {
379 self.exit();
380 }
381
382 pub fn exit(&mut self) {
384 unsafe { webview_exit(self.inner.unwrap()) }
385 }
386
387 pub fn eval(&mut self, js: &str) -> WVResult {
389 let js = CString::new(js)?;
390 let ret = unsafe { webview_eval(self.inner.unwrap(), js.as_ptr()) };
391 if ret != 0 {
392 Err(Error::JsEvaluation)
393 } else {
394 Ok(())
395 }
396 }
397
398 pub fn inject_css(&mut self, css: &str) -> WVResult {
400 let inject_func = format!("{}({})", CSS_INJECT_FUNCTION, escape(css));
401 self.eval(&inject_func).map_err(|_| Error::CssInjection)
402 }
403
404 pub fn set_color<C: Into<Color>>(&mut self, color: C) {
418 let color = color.into();
419 unsafe { webview_set_color(self.inner.unwrap(), color.r, color.g, color.b, color.a) }
420 }
421
422 pub fn set_title(&mut self, title: &str) -> WVResult {
430 let title = CString::new(title)?;
431 unsafe { webview_set_title(self.inner.unwrap(), title.as_ptr()) }
432 Ok(())
433 }
434
435 pub fn set_fullscreen(&mut self, fullscreen: bool) {
437 unsafe { webview_set_fullscreen(self.inner.unwrap(), fullscreen as _) };
438 }
439
440 #[deprecated(
442 note = "Please use crates like 'tinyfiledialogs' for dialog handling, see example in examples/dialog.rs"
443 )]
444 pub fn dialog<'b>(&'b mut self) -> DialogBuilder<'a, 'b, T> {
445 DialogBuilder::new(self)
446 }
447
448 pub fn step(&mut self) -> Option<WVResult> {
450 unsafe {
451 match webview_loop(self.inner.unwrap(), 1) {
452 0 => {
453 let closure_result = &mut self.user_data_wrapper_mut().result;
454 match closure_result {
455 Ok(_) => Some(Ok(())),
456 e => Some(mem::replace(e, Ok(()))),
457 }
458 }
459 _ => None,
460 }
461 }
462 }
463
464 pub fn run(mut self) -> WVResult<T> {
466 loop {
467 match self.step() {
468 Some(Ok(_)) => (),
469 Some(e) => e?,
470 None => return Ok(self.into_inner()),
471 }
472 }
473 }
474
475 pub fn into_inner(mut self) -> T {
477 unsafe {
478 let user_data = self._into_inner();
479 mem::forget(self);
480 user_data
481 }
482 }
483
484 unsafe fn _into_inner(&mut self) -> T {
485 let lock = self
486 .user_data_wrapper()
487 .live
488 .write()
489 .expect("A dispatch channel thread panicked while holding mutex to WebView.");
490
491 let user_data_ptr = self.user_data_wrapper_ptr();
492 webview_exit(self.inner.unwrap());
493 webview_free(self.inner.unwrap());
494 let user_data = *Box::from_raw(user_data_ptr);
495 std::mem::drop(lock);
496 user_data.inner
497 }
498}
499
500impl<'a, T> Drop for WebView<'a, T> {
501 fn drop(&mut self) {
502 if self.inner.is_some() {
503 unsafe {
504 self._into_inner();
505 }
506 self.inner = None;
507 }
508 }
509}
510
511pub struct Handle<T> {
515 inner: *mut CWebView,
516 live: Weak<RwLock<()>>,
517 _phantom: PhantomData<T>,
518}
519
520impl<T> Clone for Handle<T> {
521 fn clone(&self) -> Self {
522 Handle {
523 inner: self.inner,
524 live: self.live.clone(),
525 _phantom: PhantomData,
526 }
527 }
528}
529
530impl<T> Handle<T> {
531 pub fn dispatch<F>(&self, f: F) -> WVResult
543 where
544 F: FnOnce(&mut WebView<T>) -> WVResult + Send + 'static,
545 {
546 let mutex = self.live.upgrade().ok_or(Error::Dispatch)?;
549 let closure = Box::new(SendBoxFnOnce::new(f));
550 let _lock = mutex.read().map_err(|_| Error::Dispatch)?;
551
552 unsafe {
554 webview_dispatch(
555 self.inner,
556 Some(ffi_dispatch_handler::<T> as _),
557 Box::into_raw(closure) as _,
558 )
559 }
560 Ok(())
561 }
562}
563
564unsafe impl<T> Send for Handle<T> {}
565unsafe impl<T> Sync for Handle<T> {}
566
567extern "C" fn ffi_dispatch_handler<T>(webview: *mut CWebView, arg: *mut c_void) {
568 unsafe {
569 let mut handle = WebView::<T>::from_ptr(webview);
570 let result = {
571 let callback =
572 Box::<SendBoxFnOnce<'static, (&mut WebView<T>,), WVResult>>::from_raw(arg as _);
573 callback.call(&mut handle)
574 };
575 handle.user_data_wrapper_mut().result = result;
576 handle.inner = None;
578 }
579}
580
581extern "C" fn ffi_invoke_handler<T>(webview: *mut CWebView, arg: *const c_char) {
582 unsafe {
583 let arg = CStr::from_ptr(arg).to_string_lossy().to_string();
584 let mut handle = WebView::<T>::from_ptr(webview);
585 let result = ((*handle.user_data_wrapper_ptr()).invoke_handler)(&mut handle, &arg);
586 handle.user_data_wrapper_mut().result = result;
587 handle.inner = None;
589 }
590}