1extern 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#[derive(Debug)]
48pub enum Content<T> {
49 Url(T),
50 Html(T),
51}
52
53pub 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 pub fn new() -> Self {
121 WebViewBuilder::default()
122 }
123
124 pub fn title(mut self, title: &'a str) -> Self {
128 self.title = title;
129 self
130 }
131
132 pub fn content(mut self, content: Content<C>) -> Self {
134 self.content = Some(content);
135 self
136 }
137
138 pub fn size(mut self, width: i32, height: i32) -> Self {
142 self.width = width;
143 self.height = height;
144 self
145 }
146
147 pub fn resizable(mut self, resizable: bool) -> Self {
151 self.resizable = resizable;
152 self
153 }
154
155 pub fn debug(mut self, debug: bool) -> Self {
159 self.debug = debug;
160 self
161 }
162
163 pub fn router(mut self, router: Router<'a, T>) -> Self {
166 self.router = Some(router);
167 self
168 }
169
170 pub fn user_data(mut self, user_data: T) -> Self {
173 self.user_data = Some(user_data);
174 self
175 }
176
177 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 pub fn run(self) -> WVResult<T> {
221 self.build()?.run()
222 }
223}
224
225pub 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#[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 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 pub fn user_data(&self) -> &T {
332 &self.user_data_wrapper().inner
333 }
334
335 pub fn user_data_mut(&mut self) -> &mut T {
337 &mut self.user_data_wrapper_mut().inner
338 }
339
340 pub fn terminate(&mut self) {
342 unsafe { webview_terminate(self.inner) }
343 }
344
345 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 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 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 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 pub fn set_fullscreen(&mut self, fullscreen: bool) {
400 unsafe { webview_set_fullscreen(self.inner, fullscreen as _) };
401 }
402
403 pub fn dialog<'b>(&'b mut self) -> DialogBuilder<'a, 'b, T> {
405 DialogBuilder::new(self)
406 }
407
408 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 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 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
467pub struct Handle<T> {
471 inner: *mut CWebView,
472 live: Weak<RwLock<()>>,
473 _phantom: PhantomData<T>,
474}
475
476impl<T> Handle<T> {
477 pub fn dispatch<F>(&self, f: F) -> WVResult
489 where
490 F: FnOnce(&mut WebView<T>) -> WVResult + Send + 'static,
491 {
492 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 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}