aidoku/imports/
js.rs

1//! Module for running JavaScript and managing web views.
2use super::{
3	FFIResult, Rid,
4	net::Request,
5	std::{destroy, read_string_and_destroy},
6};
7use crate::alloc::String;
8
9#[link(wasm_import_module = "js")]
10unsafe extern "C" {
11	fn context_create() -> Rid;
12	fn context_eval(context: Rid, string_ptr: *const u8, len: usize) -> FFIResult;
13	fn context_get(context: Rid, string_ptr: *const u8, len: usize) -> FFIResult;
14
15	fn webview_create() -> Rid;
16	fn webview_load(webview: Rid, request: Rid) -> FFIResult;
17	fn webview_load_html(
18		webview: Rid,
19		string_ptr: *const u8,
20		len: usize,
21		url_ptr: *const u8,
22		url_len: usize,
23	) -> FFIResult;
24	fn webview_wait_for_load(webview: Rid) -> FFIResult;
25	fn webview_eval(webview: Rid, string_ptr: *const u8, len: usize) -> FFIResult;
26}
27
28/// Error type for JavaScript operations.
29#[derive(PartialEq, Eq, Debug, Clone)]
30pub enum JsError {
31	MissingResult,
32	InvalidContext,
33	InvalidString,
34	InvalidHandler,
35	InvalidRequest,
36}
37
38impl JsError {
39	fn from(value: FFIResult) -> Option<Self> {
40		match value {
41			-1 => Some(Self::MissingResult),
42			-2 => Some(Self::InvalidContext),
43			-3 => Some(Self::InvalidString),
44			-4 => Some(Self::InvalidHandler),
45			-5 => Some(Self::InvalidRequest),
46			_ => None,
47		}
48	}
49}
50
51/// A context for evaluating JavaScript code.
52pub struct JsContext {
53	rid: Rid,
54}
55
56impl JsContext {
57	/// Creates a new JavaScript context.
58	pub fn new() -> Self {
59		let rid = unsafe { context_create() };
60		Self { rid }
61	}
62
63	/// Evaluates JavaScript code in the context.
64	pub fn eval(&self, js: &str) -> Result<String, JsError> {
65		let js_bytes = js.as_bytes();
66		let result = unsafe { context_eval(self.rid, js_bytes.as_ptr(), js_bytes.len()) };
67		if let Some(error) = JsError::from(result) {
68			Err(error)
69		} else {
70			Ok(read_string_and_destroy(result).unwrap_or_default())
71		}
72	}
73
74	/// Retrieves the value of a JavaScript variable in the context.
75	pub fn get(&self, variable: &str) -> Result<String, JsError> {
76		let var_bytes = variable.as_bytes();
77		let result = unsafe { context_get(self.rid, var_bytes.as_ptr(), var_bytes.len()) };
78		if let Some(error) = JsError::from(result) {
79			Err(error)
80		} else {
81			Ok(read_string_and_destroy(result).unwrap_or_default())
82		}
83	}
84}
85
86impl Default for JsContext {
87	fn default() -> Self {
88		Self::new()
89	}
90}
91
92impl Drop for JsContext {
93	fn drop(&mut self) {
94		unsafe { destroy(self.rid) }
95	}
96}
97
98/// A web view that can be used to load web content.
99///
100/// This web view won't be displayed to the user. It is intended for use in the background.
101pub struct WebView {
102	rid: Rid,
103}
104
105impl WebView {
106	/// Creates a new web view.
107	pub fn new() -> Self {
108		let rid = unsafe { webview_create() };
109		Self { rid }
110	}
111
112	/// Loads a web page in the web view.
113	pub fn load(&self, request: Request) -> Result<(), JsError> {
114		let request_descriptor = request.rid;
115		let result = unsafe { webview_load(self.rid, request_descriptor) };
116		if let Some(error) = JsError::from(result) {
117			Err(error)
118		} else {
119			Ok(())
120		}
121	}
122
123	/// Loads a web page in the web view, blocking until the page is loaded.
124	pub fn load_blocking(&self, request: Request) -> Result<(), JsError> {
125		self.load(request)?;
126		self.wait_for_load();
127		Ok(())
128	}
129
130	/// Loads the given HTML content in the web view.
131	pub fn load_html(&self, html: &str, base_url: Option<&str>) -> Result<(), JsError> {
132		let html_bytes = html.as_bytes();
133		let url_bytes = base_url.map(|s| s.as_bytes()).unwrap_or_default();
134		let result = unsafe {
135			webview_load_html(
136				self.rid,
137				html_bytes.as_ptr(),
138				html_bytes.len(),
139				url_bytes.as_ptr(),
140				url_bytes.len(),
141			)
142		};
143		if let Some(error) = JsError::from(result) {
144			Err(error)
145		} else {
146			Ok(())
147		}
148	}
149
150	/// Loads HTML content in the web view, blocking until the content is loaded.
151	pub fn load_html_blocking(&self, html: &str, base_url: Option<&str>) -> Result<(), JsError> {
152		self.load_html(html, base_url)?;
153		self.wait_for_load();
154		Ok(())
155	}
156
157	/// Blocks the current thread until the web view is loaded.
158	pub fn wait_for_load(&self) {
159		unsafe { webview_wait_for_load(self.rid) };
160	}
161
162	/// Evaluates JavaScript code in the web view, blocking until the result is available.
163	pub fn eval(&self, js: &str) -> Result<String, JsError> {
164		let js_bytes = js.as_bytes();
165		let result = unsafe { webview_eval(self.rid, js_bytes.as_ptr(), js_bytes.len()) };
166		if let Some(error) = JsError::from(result) {
167			Err(error)
168		} else {
169			Ok(read_string_and_destroy(result).unwrap_or_default())
170		}
171	}
172}
173
174impl Default for WebView {
175	fn default() -> Self {
176		Self::new()
177	}
178}
179
180impl Drop for WebView {
181	fn drop(&mut self) {
182		unsafe { destroy(self.rid) }
183	}
184}