browser_window/core/browser_window/
c.rs

1use std::{
2	error::Error as StdError, ffi::CStr, fmt, mem::MaybeUninit, os::raw::*, ptr, slice, str,
3};
4
5use browser_window_c::*;
6
7use super::{
8	super::{error::Error, window::WindowImpl},
9	*,
10};
11use crate::{def_browser_event, def_event, rc::*};
12
13
14#[derive(Clone)]
15pub struct BrowserWindowImpl {
16	inner: *mut cbw_BrowserWindow,
17}
18
19struct CreationCallbackData {
20	func: CreationCallbackFn,
21	data: *mut (),
22}
23
24struct EvalJsCallbackData {
25	callback: EvalJsCallbackFn,
26	data: *mut (),
27}
28
29/// An error that may occur when evaluating or executing JavaScript code.
30#[derive(Debug)]
31pub struct JsEvaluationError {
32	message: String, /* TODO: Add line and column number files, and perhaps even more info about
33	                  * the JS error */
34}
35
36struct EventData<C, A> {
37	owner: Weak<BrowserWindowOwner>,
38	handler: BrowserWindowEventHandler<A>,
39	converter: unsafe fn(&C) -> A,
40}
41
42
43#[doc(hidden)]
44#[macro_export]
45macro_rules! def_browser_event_c {
46	($name:ident<$carg_type:ty, $rarg_type:ty> => $converter:ident => $c_event_name:ident) => {
47		def_browser_event!($name<$rarg_type>(&mut self, handler) {
48			if let Some(upgraded) = self.owner.upgrade() {
49				let c_ptr = unsafe { &mut *upgraded.0.inner.inner };
50				// Free the previous event data if overwriting
51				if c_ptr.events.$c_event_name.callback.is_some() {
52					unsafe { let _ = Box::from_raw(c_ptr.events.$c_event_name.data as *mut EventData<$carg_type, $rarg_type>); }
53				}
54
55				// Store the new event data
56				let event_data = EventData::<$carg_type, $rarg_type> {
57					owner: self.owner.clone(),
58					handler,
59					converter: $converter,
60				};
61				let event_data_ptr = Box::into_raw(Box::new(event_data));
62				c_ptr.events.$c_event_name = cbw_Event {
63					callback: Some(ffi_browser_window_event_callback::<$carg_type, $rarg_type>),
64					data: event_data_ptr as _
65				};
66			}
67		});
68	}
69}
70
71
72impl BrowserWindowExt for BrowserWindowImpl {
73	fn cookie_jar(&self) -> Option<CookieJarImpl> {
74		let inner = unsafe { cbw_CookieJar_newGlobal() };
75
76		Some(CookieJarImpl(inner))
77	}
78
79	fn eval_js(&self, js: &str, callback: EvalJsCallbackFn, callback_data: *mut ()) {
80		let data = Box::new(EvalJsCallbackData {
81			callback,
82			data: callback_data,
83		});
84		let data_ptr = Box::into_raw(data);
85
86		unsafe {
87			cbw_BrowserWindow_evalJs(
88				self.inner,
89				js.into(),
90				Some(ffi_eval_js_callback_handler),
91				data_ptr as _,
92			)
93		}
94	}
95
96	fn eval_js_threadsafe(&self, js: &str, callback: EvalJsCallbackFn, callback_data: *mut ()) {
97		let data = Box::new(EvalJsCallbackData {
98			callback,
99			data: callback_data,
100		});
101
102		let data_ptr = Box::into_raw(data);
103
104		unsafe {
105			cbw_BrowserWindow_evalJsThreaded(
106				self.inner,
107				js.into(),
108				Some(ffi_eval_js_callback_handler),
109				data_ptr as _,
110			)
111		}
112	}
113
114	fn free(&self) { unsafe { cbw_BrowserWindow_free(self.inner) } }
115
116	fn navigate(&self, uri: &str) { unsafe { cbw_BrowserWindow_navigate(self.inner, uri.into()) }; }
117
118	fn url<'a>(&'a self) -> Cow<'a, str> {
119		let owned;
120		let slice;
121		unsafe {
122			let mut slice_uninit: MaybeUninit<cbw_StrSlice> = MaybeUninit::uninit();
123			owned = cbw_BrowserWindow_getUrl(self.inner, slice_uninit.as_mut_ptr());
124			slice = slice_uninit.assume_init();
125		}
126
127		if owned > 0 {
128			let url: String = slice.into();
129			unsafe { cbw_string_free(slice) };
130			url.into()
131		} else {
132			let url: &'a str = slice.into();
133			url.into()
134		}
135	}
136
137	fn window(&self) -> WindowImpl {
138		WindowImpl {
139			inner: unsafe { cbw_BrowserWindow_getWindow(self.inner) },
140		}
141	}
142
143	fn new(
144		app: ApplicationImpl, parent: WindowImpl, source: Source, title: &str, width: Option<u32>,
145		height: Option<u32>, window_options: &WindowOptions,
146		browser_window_options: &BrowserWindowOptions, creation_callback: CreationCallbackFn,
147		_callback_data: *mut (),
148	) {
149		// Convert width and height to -1 if unspecified.
150		// Also convert to c_int as required by the C interface.
151		let w: c_int = match width {
152			None => -1,
153			Some(x) => x as _,
154		};
155		let h: c_int = match height {
156			None => -1,
157			Some(x) => x as _,
158		};
159
160		// Wrap the callback functions so that they invoke our Rust functions from C
161		let callback_data = Box::new(CreationCallbackData {
162			func: creation_callback,
163			data: _callback_data,
164		});
165
166		// Source
167		let mut _url: String = "file:///".into(); // Stays here so that the reference to it that gets passed to C stays valid for the function call to `bw_BrowserWindow_new`.
168		let source2 = match &source {
169			// Use a reference, we want source to live until the end of the function because
170			// bw_BrowserWindowSource holds a reference to its internal string.
171			Source::File(path) => {
172				_url += path.to_str().unwrap();
173
174				cbw_BrowserWindowSource {
175					data: _url.as_str().into(),
176					is_html: 0,
177				}
178			}
179			Source::Html(html) => cbw_BrowserWindowSource {
180				data: html.as_str().into(),
181				is_html: 1,
182			},
183			Source::Url(url) => cbw_BrowserWindowSource {
184				data: url.as_str().into(),
185				is_html: 0,
186			},
187		};
188
189		unsafe {
190			let browser = cbw_BrowserWindow_new(
191				app.inner,
192				parent.inner,
193				title.into(),
194				w,
195				h,
196				window_options as _,
197			);
198			cbw_BrowserWindow_create(
199				browser,
200				w,
201				h,
202				source2,
203				browser_window_options as _,
204				Some(ffi_creation_callback_handler),
205				Box::into_raw(callback_data) as _,
206			);
207		};
208	}
209}
210
211impl BrowserWindowEventExt for BrowserWindowImpl {
212	fn on_address_changed(&self, handle: Weak<BrowserWindowOwner>) -> AddressChangedEvent {
213		AddressChangedEvent::new(handle)
214	}
215
216	fn on_console_message(&self, handle: Weak<BrowserWindowOwner>) -> ConsoleMessageEvent {
217		ConsoleMessageEvent::new(handle)
218	}
219
220	fn on_favicon_changed(&self, handle: Weak<BrowserWindowOwner>) -> FaviconChangedEvent {
221		FaviconChangedEvent::new(handle)
222	}
223
224	fn on_fullscreen_mode_changed(
225		&self, handle: Weak<BrowserWindowOwner>,
226	) -> FullscreenModeChangedEvent {
227		FullscreenModeChangedEvent::new(handle)
228	}
229
230	fn on_loading_progress_changed(
231		&self, handle: Weak<BrowserWindowOwner>,
232	) -> LoadingProgressChangedEvent {
233		LoadingProgressChangedEvent::new(handle)
234	}
235
236	fn on_message(&self, handle: Weak<BrowserWindowOwner>) -> MessageEvent {
237		MessageEvent::new(handle)
238	}
239
240	fn on_navigation_end(&self, handle: Weak<BrowserWindowOwner>) -> NavigationEndEvent {
241		NavigationEndEvent::new(handle)
242	}
243
244	fn on_navigation_start(&self, handle: Weak<BrowserWindowOwner>) -> NavigationStartEvent {
245		NavigationStartEvent::new(handle)
246	}
247
248	fn on_page_title_changed(&self, handle: Weak<BrowserWindowOwner>) -> PageTitleChangedEvent {
249		PageTitleChangedEvent::new(handle)
250	}
251
252	fn on_status_message(&self, handle: Weak<BrowserWindowOwner>) -> StatusMessageEvent {
253		StatusMessageEvent::new(handle)
254	}
255
256	fn on_tooltip(&self, handle: Weak<BrowserWindowOwner>) -> TooltipEvent {
257		TooltipEvent::new(handle)
258	}
259}
260
261def_browser_event_c!(AddressChangedEvent<cbw_CStrSlice, String> => str_converter => on_address_changed);
262def_browser_event_c!(ConsoleMessageEvent<cbw_CStrSlice, String> => str_converter => on_console_message);
263def_browser_event_c!(FaviconChangedEvent<cbw_CStrSlice, String> => str_converter => on_favicon_changed);
264def_browser_event_c!(FullscreenModeChangedEvent<c_int, bool> => bool_converter => on_fullscreen_mode_changed);
265def_browser_event_c!(LoadingProgressChangedEvent<c_double, f64> => f64_converter => on_loading_progress_changed);
266def_browser_event_c!(MessageEvent<cbw_BrowserWindowMessageArgs, MessageEventArgs> => message_args_converter => on_message);
267def_browser_event_c!(NavigationStartEvent<(), ()> => no_converter => on_navigation_start);
268def_browser_event_c!(NavigationEndEvent<cbw_Err, Result<(), Error>> => error_converter => on_navigation_end);
269def_browser_event_c!(PageTitleChangedEvent<cbw_CStrSlice, String> => str_converter => on_page_title_changed);
270def_browser_event_c!(StatusMessageEvent<cbw_CStrSlice, String> => str_converter => on_status_message);
271def_browser_event_c!(TooltipEvent<cbw_CStrSlice, String> => str_converter => on_tooltip);
272
273impl JsEvaluationError {
274	pub(super) unsafe fn new(err: *const cbw_Err) -> Self {
275		let msg_ptr = ((*err).alloc_message.unwrap())((*err).code, (*err).data);
276		let cstr = CStr::from_ptr(msg_ptr);
277		let message: String = cstr.to_string_lossy().into();
278
279		Self { message }
280	}
281}
282
283impl StdError for JsEvaluationError {
284	fn source(&self) -> Option<&(dyn StdError + 'static)> { None }
285}
286
287impl fmt::Display for JsEvaluationError {
288	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
289		write!(f, "{}", self.message.as_str())
290	}
291}
292
293/***************************************************************************
294 * ************************************* The C handler functions that are
295 * invoked by external C code, and that in turn invoke relevant Rust
296 * handlers. * *************************************************************
297 * ************************************************ */
298
299pub(super) unsafe extern "C" fn ffi_creation_callback_handler(
300	bw: *mut cbw_BrowserWindow, _data: *mut c_void,
301) {
302	let data_ptr = _data as *mut CreationCallbackData;
303	let data = Box::from_raw(data_ptr);
304
305	let handle = BrowserWindowImpl { inner: bw };
306
307	(data.func)(handle, data.data);
308}
309
310unsafe extern "C" fn ffi_eval_js_callback_handler(
311	bw: *mut cbw_BrowserWindow, _data: *mut c_void, _result: *const c_char, error: *const cbw_Err,
312) {
313	let data_ptr = _data as *mut EvalJsCallbackData;
314	let data = Box::from_raw(data_ptr);
315
316	let (handle, result) = ffi_eval_js_callback_result(bw, _result, error);
317
318	(data.callback)(handle, data.data, result);
319}
320
321/// Processes the result received from the C function, and returns it in a Rust
322/// Result.
323unsafe fn ffi_eval_js_callback_result(
324	bw: *mut cbw_BrowserWindow, result: *const c_char, error: *const cbw_Err,
325) -> (BrowserWindowImpl, Result<JsValue, JsEvaluationError>) {
326	// Construct a result value depending on whether the result or error parameters
327	// are set
328	let result_val: Result<JsValue, JsEvaluationError> = if error.is_null() {
329		let result_str = CStr::from_ptr(result).to_string_lossy().to_string();
330
331		// Parse the string
332		Ok(JsValue::from_string(&result_str))
333	} else {
334		Err(JsEvaluationError::new(error))
335	};
336
337	let handle = BrowserWindowImpl { inner: bw };
338
339	// return
340	(handle, result_val)
341}
342
343unsafe extern "C" fn ffi_browser_window_event_callback<C, A>(
344	handler_data: *mut c_void, arg_ptr: *mut c_void,
345) -> i32 {
346	let event_data_ptr = handler_data as *mut EventData<C, A>;
347	let event_data = &mut *event_data_ptr;
348	let arg_ptr2 = arg_ptr as *mut C;
349	let carg = &*arg_ptr2;
350
351	// Convert C type to Rust type
352	let rarg = (event_data.converter)(carg);
353
354	// Run the event handler
355	let rc_handle = event_data
356		.owner
357		.upgrade()
358		.expect("browser window handle is gone");
359	match &mut event_data.handler {
360		EventHandler::Sync(callback) => {
361			(callback)(&*rc_handle, rarg);
362		}
363		EventHandler::Async(callback) => {
364			let app = rc_handle.0.app();
365			let future = (callback)(BrowserWindow(rc_handle.clone()), rarg);
366			app.spawn(future);
367		}
368	}
369	return 0;
370}
371
372unsafe fn no_converter(_input: &()) -> () { () }
373
374unsafe fn error_converter(input: &cbw_Err) -> Result<(), Error> {
375	if input.code == 0 {
376		Ok(())
377	} else {
378		let err2 = input.clone();
379		Err(Error::from(err2))
380	}
381}
382
383unsafe fn bool_converter(input: &c_int) -> bool { *input > 0 }
384
385unsafe fn f64_converter(input: &c_double) -> f64 { *input }
386
387unsafe fn str_converter(input: &cbw_CStrSlice) -> String {
388	let string =
389		str::from_utf8_unchecked(slice::from_raw_parts(input.data as *const u8, input.len) as _);
390	string.to_string()
391}
392
393unsafe fn message_args_converter(input: &cbw_BrowserWindowMessageArgs) -> MessageEventArgs {
394	// Convert the command and args to a String and `Vec<&str>`
395	let cmd_string = str::from_utf8_unchecked(slice::from_raw_parts(
396		input.cmd.data as *const u8,
397		input.cmd.len,
398	));
399	let mut args_vec: Vec<JsValue> = Vec::with_capacity(input.arg_count as usize);
400	for i in 0..input.arg_count {
401		args_vec.push(JsValue::from_string((*input.args.add(i as usize)).into()));
402	}
403
404	MessageEventArgs {
405		cmd: cmd_string.to_string(),
406		args: args_vec,
407	}
408}
409
410#[allow(non_snake_case)]
411#[no_mangle]
412extern "C" fn bw_Window_freeUserData(w: *mut c_void) {
413	let w_ptr = w as *mut cbw_Window;
414	unsafe {
415		if (*w_ptr).user_data != ptr::null_mut() {
416			BrowserWindowImpl::free_user_data((*w_ptr).user_data as _);
417			(*w_ptr).user_data = ptr::null_mut();
418		}
419	}
420}