web_view/
lib.rs

1//! [![Build Status]][travis] [![Latest Version]][crates.io]
2//!
3//! [Build Status]: https://api.travis-ci.org/Boscop/web-view.svg?branch=master
4//! [travis]: https://travis-ci.org/Boscop/web-view
5//! [Latest Version]: https://img.shields.io/crates/v/web-view.svg
6//! [crates.io]: https://crates.io/crates/web-view
7//!
8//! This library provides Rust bindings for the [webview](https://github.com/zserge/webview) library
9//! to allow easy creation of cross-platform Rust desktop apps with GUIs based on web technologies.
10//!
11//! It supports two-way bindings for communication between the Rust backend and JavaScript frontend.
12//!
13//! It uses Cocoa/WebKit on macOS, gtk-webkit2 on Linux and MSHTML (IE10/11) on Windows, so your app
14//! will be **much** leaner than with Electron.
15//!
16//! To use a custom version of webview, define an environment variable WEBVIEW_DIR with the path to
17//! its source directory.
18//!
19//! For usage info please check out [the examples] and the [original readme].
20//!
21//! [the examples]: https://github.com/Boscop/web-view/tree/master/examples
22//! [original readme]: https://github.com/zserge/webview/blob/master/README.md
23
24#![allow(deprecated)] // TODO: remove this when removing dialogs
25
26extern 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
52/// JavaScript function used to insert new css rules to webview.
53/// This function should be called with only one argument
54/// and that is the css to insert int webview.
55/// With every call of this function new style element
56/// will get created with css pasted as its children.
57const 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/// Content displayable inside a [`WebView`].
64///
65/// # Variants
66///
67/// - `Url` - Content to be fetched from a URL.
68/// - `Html` - A string containing literal HTML.
69///
70/// [`WebView`]: struct.WebView.html
71#[derive(Debug)]
72pub enum Content<T> {
73    Url(T),
74    Html(T),
75}
76
77/// Builder for constructing a [`WebView`] instance.
78///
79/// # Example
80///
81/// ```no_run
82/// extern crate web_view;
83///
84/// use web_view::*;
85///
86/// fn main() {
87///     WebViewBuilder::new()
88///         .title("Minimal webview example")
89///         .content(Content::Url("https://en.m.wikipedia.org/wiki/Main_Page"))
90///         .size(800, 600)
91///         .resizable(true)
92///         .debug(true)
93///         .user_data(())
94///         .invoke_handler(|_webview, _arg| Ok(()))
95///         .build()
96///         .unwrap()
97///         .run()
98///         .unwrap();
99/// }
100/// ```
101///
102/// [`WebView`]: struct.WebView.html
103pub 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    /// Alias for [`WebViewBuilder::default()`].
146    ///
147    /// [`WebViewBuilder::default()`]: struct.WebviewBuilder.html#impl-Default
148    pub fn new() -> Self {
149        WebViewBuilder::default()
150    }
151
152    /// Sets the title of the WebView window.
153    ///
154    /// Defaults to `"Application"`.
155    pub fn title(mut self, title: &'a str) -> Self {
156        self.title = title;
157        self
158    }
159
160    /// Sets the content of the WebView. Either a URL or a HTML string.
161    pub fn content(mut self, content: Content<C>) -> Self {
162        self.content = Some(content);
163        self
164    }
165
166    /// Sets the size of the WebView window.
167    ///
168    /// Defaults to 800 x 600.
169    pub fn size(mut self, width: i32, height: i32) -> Self {
170        self.width = width;
171        self.height = height;
172        self
173    }
174
175    /// Sets the resizability of the WebView window. If set to false, the window cannot be resized.
176    ///
177    /// Defaults to `true`.
178    pub fn resizable(mut self, resizable: bool) -> Self {
179        self.resizable = resizable;
180        self
181    }
182
183    /// Enables or disables debug mode.
184    ///
185    /// Defaults to `true` for debug builds, `false` for release builds.
186    pub fn debug(mut self, debug: bool) -> Self {
187        self.debug = debug;
188        self
189    }
190    /// The window crated will be frameless
191    ///
192    /// defaults to `false`
193    pub fn frameless(mut self, frameless: bool) -> Self {
194        self.frameless = frameless;
195        self
196    }
197
198    /// Sets the invoke handler callback. This will be called when a message is received from
199    /// JavaScript.
200    ///
201    /// # Errors
202    ///
203    /// If the closure returns an `Err`, it will be returned on the next call to [`step()`].
204    ///
205    /// [`step()`]: struct.WebView.html#method.step
206    pub fn invoke_handler(mut self, invoke_handler: I) -> Self {
207        self.invoke_handler = Some(invoke_handler);
208        self
209    }
210
211    /// Sets the initial state of the user data. This is an arbitrary value stored on the WebView
212    /// thread, accessible from dispatched closures without synchronization overhead.
213    pub fn user_data(mut self, user_data: T) -> Self {
214        self.user_data = Some(user_data);
215        self
216    }
217
218    /// Validates provided arguments and returns a new WebView if successful.
219    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    /// Validates provided arguments and runs a new WebView to completion, returning the user data.
252    ///
253    /// Equivalent to `build()?.run()`.
254    pub fn run(self) -> WVResult<T> {
255        self.build()?.run()
256    }
257}
258
259/// Constructs a new builder for a [`WebView`].
260///
261/// Alias for [`WebViewBuilder::default()`].
262///
263/// [`WebView`]: struct.Webview.html
264/// [`WebViewBuilder::default()`]: struct.WebviewBuilder.html#impl-Default
265pub 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/// An owned webview instance.
281///
282/// Construct via a [`WebViewBuilder`].
283///
284/// [`WebViewBuilder`]: struct.WebViewBuilder.html
285#[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    /// Creates a thread-safe [`Handle`] to the `WebView`, from which closures can be dispatched.
345    ///
346    /// [`Handle`]: struct.Handle.html
347    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    /// Borrows the user data immutably.
368    pub fn user_data(&self) -> &T {
369        &self.user_data_wrapper().inner
370    }
371
372    /// Borrows the user data mutably.
373    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    /// Gracefully exits the webview
383    pub fn exit(&mut self) {
384        unsafe { webview_exit(self.inner.unwrap()) }
385    }
386
387    /// Executes the provided string as JavaScript code within the `WebView` instance.
388    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    /// Injects the provided string as CSS within the `WebView` instance.
399    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    /// Sets the color of the title bar.
405    ///
406    /// # Examples
407    ///
408    /// Without specifying alpha (defaults to 255):
409    /// ```ignore
410    /// webview.set_color((123, 321, 213));
411    /// ```
412    ///
413    /// Specifying alpha:
414    /// ```ignore
415    /// webview.set_color((123, 321, 213, 127));
416    /// ```
417    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    /// Sets the title displayed at the top of the window.
423    ///
424    /// # Errors
425    ///
426    /// If `title` contain a nul byte, returns [`Error::NulByte`].
427    ///
428    /// [`Error::NulByte`]: enum.Error.html#variant.NulByte
429    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    /// Enables or disables fullscreen.
436    pub fn set_fullscreen(&mut self, fullscreen: bool) {
437        unsafe { webview_set_fullscreen(self.inner.unwrap(), fullscreen as _) };
438    }
439
440    /// Returns a builder for opening a new dialog window.
441    #[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    /// Iterates the event loop. Returns `None` if the view has been closed or terminated.
449    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    /// Runs the event loop to completion and returns the user data.
465    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    /// Consumes the `WebView` and returns ownership of the user data.
476    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
511/// A thread-safe handle to a [`WebView`] instance. Used to dispatch closures onto its task queue.
512///
513/// [`WebView`]: struct.WebView.html
514pub 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    /// Schedules a closure to be run on the [`WebView`] thread.
532    ///
533    /// # Errors
534    ///
535    /// Returns [`Error::Dispatch`] if the [`WebView`] has been dropped.
536    ///
537    /// If the closure returns an `Err`, it will be returned on the next call to [`step()`].
538    ///
539    /// [`WebView`]: struct.WebView.html
540    /// [`Error::Dispatch`]: enum.Error.html#variant.Dispatch
541    /// [`step()`]: struct.WebView.html#method.step
542    pub fn dispatch<F>(&self, f: F) -> WVResult
543    where
544        F: FnOnce(&mut WebView<T>) -> WVResult + Send + 'static,
545    {
546        // Abort if WebView has been dropped. Otherwise, keep it alive until closure has been
547        // dispatched.
548        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        // Send closure to webview.
553        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        // Do not clean up the webview on drop of the temporary WebView in handle
577        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        // Do not clean up the webview on drop of the temporary WebView in handle
588        handle.inner = None;
589    }
590}