better_web_view/
lib.rs

1//! [![Latest Version]][crates.io]
2//!
3//! [Latest Version]: https://img.shields.io/crates/v/better-web-view.svg
4//! [crates.io]: https://crates.io/crates/better-web-view
5//!
6//! The library is a [web-view](https://github.com/Boscop/web-view) modification and provides a better way 
7//! of communication between the Rust backend and the JavaScript frontend.
8//! 
9//! For usage info please check out [the example](https://github.com/Firebain/better-web-view/tree/master/examples) 
10//! and the [original repo](https://github.com/Boscop/web-view).
11extern crate boxfnonce;
12extern crate urlencoding;
13extern crate webview_sys as ffi;
14
15mod color;
16mod dialog;
17mod error;
18mod escape;
19mod routing;
20pub use color::Color;
21pub use dialog::DialogBuilder;
22pub use error::{CustomError, Error, WVResult};
23pub use escape::escape;
24pub use routing::Router;
25pub use routing::messages::{Request, Response};
26pub use serde_json::{json, Value};
27
28use boxfnonce::SendBoxFnOnce;
29use ffi::*;
30use std::{
31    ffi::{CStr, CString},
32    marker::PhantomData,
33    mem,
34    os::raw::*,
35    sync::{Arc, RwLock, Weak},
36};
37use urlencoding::encode;
38
39/// Content displayable inside a [`WebView`].
40///
41/// # Variants
42///
43/// - `Url` - Content to be fetched from a URL.
44/// - `Html` - A string containing literal HTML.
45///
46/// [`WebView`]: struct.WebView.html
47#[derive(Debug)]
48pub enum Content<T> {
49    Url(T),
50    Html(T),
51}
52
53/// Builder for constructing a [`WebView`] instance.
54///
55/// # Example
56///
57/// ```no_run
58/// extern crate web_view;
59///
60/// use web_view::*;
61///
62/// fn main() {
63///     WebViewBuilder::new()
64///         .title("Minimal webview example")
65///         .content(Content::Url("https://en.m.wikipedia.org/wiki/Main_Page"))
66///         .size(800, 600)
67///         .resizable(true)
68///         .debug(true)
69///         .user_data(())
70///         .router(Router::default())
71///         .build()
72///         .unwrap()
73///         .run()
74///         .unwrap();
75/// }
76/// ```
77///
78/// [`WebView`]: struct.WebView.html
79pub struct WebViewBuilder<'a, T: 'a, C> {
80    pub title: &'a str,
81    pub content: Option<Content<C>>,
82    pub width: i32,
83    pub height: i32,
84    pub resizable: bool,
85    pub debug: bool,
86    pub router: Option<Router<'a, T>>,
87    pub user_data: Option<T>,
88}
89
90impl<'a, T: 'a, C> Default for WebViewBuilder<'a, T, C>
91where
92    C: AsRef<str>,
93{
94    fn default() -> Self {
95        #[cfg(debug_assertions)]
96        let debug = true;
97        #[cfg(not(debug_assertions))]
98        let debug = false;
99
100        WebViewBuilder {
101            title: "Application",
102            content: None,
103            width: 800,
104            height: 600,
105            resizable: true,
106            debug,
107            router: None,
108            user_data: None,
109        }
110    }
111}
112
113impl<'a, T: 'a, C> WebViewBuilder<'a, T, C>
114where
115    C: AsRef<str>,
116{
117    /// Alias for [`WebViewBuilder::default()`].
118    ///
119    /// [`WebViewBuilder::default()`]: struct.WebviewBuilder.html#impl-Default
120    pub fn new() -> Self {
121        WebViewBuilder::default()
122    }
123
124    /// Sets the title of the WebView window.
125    ///
126    /// Defaults to `"Application"`.
127    pub fn title(mut self, title: &'a str) -> Self {
128        self.title = title;
129        self
130    }
131
132    /// Sets the content of the WebView. Either a URL or a HTML string.
133    pub fn content(mut self, content: Content<C>) -> Self {
134        self.content = Some(content);
135        self
136    }
137
138    /// Sets the size of the WebView window.
139    ///
140    /// Defaults to 800 x 600.
141    pub fn size(mut self, width: i32, height: i32) -> Self {
142        self.width = width;
143        self.height = height;
144        self
145    }
146
147    /// Sets the resizability of the WebView window. If set to false, the window cannot be resized.
148    ///
149    /// Defaults to `true`.
150    pub fn resizable(mut self, resizable: bool) -> Self {
151        self.resizable = resizable;
152        self
153    }
154
155    /// Enables or disables debug mode.
156    ///
157    /// Defaults to `true` for debug builds, `false` for release builds.
158    pub fn debug(mut self, debug: bool) -> Self {
159        self.debug = debug;
160        self
161    }
162
163    /// Sets the route. Sets a router. The router determine which callback is executed when 
164    /// receives a javascript request
165    pub fn router(mut self, router: Router<'a, T>) -> Self {
166        self.router = Some(router);
167        self
168    }
169
170    /// Sets the initial state of the user data. This is an arbitrary value stored on the WebView
171    /// thread, accessible from dispatched closures without synchronization overhead.
172    pub fn user_data(mut self, user_data: T) -> Self {
173        self.user_data = Some(user_data);
174        self
175    }
176
177    /// Validates provided arguments and returns a new WebView if successful.
178    pub fn build(self) -> WVResult<WebView<'a, T>> {
179        macro_rules! require_field {
180            ($name:ident) => {
181                self.$name
182                    .ok_or_else(|| Error::UninitializedField(stringify!($name)))?
183            };
184        }
185
186        let title = CString::new(self.title)?;
187        let content = require_field!(content);
188        let url = match content {
189            Content::Url(url) => CString::new(url.as_ref())?,
190            Content::Html(html) => {
191                CString::new(format!("data:text/html,{}", encode(html.as_ref())))?
192            }
193        };
194        let user_data = require_field!(user_data);
195        let router = require_field!(router);
196
197        let invoke_handler = move |webview: &mut WebView<T>, arg: &str| -> WVResult {
198            router.resolve(webview, arg)
199        };
200
201        let mut webview = WebView::new(
202            &title,
203            &url,
204            self.width,
205            self.height,
206            self.resizable,
207            self.debug,
208            user_data,
209            invoke_handler,
210        )?;
211
212        webview.eval(include_str!("../dist/backend.js"))?;
213
214        Ok(webview)
215    }
216
217    /// Validates provided arguments and runs a new WebView to completion, returning the user data.
218    ///
219    /// Equivalent to `build()?.run()`.
220    pub fn run(self) -> WVResult<T> {
221        self.build()?.run()
222    }
223}
224
225/// Constructs a new builder for a [`WebView`].
226///
227/// Alias for [`WebViewBuilder::default()`].
228///
229/// [`WebView`]: struct.Webview.html
230/// [`WebViewBuilder::default()`]: struct.WebviewBuilder.html#impl-Default
231pub fn builder<'a, T, C>() -> WebViewBuilder<'a, T, C>
232where
233    C: AsRef<str>,
234{
235    WebViewBuilder::new()
236}
237
238struct UserData<'a, T> {
239    inner: T,
240    live: Arc<RwLock<()>>,
241    invoke_handler: Box<FnMut(&mut WebView<T>, &str) -> WVResult + 'a>,
242    result: WVResult,
243}
244
245/// An owned webview instance.
246///
247/// Construct via a [`WebViewBuilder`].
248///
249/// [`WebViewBuilder`]: struct.WebViewBuilder.html
250#[derive(Debug)]
251pub struct WebView<'a, T: 'a> {
252    inner: *mut CWebView,
253    _phantom: PhantomData<&'a mut T>,
254}
255
256impl<'a, T> WebView<'a, T> {
257    #![cfg_attr(feature = "cargo-clippy", allow(clippy::too_many_arguments))]
258    fn new<I>(
259        title: &CStr,
260        url: &CStr,
261        width: i32,
262        height: i32,
263        resizable: bool,
264        debug: bool,
265        user_data: T,
266        invoke_handler: I,
267    ) -> WVResult<WebView<'a, T>>
268    where
269        I: FnMut(&mut WebView<T>, &str) -> WVResult + 'a,
270    {
271        let user_data = Box::new(UserData {
272            inner: user_data,
273            live: Arc::new(RwLock::new(())),
274            invoke_handler: Box::new(invoke_handler),
275            result: Ok(()),
276        });
277        let user_data_ptr = Box::into_raw(user_data);
278
279        unsafe {
280            let inner = wrapper_webview_new(
281                title.as_ptr(),
282                url.as_ptr(),
283                width,
284                height,
285                resizable as _,
286                debug as _,
287                Some(ffi_invoke_handler::<T>),
288                user_data_ptr as _,
289            );
290
291            if inner.is_null() {
292                Box::<UserData<T>>::from_raw(user_data_ptr);
293                Err(Error::Initialization)
294            } else {
295                Ok(WebView::from_ptr(inner))
296            }
297        }
298    }
299
300    unsafe fn from_ptr(inner: *mut CWebView) -> WebView<'a, T> {
301        WebView {
302            inner,
303            _phantom: PhantomData,
304        }
305    }
306
307    /// Creates a thread-safe [`Handle`] to the `WebView`, from which closures can be dispatched.
308    ///
309    /// [`Handle`]: struct.Handle.html
310    pub fn handle(&self) -> Handle<T> {
311        Handle {
312            inner: self.inner,
313            live: Arc::downgrade(&self.user_data_wrapper().live),
314            _phantom: PhantomData,
315        }
316    }
317
318    fn user_data_wrapper_ptr(&self) -> *mut UserData<'a, T> {
319        unsafe { wrapper_webview_get_userdata(self.inner) as _ }
320    }
321
322    fn user_data_wrapper(&self) -> &UserData<'a, T> {
323        unsafe { &(*self.user_data_wrapper_ptr()) }
324    }
325
326    fn user_data_wrapper_mut(&mut self) -> &mut UserData<'a, T> {
327        unsafe { &mut (*self.user_data_wrapper_ptr()) }
328    }
329
330    /// Borrows the user data immutably.
331    pub fn user_data(&self) -> &T {
332        &self.user_data_wrapper().inner
333    }
334
335    /// Borrows the user data mutably.
336    pub fn user_data_mut(&mut self) -> &mut T {
337        &mut self.user_data_wrapper_mut().inner
338    }
339
340    /// Forces the `WebView` instance to end, without dropping.
341    pub fn terminate(&mut self) {
342        unsafe { webview_terminate(self.inner) }
343    }
344
345    /// Executes the provided string as JavaScript code within the `WebView` instance.
346    pub fn eval(&mut self, js: &str) -> WVResult {
347        let js = CString::new(js)?;
348        let ret = unsafe { webview_eval(self.inner, js.as_ptr()) };
349        if ret != 0 {
350            Err(Error::JsEvaluation)
351        } else {
352            Ok(())
353        }
354    }
355
356    /// Injects the provided string as CSS within the `WebView` instance.
357    pub fn inject_css(&mut self, css: &str) -> WVResult {
358        let css = CString::new(css)?;
359        let ret = unsafe { webview_inject_css(self.inner, css.as_ptr()) };
360        if ret != 0 {
361            Err(Error::CssInjection)
362        } else {
363            Ok(())
364        }
365    }
366
367    /// Sets the color of the title bar.
368    ///
369    /// # Examples
370    ///
371    /// Without specifying alpha (defaults to 255):
372    /// ```ignore
373    /// webview.set_color((123, 321, 213));
374    /// ```
375    ///
376    /// Specifying alpha:
377    /// ```ignore
378    /// webview.set_color((123, 321, 213, 127));
379    /// ```
380    pub fn set_color<C: Into<Color>>(&mut self, color: C) {
381        let color = color.into();
382        unsafe { webview_set_color(self.inner, color.r, color.g, color.b, color.a) }
383    }
384
385    /// Sets the title displayed at the top of the window.
386    ///
387    /// # Errors
388    ///
389    /// If `title` contain a nul byte, returns [`Error::NulByte`].
390    ///
391    /// [`Error::NulByte`]: enum.Error.html#variant.NulByte
392    pub fn set_title(&mut self, title: &str) -> WVResult {
393        let title = CString::new(title)?;
394        unsafe { webview_set_title(self.inner, title.as_ptr()) }
395        Ok(())
396    }
397
398    /// Enables or disables fullscreen.
399    pub fn set_fullscreen(&mut self, fullscreen: bool) {
400        unsafe { webview_set_fullscreen(self.inner, fullscreen as _) };
401    }
402
403    /// Returns a builder for opening a new dialog window.
404    pub fn dialog<'b>(&'b mut self) -> DialogBuilder<'a, 'b, T> {
405        DialogBuilder::new(self)
406    }
407
408    /// Iterates the event loop. Returns `None` if the view has been closed or terminated.
409    pub fn step(&mut self) -> Option<WVResult> {
410        unsafe {
411            match webview_loop(self.inner, 1) {
412                0 => {
413                    let closure_result = &mut self.user_data_wrapper_mut().result;
414                    match closure_result {
415                        Ok(_) => Some(Ok(())),
416                        e => Some(mem::replace(e, Ok(()))),
417                    }
418                }
419                _ => None,
420            }
421        }
422    }
423
424    /// Runs the event loop to completion and returns the user data.
425    pub fn run(mut self) -> WVResult<T> {
426        loop {
427            match self.step() {
428                Some(Ok(_)) => (),
429                Some(e) => e?,
430                None => return Ok(self.into_inner()),
431            }
432        }
433    }
434
435    /// Consumes the `WebView` and returns ownership of the user data.
436    pub fn into_inner(mut self) -> T {
437        unsafe {
438            let user_data = self._into_inner();
439            mem::forget(self);
440            user_data
441        }
442    }
443
444    unsafe fn _into_inner(&mut self) -> T {
445        let _lock = self
446            .user_data_wrapper()
447            .live
448            .write()
449            .expect("A dispatch channel thread panicked while holding mutex to WebView.");
450
451        let user_data_ptr = self.user_data_wrapper_ptr();
452        webview_exit(self.inner);
453        wrapper_webview_free(self.inner);
454        let user_data = *Box::from_raw(user_data_ptr);
455        user_data.inner
456    }
457}
458
459impl<'a, T> Drop for WebView<'a, T> {
460    fn drop(&mut self) {
461        unsafe {
462            self._into_inner();
463        }
464    }
465}
466
467/// A thread-safe handle to a [`WebView`] instance. Used to dispatch closures onto its task queue.
468///
469/// [`WebView`]: struct.WebView.html
470pub struct Handle<T> {
471    inner: *mut CWebView,
472    live: Weak<RwLock<()>>,
473    _phantom: PhantomData<T>,
474}
475
476impl<T> Handle<T> {
477    /// Schedules a closure to be run on the [`WebView`] thread.
478    ///
479    /// # Errors
480    ///
481    /// Returns [`Error::Dispatch`] if the [`WebView`] has been dropped.
482    ///
483    /// If the closure returns an `Err`, it will be returned on the next call to [`step()`].
484    ///
485    /// [`WebView`]: struct.WebView.html
486    /// [`Error::Dispatch`]: enum.Error.html#variant.Dispatch
487    /// [`step()`]: struct.WebView.html#method.step
488    pub fn dispatch<F>(&self, f: F) -> WVResult
489    where
490        F: FnOnce(&mut WebView<T>) -> WVResult + Send + 'static,
491    {
492        // Abort if WebView has been dropped. Otherwise, keep it alive until closure has been
493        // dispatched.
494        let mutex = self.live.upgrade().ok_or(Error::Dispatch)?;
495        let closure = Box::new(SendBoxFnOnce::new(f));
496        let _lock = mutex.read().map_err(|_| Error::Dispatch)?;
497
498        // Send closure to webview.
499        unsafe {
500            webview_dispatch(
501                self.inner,
502                Some(ffi_dispatch_handler::<T> as _),
503                Box::into_raw(closure) as _,
504            )
505        }
506        Ok(())
507    }
508}
509
510unsafe impl<T> Send for Handle<T> {}
511unsafe impl<T> Sync for Handle<T> {}
512
513fn read_str(s: &[u8]) -> String {
514    let end = s.iter().position(|&b| b == 0).map_or(0, |i| i + 1);
515    match CStr::from_bytes_with_nul(&s[..end]) {
516        Ok(s) => s.to_string_lossy().into_owned(),
517        Err(_) => "".to_string(),
518    }
519}
520
521extern "system" fn ffi_dispatch_handler<T>(webview: *mut CWebView, arg: *mut c_void) {
522    unsafe {
523        let mut handle = mem::ManuallyDrop::new(WebView::<T>::from_ptr(webview));
524        let result = {
525            let callback =
526                Box::<SendBoxFnOnce<'static, (&mut WebView<T>,), WVResult>>::from_raw(arg as _);
527            callback.call(&mut handle)
528        };
529        handle.user_data_wrapper_mut().result = result;
530    }
531}
532
533extern "system" fn ffi_invoke_handler<T>(webview: *mut CWebView, arg: *const c_char) {
534    unsafe {
535        let arg = CStr::from_ptr(arg).to_string_lossy().to_string();
536        let mut handle = mem::ManuallyDrop::new(WebView::<T>::from_ptr(webview));
537        let result = ((*handle.user_data_wrapper_ptr()).invoke_handler)(&mut *handle, &arg);
538        handle.user_data_wrapper_mut().result = result;
539    }
540}
OSZAR »