use std::{
error::Error as StdError, ffi::CStr, fmt, mem::MaybeUninit, os::raw::*, ptr, slice, str,
};
use browser_window_c::*;
use super::{
super::{error::Error, window::WindowImpl},
*,
};
use crate::{
def_browser_event, def_event,
rc::*,
};
#[derive(Clone)]
pub struct BrowserWindowImpl {
inner: *mut cbw_BrowserWindow,
}
struct CreationCallbackData {
func: CreationCallbackFn,
data: *mut (),
}
struct EvalJsCallbackData {
callback: EvalJsCallbackFn,
data: *mut (),
}
#[derive(Debug)]
pub struct JsEvaluationError {
message: String, }
struct EventData<C, A> {
owner: Weak<BrowserWindowOwner>,
handler: BrowserWindowEventHandler<A>,
converter: unsafe fn(&C) -> A,
}
#[doc(hidden)]
#[macro_export]
macro_rules! def_browser_event_c {
($name:ident<$carg_type:ty, $rarg_type:ty> => $converter:ident => $c_event_name:ident) => {
def_browser_event!($name<$rarg_type>(&mut self, handler) {
if let Some(upgraded) = self.owner.upgrade() {
let c_ptr = unsafe { &mut *upgraded.0.inner.inner };
if c_ptr.events.$c_event_name.callback.is_some() {
unsafe { let _ = Box::from_raw(c_ptr.events.$c_event_name.data as *mut EventData<$carg_type, $rarg_type>); }
}
let event_data = EventData::<$carg_type, $rarg_type> {
owner: self.owner.clone(),
handler,
converter: $converter,
};
let event_data_ptr = Box::into_raw(Box::new(event_data));
c_ptr.events.$c_event_name = cbw_Event {
callback: Some(ffi_browser_window_event_callback::<$carg_type, $rarg_type>),
data: event_data_ptr as _
};
}
});
}
}
impl BrowserWindowExt for BrowserWindowImpl {
fn cookie_jar(&self) -> Option<CookieJarImpl> {
let inner = unsafe { cbw_CookieJar_newGlobal() };
Some(CookieJarImpl(inner))
}
fn eval_js(&self, js: &str, callback: EvalJsCallbackFn, callback_data: *mut ()) {
let data = Box::new(EvalJsCallbackData {
callback,
data: callback_data,
});
let data_ptr = Box::into_raw(data);
unsafe {
cbw_BrowserWindow_evalJs(
self.inner,
js.into(),
Some(ffi_eval_js_callback_handler),
data_ptr as _,
)
}
}
fn eval_js_threadsafe(&self, js: &str, callback: EvalJsCallbackFn, callback_data: *mut ()) {
let data = Box::new(EvalJsCallbackData {
callback,
data: callback_data,
});
let data_ptr = Box::into_raw(data);
unsafe {
cbw_BrowserWindow_evalJsThreaded(
self.inner,
js.into(),
Some(ffi_eval_js_callback_handler),
data_ptr as _,
)
}
}
fn free(&self) { unsafe { cbw_BrowserWindow_free(self.inner) } }
fn navigate(&self, uri: &str) { unsafe { cbw_BrowserWindow_navigate(self.inner, uri.into()) }; }
fn url<'a>(&'a self) -> Cow<'a, str> {
let owned;
let slice;
unsafe {
let mut slice_uninit: MaybeUninit<cbw_StrSlice> = MaybeUninit::uninit();
owned = cbw_BrowserWindow_getUrl(self.inner, slice_uninit.as_mut_ptr());
slice = slice_uninit.assume_init();
}
if owned > 0 {
let url: String = slice.into();
unsafe { cbw_string_free(slice) };
url.into()
} else {
let url: &'a str = slice.into();
url.into()
}
}
fn window(&self) -> WindowImpl {
WindowImpl {
inner: unsafe { cbw_BrowserWindow_getWindow(self.inner) },
}
}
fn new(
app: ApplicationImpl, parent: WindowImpl, source: Source, title: &str, width: Option<u32>,
height: Option<u32>, window_options: &WindowOptions,
browser_window_options: &BrowserWindowOptions, creation_callback: CreationCallbackFn,
_callback_data: *mut (),
) {
let w: c_int = match width {
None => -1,
Some(x) => x as _,
};
let h: c_int = match height {
None => -1,
Some(x) => x as _,
};
let callback_data = Box::new(CreationCallbackData {
func: creation_callback,
data: _callback_data,
});
let mut _url: String = "file:///".into(); let source2 = match &source {
Source::File(path) => {
_url += path.to_str().unwrap();
cbw_BrowserWindowSource {
data: _url.as_str().into(),
is_html: 0,
}
}
Source::Html(html) => cbw_BrowserWindowSource {
data: html.as_str().into(),
is_html: 1,
},
Source::Url(url) => cbw_BrowserWindowSource {
data: url.as_str().into(),
is_html: 0,
},
};
unsafe {
let browser = cbw_BrowserWindow_new(
app.inner,
parent.inner,
title.into(),
w,
h,
window_options as _,
);
cbw_BrowserWindow_create(
browser,
w,
h,
source2,
browser_window_options as _,
Some(ffi_creation_callback_handler),
Box::into_raw(callback_data) as _,
);
};
}
}
impl BrowserWindowEventExt for BrowserWindowImpl {
fn on_address_changed(&self, handle: Weak<BrowserWindowOwner>) -> AddressChangedEvent {
AddressChangedEvent::new(handle)
}
fn on_console_message(&self, handle: Weak<BrowserWindowOwner>) -> ConsoleMessageEvent {
ConsoleMessageEvent::new(handle)
}
fn on_favicon_changed(&self, handle: Weak<BrowserWindowOwner>) -> FaviconChangedEvent {
FaviconChangedEvent::new(handle)
}
fn on_fullscreen_mode_changed(
&self, handle: Weak<BrowserWindowOwner>,
) -> FullscreenModeChangedEvent {
FullscreenModeChangedEvent::new(handle)
}
fn on_loading_progress_changed(
&self, handle: Weak<BrowserWindowOwner>,
) -> LoadingProgressChangedEvent {
LoadingProgressChangedEvent::new(handle)
}
fn on_message(&self, handle: Weak<BrowserWindowOwner>) -> MessageEvent {
MessageEvent::new(handle)
}
fn on_navigation_end(&self, handle: Weak<BrowserWindowOwner>) -> NavigationEndEvent {
NavigationEndEvent::new(handle)
}
fn on_navigation_start(&self, handle: Weak<BrowserWindowOwner>) -> NavigationStartEvent {
NavigationStartEvent::new(handle)
}
fn on_page_title_changed(&self, handle: Weak<BrowserWindowOwner>) -> PageTitleChangedEvent {
PageTitleChangedEvent::new(handle)
}
fn on_status_message(&self, handle: Weak<BrowserWindowOwner>) -> StatusMessageEvent {
StatusMessageEvent::new(handle)
}
fn on_tooltip(&self, handle: Weak<BrowserWindowOwner>) -> TooltipEvent {
TooltipEvent::new(handle)
}
}
def_browser_event_c!(AddressChangedEvent<cbw_CStrSlice, String> => str_converter => on_address_changed);
def_browser_event_c!(ConsoleMessageEvent<cbw_CStrSlice, String> => str_converter => on_console_message);
def_browser_event_c!(FaviconChangedEvent<cbw_CStrSlice, String> => str_converter => on_favicon_changed);
def_browser_event_c!(FullscreenModeChangedEvent<c_int, bool> => bool_converter => on_fullscreen_mode_changed);
def_browser_event_c!(LoadingProgressChangedEvent<c_double, f64> => f64_converter => on_loading_progress_changed);
def_browser_event_c!(MessageEvent<cbw_BrowserWindowMessageArgs, MessageEventArgs> => message_args_converter => on_message);
def_browser_event_c!(NavigationStartEvent<(), ()> => no_converter => on_navigation_start);
def_browser_event_c!(NavigationEndEvent<cbw_Err, Result<(), Error>> => error_converter => on_navigation_end);
def_browser_event_c!(PageTitleChangedEvent<cbw_CStrSlice, String> => str_converter => on_page_title_changed);
def_browser_event_c!(StatusMessageEvent<cbw_CStrSlice, String> => str_converter => on_status_message);
def_browser_event_c!(TooltipEvent<cbw_CStrSlice, String> => str_converter => on_tooltip);
impl JsEvaluationError {
pub(super) unsafe fn new(err: *const cbw_Err) -> Self {
let msg_ptr = ((*err).alloc_message.unwrap())((*err).code, (*err).data);
let cstr = CStr::from_ptr(msg_ptr);
let message: String = cstr.to_string_lossy().into();
Self { message }
}
}
impl StdError for JsEvaluationError {
fn source(&self) -> Option<&(dyn StdError + 'static)> { None }
}
impl fmt::Display for JsEvaluationError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.message.as_str())
}
}
pub(super) unsafe extern "C" fn ffi_creation_callback_handler(
bw: *mut cbw_BrowserWindow, _data: *mut c_void,
) {
let data_ptr = _data as *mut CreationCallbackData;
let data = Box::from_raw(data_ptr);
let handle = BrowserWindowImpl { inner: bw };
(data.func)(handle, data.data);
}
unsafe extern "C" fn ffi_eval_js_callback_handler(
bw: *mut cbw_BrowserWindow, _data: *mut c_void, _result: *const c_char, error: *const cbw_Err,
) {
let data_ptr = _data as *mut EvalJsCallbackData;
let data = Box::from_raw(data_ptr);
let (handle, result) = ffi_eval_js_callback_result(bw, _result, error);
(data.callback)(handle, data.data, result);
}
unsafe fn ffi_eval_js_callback_result(
bw: *mut cbw_BrowserWindow, result: *const c_char, error: *const cbw_Err,
) -> (BrowserWindowImpl, Result<JsValue, JsEvaluationError>) {
let result_val: Result<JsValue, JsEvaluationError> = if error.is_null() {
let result_str = CStr::from_ptr(result).to_string_lossy().to_string();
Ok(JsValue::from_string(&result_str))
} else {
Err(JsEvaluationError::new(error))
};
let handle = BrowserWindowImpl { inner: bw };
(handle, result_val)
}
unsafe extern "C" fn ffi_browser_window_event_callback<C, A>(
handler_data: *mut c_void, arg_ptr: *mut c_void,
) -> i32 {
let event_data_ptr = handler_data as *mut EventData<C, A>;
let event_data = &mut *event_data_ptr;
let arg_ptr2 = arg_ptr as *mut C;
let carg = &*arg_ptr2;
let rarg = (event_data.converter)(carg);
let rc_handle = event_data
.owner
.upgrade()
.expect("browser window handle is gone");
match &mut event_data.handler {
EventHandler::Sync(callback) => {
(callback)(&*rc_handle, rarg);
}
EventHandler::Async(callback) => {
let app = rc_handle.0.app();
let future = (callback)(BrowserWindow(rc_handle.clone()), rarg);
app.spawn(future);
}
}
return 0;
}
unsafe fn no_converter(_input: &()) -> () { () }
unsafe fn error_converter(input: &cbw_Err) -> Result<(), Error> {
if input.code == 0 {
Ok(())
} else {
let err2 = input.clone();
Err(Error::from(err2))
}
}
unsafe fn bool_converter(input: &c_int) -> bool { *input > 0 }
unsafe fn f64_converter(input: &c_double) -> f64 { *input }
unsafe fn str_converter(input: &cbw_CStrSlice) -> String {
let string = str::from_utf8_unchecked(slice::from_raw_parts(input.data as *const u8, input.len) as _);
string.to_string()
}
unsafe fn message_args_converter(
input: &cbw_BrowserWindowMessageArgs,
) -> MessageEventArgs {
let cmd_string = str::from_utf8_unchecked(slice::from_raw_parts(
input.cmd.data as *const u8,
input.cmd.len,
));
let mut args_vec: Vec<JsValue> = Vec::with_capacity(input.arg_count as usize);
for i in 0..input.arg_count {
args_vec.push(JsValue::from_string((*input.args.add(i as usize)).into()));
}
MessageEventArgs {
cmd: cmd_string.to_string(),
args: args_vec,
}
}
#[allow(non_snake_case)]
#[no_mangle]
extern "C" fn bw_Window_freeUserData(w: *mut c_void) {
let w_ptr = w as *mut cbw_Window;
unsafe {
if (*w_ptr).user_data != ptr::null_mut() {
BrowserWindowImpl::free_user_data((*w_ptr).user_data as _);
(*w_ptr).user_data = ptr::null_mut();
}
}
}