browser_window/
browser.rs

1//! This module contains all browser related handles and stuff.
2//!
3//! Keep in mind that `BrowserWindow` exposes the same methods as
4//! `BrowserWindowHandle` and `WindowHandle` does. The methods of
5//! `BrowserWindowHandle` are displayed correctly at the page of
6//! `BrowserWindow`, but the methods of `WindowHandle` are not displayed.
7//! Be sure to check them out [here](../window/struct.WindowHandle.html).
8
9#[cfg(feature = "threadsafe")]
10use std::future::Future;
11use std::{borrow::Cow, ops::Deref};
12
13use futures_channel::oneshot;
14#[cfg(feature = "threadsafe")]
15use unsafe_send_sync::UnsafeSend;
16
17#[cfg(feature = "threadsafe")]
18use crate::delegate::*;
19use crate::{
20	application::*,
21	core::{
22		browser_window::{
23			BrowserWindowEventExt, BrowserWindowExt, BrowserWindowImpl, JsEvaluationError,
24		},
25		window::WindowExt,
26	},
27	decl_browser_event, decl_event,
28	event::EventHandler,
29	prelude::*,
30	rc::Rc,
31	window::*,
32	HasHandle,
33};
34
35mod builder;
36
37pub use builder::{BrowserWindowBuilder, Source};
38
39
40/// The future that dispatches a closure on the GUI thread.
41#[cfg(feature = "threadsafe")]
42pub type BrowserDelegateFuture<'a, R> = DelegateFuture<'a, BrowserWindow, BrowserWindowHandle, R>;
43
44/// An owned browser window handle.
45/// When this handle goes out of scope, its resources will get scheduled for
46/// cleanup. The resources will only ever be cleaned up whenever both this
47/// handle has gone out of scope, and when the window has actually been closed
48/// by the user. If the window has been closed by the user but this handle still
49/// exists, the window is actually just been closed. It can be reshown by
50/// calling `show` on this handle.
51pub struct BrowserWindowOwner(pub(super) BrowserWindowHandle);
52#[derive(Clone)]
53pub struct BrowserWindow(pub(super) Rc<BrowserWindowOwner>);
54#[cfg(feature = "threadsafe")]
55/// **Note:** Only available with feature `threadsafe` enabled.
56///
57/// A thread-safe handle to a browser window.
58/// It allows you to dispatch code to the GUI thread and thereby manipulate the
59/// browser window from any thread. To do this, you will need to use the
60/// functions `dispatch`, `dispatch_async`, `delegate` and `delegate_async`.
61///
62/// # Example
63///
64/// This example fetches a value from within JavaScript:
65/// ```
66/// use browser_window::{application::*, browser::*};
67///
68/// async fn get_cookies(app: &ApplicationHandleThreaded) -> String {
69/// 	let mut builder =
70/// 		BrowserWindowBuilder::new(Source::Url("https://www.duckduckgo.com/".into()));
71/// 	builder.title("test");
72///
73/// 	let bw: BrowserWindowThreaded = builder.build_threaded(app).await.unwrap();
74///
75/// 	// Waits for `eval_js` to give back its result from the GUI thread
76/// 	let result = bw
77/// 		.delegate_async(|handle| async move {
78/// 			// Execute `eval_js` on the GUI thread
79/// 			handle.eval_js("document.cookies").await
80/// 		})
81/// 		.await
82/// 		.unwrap();
83///
84/// 	result.unwrap().to_string()
85/// }
86/// ```
87#[derive(Clone)]
88pub struct BrowserWindowThreaded(BrowserWindow);
89#[cfg(feature = "threadsafe")]
90unsafe impl Sync for BrowserWindowThreaded {}
91
92pub type BrowserWindowEventHandler<A> = EventHandler<BrowserWindowHandle, BrowserWindow, A>;
93
94/// This is a handle to an existing browser window.
95pub struct BrowserWindowHandle {
96	pub(super) inner: BrowserWindowImpl,
97	app: ApplicationHandle,
98	window: WindowHandle,
99}
100
101pub struct MessageEventArgs {
102	pub cmd: String,
103	pub args: Vec<JsValue>,
104}
105
106
107decl_browser_event!(AddressChangedEvent);
108decl_browser_event!(AuthCredentialsEvent);
109decl_browser_event!(CertificateErrorEvent);
110decl_browser_event!(ConsoleMessageEvent);
111decl_browser_event!(DownloadProgressEvent);
112decl_browser_event!(DownloadStartedEvent);
113decl_browser_event!(FaviconChangedEvent);
114decl_browser_event!(FileDialogEvent);
115decl_browser_event!(FullscreenModeChangedEvent);
116decl_browser_event!(KeyPressEvent);
117decl_browser_event!(KeyPressedEvent);
118decl_browser_event!(LoadingProgressChangedEvent);
119decl_browser_event!(MessageEvent);
120decl_browser_event!(NavigationEndEvent);
121decl_browser_event!(NavigationStartEvent);
122decl_browser_event!(PageTitleChangedEvent);
123decl_browser_event!(ScrollOffsetChangedEvent);
124decl_browser_event!(SelectClientCertificateEvent);
125decl_browser_event!(StartDraggingEvent);
126decl_browser_event!(StatusMessageEvent);
127decl_browser_event!(TooltipEvent);
128decl_browser_event!(TextSelectionChangedEvent);
129
130
131impl BrowserWindow {
132	/// Whenver the address URI changes
133	pub fn on_address_changed(&self) -> AddressChangedEvent {
134		self.0.0.inner.on_address_changed(Rc::downgrade(&self.0))
135	}
136
137	/// When a console message is printend.
138	pub fn on_console_message(&self) -> ConsoleMessageEvent {
139		self.0.0.inner.on_console_message(Rc::downgrade(&self.0))
140	}
141
142	/// Whenever the browser goes into or out of full screen mode.
143	pub fn on_fullscreen_mode_changed(&self) -> FullscreenModeChangedEvent {
144		self.0
145			.0
146			.inner
147			.on_fullscreen_mode_changed(Rc::downgrade(&self.0))
148	}
149
150	/// Loading progress updates
151	pub fn on_loading_progress_changed(&self) -> LoadingProgressChangedEvent {
152		self.0
153			.0
154			.inner
155			.on_loading_progress_changed(Rc::downgrade(&self.0))
156	}
157
158	/// The event that will fire whenever `invoke_extern` is called with JS on
159	/// the client side.
160	/// This event is implemented for _all_ browser frameworks.
161	pub fn on_message(&self) -> MessageEvent { self.0.0.inner.on_message(Rc::downgrade(&self.0)) }
162
163	/// Whenever navigation has finished and the page has loaded.
164	pub fn on_navigation_end(&self) -> NavigationEndEvent {
165		self.0.0.inner.on_navigation_end(Rc::downgrade(&self.0))
166	}
167
168	/// Whenever navigation to a new link happens.
169	pub fn on_navigation_start(&self) -> NavigationStartEvent {
170		self.0.0.inner.on_navigation_start(Rc::downgrade(&self.0))
171	}
172
173	/// Whenver the page title changes.
174	pub fn on_page_title_changed(&self) -> PageTitleChangedEvent {
175		self.0.0.inner.on_page_title_changed(Rc::downgrade(&self.0))
176	}
177
178	pub fn on_status_message(&self) -> StatusMessageEvent {
179		self.0.0.inner.on_status_message(Rc::downgrade(&self.0))
180	}
181
182	/// Whenever the browser is about to show a tooltip
183	pub fn on_tooltip(&self) -> TooltipEvent { self.0.0.inner.on_tooltip(Rc::downgrade(&self.0)) }
184
185	/// Not implemented yet.
186	pub fn on_auth_credentials(&self) -> AuthCredentialsEvent {
187		unimplemented!();
188	}
189
190	/// Not implemented yet.
191	pub fn on_certificate_error(&self) -> CertificateErrorEvent {
192		unimplemented!();
193	}
194
195	/// Not implemented yet.
196	pub fn on_download_progress(&self) -> DownloadProgressEvent {
197		unimplemented!();
198	}
199
200	/// Not implemented yet.
201	pub fn on_download_started(&self) -> DownloadStartedEvent {
202		unimplemented!();
203	}
204
205	/// Not implemented yet.
206	pub fn on_favicon_changed(&self) -> FaviconChangedEvent {
207		unimplemented!();
208	}
209
210	/// Not implemented yet.
211	pub fn on_file_dialog(&self) -> FileDialogEvent {
212		unimplemented!();
213	}
214
215	/// Not implemented yet.
216	pub fn on_key_press(&self) -> KeyPressEvent {
217		unimplemented!();
218	}
219
220	/// Not implemented yet.
221	pub fn on_key_pressed(&self) -> KeyPressedEvent {
222		unimplemented!();
223	}
224
225	/// Not implemented yet.
226	pub fn on_scroll_offset_changed(&self) -> ScrollOffsetChangedEvent {
227		unimplemented!();
228	}
229
230	/// Not implemented yet.
231	pub fn on_select_client_certificate(&self) -> SelectClientCertificateEvent {
232		unimplemented!();
233	}
234
235	/// Not implemented yet.
236	pub fn on_start_dragging(&self) -> StartDraggingEvent {
237		unimplemented!();
238	}
239
240	/// Not implemented yet.
241	pub fn on_text_selection_changed(&self) -> TextSelectionChangedEvent {
242		unimplemented!();
243	}
244}
245
246impl Deref for BrowserWindow {
247	type Target = BrowserWindowHandle;
248
249	fn deref(&self) -> &Self::Target { &self.0.0 }
250}
251
252impl HasHandle<ApplicationHandle> for BrowserWindow {
253	fn handle(&self) -> &ApplicationHandle { &self.app }
254}
255
256impl HasHandle<WindowHandle> for BrowserWindow {
257	fn handle(&self) -> &WindowHandle { &self.window }
258}
259
260// Core browser window functions
261impl BrowserWindowHandle {
262	/// Returns the application handle associated with this browser window.
263	pub fn app(&self) -> ApplicationHandle { ApplicationHandle::new(self.inner.window().app()) }
264
265	pub fn close(self) {
266		// The window isn't actually destroyed until the reference count of the owner
267		// reaches 0.
268		self.inner.window().hide();
269	}
270
271	/// Executes the given javascript code and returns the output as a string.
272	/// If you don't need the result, see `exec_js`.
273	///
274	/// There may be some discrepancies in what JS values are being returned for
275	/// the same code in different browser engines, or how accurate they are.
276	/// For example, Edge WebView2 doesn't return `JsValue::Undefined`, it uses
277	/// `JsValue::Null` instead.
278	pub async fn eval_js(&self, js: &str) -> Result<JsValue, JsEvaluationError> {
279		//
280		let (tx, rx) = oneshot::channel();
281
282		self._eval_js(js, |_, result| {
283			// The callback of `_eval_js` is not necessarily called from our GUI thread, so
284			// this is a quickfix to make that safe. Otherwise, the `panic` may not be
285			// propegated correctly! FIXME: Call this result callback closure form the GUI
286			// thread from within the CEF implementation.
287			if let Err(_) = tx.send(result) {
288				panic!("Unable to send JavaScript result back")
289			}
290		});
291
292		rx.await.unwrap()
293	}
294
295	/// Executes the given JavaScript code, and provides the output via a
296	/// callback.
297	///
298	/// # Arguments
299	/// * `on_complete` - The closure that will be called when the output is
300	///   ready.
301	fn _eval_js<'a, H>(&self, js: &str, on_complete: H)
302	where
303		H: FnOnce(&BrowserWindowHandle, Result<JsValue, JsEvaluationError>) + 'a,
304	{
305		let data_ptr: *mut H = Box::into_raw(Box::new(on_complete));
306
307		self.inner
308			.eval_js(js.into(), eval_js_callback::<H>, data_ptr as _);
309	}
310
311	/// Executes the given javascript code without waiting on it to finish.
312	pub fn exec_js(&self, js: &str) { self._eval_js(js, |_, _| {}); }
313
314	/// Causes the browser to navigate to the given url.
315	pub fn navigate(&self, url: &str) { self.inner.navigate(url) }
316
317	pub fn url<'a>(&'a self) -> Cow<'a, str> { self.inner.url() }
318
319	pub fn window(&self) -> &WindowHandle { &self.window }
320}
321
322impl HasHandle<ApplicationHandle> for BrowserWindowHandle {
323	fn handle(&self) -> &ApplicationHandle { &self.app }
324}
325
326impl HasHandle<WindowHandle> for BrowserWindowHandle {
327	fn handle(&self) -> &WindowHandle { &self.window }
328}
329
330// Functions to reach the UI thread
331#[cfg(feature = "threadsafe")]
332impl BrowserWindowThreaded {
333	/// The thread-safe application handle associated with this browser window.
334	pub fn app(&self) -> ApplicationHandleThreaded {
335		ApplicationHandleThreaded::from_core_handle(self.0.inner.window().app())
336	}
337
338	/// Closes the browser.
339	pub fn close(self) -> bool { self.dispatch(|bw| unsafe { bw.clone().close() }) }
340
341	/// Executes the given closure within the GUI thread, and return the value
342	/// that the closure returned. Also see `ApplicationThreaded::delegate`.
343	///
344	/// The function signature is practically the same as:
345	/// ```ignore
346	/// pub async fn delegate<'a,F,R>( &self, func: F ) -> Result<R, DelegateError> where
347	/// 	F: FnOnce( BrowserWindowHandle ) -> R + Send + 'a,
348	/// 	R: Send { /*...*/ }
349	/// ```
350	pub fn delegate<'a, F, R>(&self, func: F) -> BrowserDelegateFuture<'a, R>
351	where
352		F: FnOnce(&BrowserWindowHandle) -> R + Send + 'a,
353		R: Send,
354	{
355		BrowserDelegateFuture::new(self.0.clone(), func)
356	}
357
358	/// Executes the given async closure `func` on the GUI thread, and gives
359	/// back the result when done.
360	/// Also see `ApplicationThreaded::delegate_async`.
361	pub fn delegate_async<'a, C, F, R>(&self, func: C) -> DelegateFutureFuture<'a, R>
362	where
363		C: FnOnce(BrowserWindow) -> F + Send + 'a,
364		F: Future<Output = R>,
365		R: Send + 'static,
366	{
367		let handle = self.0.clone();
368		DelegateFutureFuture::new(unsafe { self.app().handle.clone() }, async move {
369			func(handle.into()).await
370		})
371	}
372
373	/// Executes the given future on the GUI thread, and gives back the result
374	/// when done. Also see `ApplicationThreaded::delegate_future`.
375	pub fn delegate_future<'a, F, R>(&self, fut: F) -> DelegateFutureFuture<'a, R>
376	where
377		F: Future<Output = R> + Send + 'a,
378		R: Send + 'static,
379	{
380		DelegateFutureFuture::new(unsafe { self.app().handle.clone() }, fut)
381	}
382
383	/// Executes the given close on the GUI thread.
384	/// See also `Application::dispatch`.
385	pub fn dispatch<'a, F>(&self, func: F) -> bool
386	where
387		F: FnOnce(&BrowserWindowHandle) + Send + 'a,
388	{
389		let handle = UnsafeSend::new(self.0.clone());
390		// FIXME: It is more efficient to reimplement this for the browser window
391		self.app().dispatch(move |_| {
392			func(&handle.unwrap());
393		})
394	}
395
396	/// Executes the given closure on the GUI thread.
397	/// See also `Application::dispatch`.
398	pub fn dispatch_async<'a, C, F>(&self, func: C) -> bool
399	where
400		C: FnOnce(BrowserWindow) -> F + Send + 'a,
401		F: Future<Output = ()> + 'static,
402	{
403		let handle = UnsafeSend::new(self.0.clone());
404		self.app().dispatch(move |a| {
405			a.spawn(func(handle.unwrap()));
406		})
407	}
408}
409
410impl BrowserWindowHandle {
411	pub(crate) fn new(inner_handle: BrowserWindowImpl) -> Self {
412		Self {
413			app: ApplicationHandle::new(inner_handle.window().app()),
414			window: WindowHandle::new(inner_handle.window()),
415			inner: inner_handle,
416		}
417	}
418
419	#[cfg(feature = "threadsafe")]
420	unsafe fn clone(&self) -> Self {
421		Self {
422			app: self.app.clone(),
423			window: self.window.clone(),
424			inner: self.inner.clone(),
425		}
426	}
427}
428
429impl Deref for BrowserWindowHandle {
430	type Target = WindowHandle;
431
432	fn deref(&self) -> &Self::Target { &self.window }
433}
434
435impl HasAppHandle for BrowserWindowHandle {
436	fn app_handle(&self) -> &ApplicationHandle { &self.app }
437}
438
439impl BrowserWindowOwner {
440	fn cleanup(handle: &BrowserWindowHandle) {
441		handle.inner.free();
442		handle.inner.window().free();
443	}
444}
445
446impl Deref for BrowserWindowOwner {
447	type Target = BrowserWindowHandle;
448
449	fn deref(&self) -> &Self::Target { &self.0 }
450}
451
452impl Drop for BrowserWindowOwner {
453	fn drop(&mut self) {
454		#[cfg(not(feature = "threadsafe"))]
455		Self::cleanup(&self.0);
456		#[cfg(feature = "threadsafe")]
457		{
458			let bw = unsafe { UnsafeSend::new(self.0.clone()) };
459			self.app().into_threaded().dispatch(|_| {
460				Self::cleanup(&bw.unwrap());
461			});
462		}
463	}
464}
465
466
467fn eval_js_callback<H>(
468	_handle: BrowserWindowImpl, cb_data: *mut (), result: Result<JsValue, JsEvaluationError>,
469) where
470	H: FnOnce(&BrowserWindowHandle, Result<JsValue, JsEvaluationError>),
471{
472	let data_ptr = cb_data as *mut H;
473	let data = unsafe { Box::from_raw(data_ptr) };
474
475	let handle = BrowserWindowHandle::new(_handle);
476
477	(*data)(&handle, result);
478}