deft_quick_js/lib.rs
1//! quick-js is a a Rust wrapper for [QuickJS](https://bellard.org/quickjs/), a new Javascript
2//! engine by Fabrice Bellard.
3//!
4//! It enables easy and straight-forward execution of modern Javascript from Rust.
5//!
6//! ## Limitations
7//!
8//! * Building on Windows requires the `x86_64-pc-windows-gnu` toolchain
9//!
10//! ## Quickstart:
11//!
12//! ```rust
13//! use deft_quick_js::{Context, JsValue};
14//!
15//! let context = Context::new().unwrap();
16//!
17//! // Eval.
18//!
19//! let value = context.eval("1 + 2").unwrap();
20//! assert_eq!(value, JsValue::Int(3));
21//!
22//! let value = context.eval_as::<String>(" var x = 100 + 250; x.toString() ").unwrap();
23//! assert_eq!(&value, "350");
24//!
25//! // Callbacks.
26//!
27//! context.add_callback("myCallback", |a: i32, b: i32| a + b).unwrap();
28//!
29//! context.eval(r#"
30//! // x will equal 30
31//! var x = myCallback(10, 20);
32//! "#).unwrap();
33//! ```
34
35// #![deny(missing_docs)]
36
37extern crate core;
38
39pub mod bindings;
40mod callback;
41pub mod console;
42mod value;
43
44#[cfg(test)]
45mod tests;
46pub mod loader;
47pub mod exception;
48
49use std::{convert::TryFrom, error, fmt};
50use std::any::Any;
51use libquickjs_sys::{JS_EVAL_TYPE_GLOBAL, JS_EVAL_TYPE_MODULE};
52use loader::JsModuleLoader;
53
54pub use self::{
55 callback::{Arguments, Callback},
56 value::*,
57};
58
59pub use libquickjs_sys;
60use crate::exception::HostPromiseRejectionTracker;
61
62/// Error on Javascript execution.
63#[derive(Debug)]
64pub enum ExecutionError {
65 /// Code to be executed contained zero-bytes.
66 InputWithZeroBytes,
67 /// Value conversion failed. (either input arguments or result value).
68 Conversion(ValueError),
69 /// Internal error.
70 Internal(String),
71 /// JS Exception was thrown.
72 Exception(JsValue),
73 /// JS Runtime exceeded the memory limit.
74 OutOfMemory,
75 #[doc(hidden)]
76 __NonExhaustive,
77}
78
79impl fmt::Display for ExecutionError {
80 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
81 use ExecutionError::*;
82 match self {
83 InputWithZeroBytes => write!(f, "Invalid script input: code contains zero byte (\\0)"),
84 Conversion(e) => e.fmt(f),
85 Internal(e) => write!(f, "Internal error: {}", e),
86 Exception(e) => write!(f, "{:?}", e),
87 OutOfMemory => write!(f, "Out of memory: runtime memory limit exceeded"),
88 __NonExhaustive => unreachable!(),
89 }
90 }
91}
92
93impl error::Error for ExecutionError {}
94
95impl From<ValueError> for ExecutionError {
96 fn from(v: ValueError) -> Self {
97 ExecutionError::Conversion(v)
98 }
99}
100
101/// Error on context creation.
102#[derive(Debug)]
103pub enum ContextError {
104 /// Runtime could not be created.
105 RuntimeCreationFailed,
106 /// Context could not be created.
107 ContextCreationFailed,
108 /// Execution error while building.
109 Execution(ExecutionError),
110 #[doc(hidden)]
111 __NonExhaustive,
112}
113
114impl fmt::Display for ContextError {
115 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
116 use ContextError::*;
117 match self {
118 RuntimeCreationFailed => write!(f, "Could not create runtime"),
119 ContextCreationFailed => write!(f, "Could not create context"),
120 Execution(e) => e.fmt(f),
121 __NonExhaustive => unreachable!(),
122 }
123 }
124}
125
126impl error::Error for ContextError {}
127
128/// A builder for [Context](Context).
129///
130/// Create with [Context::builder](Context::builder).
131pub struct ContextBuilder {
132 memory_limit: Option<usize>,
133 console_backend: Option<Box<dyn console::ConsoleBackend>>,
134 module_loader: Option<Box<dyn JsModuleLoader>>,
135}
136
137impl ContextBuilder {
138 fn new() -> Self {
139 Self {
140 memory_limit: None,
141 console_backend: None,
142 module_loader: None,
143 }
144 }
145
146 /// Sets the memory limit of the Javascript runtime (in bytes).
147 ///
148 /// If the limit is exceeded, methods like `eval` will return
149 /// a `Err(ExecutionError::Exception(JsValue::Null))`
150 // TODO: investigate why we don't get a proper exception message here.
151 pub fn memory_limit(self, max_bytes: usize) -> Self {
152 let mut s = self;
153 s.memory_limit = Some(max_bytes);
154 s
155 }
156
157 /// Set a console handler that will proxy `console.{log,trace,debug,...}`
158 /// calls.
159 ///
160 /// The given argument must implement the [console::ConsoleBackend] trait.
161 ///
162 /// A very simple logger could look like this:
163 pub fn console<B>(mut self, backend: B) -> Self
164 where
165 B: console::ConsoleBackend,
166 {
167 self.console_backend = Some(Box::new(backend));
168 self
169 }
170
171 /// Set js module loader
172 pub fn module_loader<L>(mut self, loader: L) -> Self
173 where
174 L: JsModuleLoader,
175 {
176 self.module_loader = Some(Box::new(loader));
177 self
178 }
179
180 /// Finalize the builder and build a JS Context.
181 pub fn build(self) -> Result<Context, ContextError> {
182 let mut wrapper = bindings::ContextWrapper::new(self.memory_limit)?;
183 if let Some(be) = self.console_backend {
184 wrapper.set_console(be).map_err(ContextError::Execution)?;
185 }
186 if let Some(ml) = self.module_loader {
187 wrapper.set_module_loader(ml);
188 }
189 Ok(Context::from_wrapper(wrapper))
190 }
191}
192
193/// Context is a wrapper around a QuickJS Javascript context.
194/// It is the primary way to interact with the runtime.
195///
196/// For each `Context` instance a new instance of QuickJS
197/// runtime is created. It means that it is safe to use
198/// different contexts in different threads, but each
199/// `Context` instance must be used only from a single thread.
200pub struct Context {
201 wrapper: bindings::ContextWrapper,
202}
203
204impl Context {
205 fn from_wrapper(wrapper: bindings::ContextWrapper) -> Self {
206 Self { wrapper }
207 }
208
209 /// Create a `ContextBuilder` that allows customization of JS Runtime settings.
210 ///
211 /// For details, see the methods on `ContextBuilder`.
212 ///
213 /// ```rust
214 /// let _context = deft_quick_js::Context::builder()
215 /// .memory_limit(100_000)
216 /// .build()
217 /// .unwrap();
218 /// ```
219 pub fn builder() -> ContextBuilder {
220 ContextBuilder::new()
221 }
222
223 /// Create a new Javascript context with default settings.
224 pub fn new() -> Result<Self, ContextError> {
225 let wrapper = bindings::ContextWrapper::new(None)?;
226 Ok(Self::from_wrapper(wrapper))
227 }
228
229 /// Reset the Javascript engine.
230 ///
231 /// All state and callbacks will be removed.
232 pub fn reset(self) -> Result<Self, ContextError> {
233 let wrapper = self.wrapper.reset()?;
234 Ok(Self { wrapper })
235 }
236
237 /// Evaluates Javascript code and returns the value of the final expression.
238 ///
239 /// **Promises**:
240 /// If the evaluated code returns a Promise, the event loop
241 /// will be executed until the promise is finished. The final value of
242 /// the promise will be returned, or a `ExecutionError::Exception` if the
243 /// promise failed.
244 ///
245 /// ```rust
246 /// use deft_quick_js::{Context, JsValue};
247 /// let context = Context::new().unwrap();
248 ///
249 /// let value = context.eval(" 1 + 2 + 3 ");
250 /// assert_eq!(
251 /// value,
252 /// Ok(JsValue::Int(6)),
253 /// );
254 ///
255 /// let value = context.eval(r#"
256 /// function f() { return 55 * 3; }
257 /// let y = f();
258 /// var x = y.toString() + "!"
259 /// x
260 /// "#);
261 /// assert_eq!(
262 /// value,
263 /// Ok(JsValue::String("165!".to_string())),
264 /// );
265 /// ```
266 pub fn eval(&self, code: &str, filename: &str) -> Result<JsValue, ExecutionError> {
267 let value_raw = self.wrapper.eval(code, JS_EVAL_TYPE_GLOBAL, filename)?;
268 let value = value_raw.to_value()?;
269 Ok(value)
270 }
271
272 /// Eval as module
273 pub fn eval_module(&self, code: &str, filename: &str) -> Result<JsValue, ExecutionError> {
274 let value_raw = self.wrapper.eval(code, JS_EVAL_TYPE_MODULE, filename)?;
275 let value = value_raw.to_value()?;
276 Ok(value)
277 }
278
279 /// Evaluates Javascript code and returns the value of the final expression
280 /// as a Rust type.
281 ///
282 /// **Promises**:
283 /// If the evaluated code returns a Promise, the event loop
284 /// will be executed until the promise is finished. The final value of
285 /// the promise will be returned, or a `ExecutionError::Exception` if the
286 /// promise failed.
287 ///
288 /// ```rust
289 /// use deft_quick_js::{Context};
290 /// let context = Context::new().unwrap();
291 ///
292 /// let res = context.eval_as::<bool>(" 100 > 10 ");
293 /// assert_eq!(
294 /// res,
295 /// Ok(true),
296 /// );
297 ///
298 /// let value: i32 = context.eval_as(" 10 + 10 ").unwrap();
299 /// assert_eq!(
300 /// value,
301 /// 20,
302 /// );
303 /// ```
304 pub fn eval_as<R>(&self, code: &str, filename: &str) -> Result<R, ExecutionError>
305 where
306 R: TryFrom<JsValue>,
307 R::Error: Into<ValueError>,
308 {
309 let value_raw = self.wrapper.eval(code, JS_EVAL_TYPE_GLOBAL,filename)?;
310 let value = value_raw.to_value()?;
311 let ret = R::try_from(value).map_err(|e| e.into())?;
312 Ok(ret)
313 }
314
315 /// Set a global variable.
316 ///
317 /// ```rust
318 /// use deft_quick_js::{Context, JsValue};
319 /// let context = Context::new().unwrap();
320 ///
321 /// context.set_global("someGlobalVariable", 42).unwrap();
322 /// let value = context.eval_as::<i32>("someGlobalVariable").unwrap();
323 /// assert_eq!(
324 /// value,
325 /// 42,
326 /// );
327 /// ```
328 pub fn set_global<V>(&self, name: &str, value: V) -> Result<(), ExecutionError>
329 where
330 V: Into<JsValue>,
331 {
332 let global = self.wrapper.global()?;
333 let v = self.wrapper.serialize_value(value.into())?;
334 global.set_property(name, v)?;
335 Ok(())
336 }
337
338 /// Call a global function in the Javascript namespace.
339 ///
340 /// **Promises**:
341 /// If the evaluated code returns a Promise, the event loop
342 /// will be executed until the promise is finished. The final value of
343 /// the promise will be returned, or a `ExecutionError::Exception` if the
344 /// promise failed.
345 ///
346 /// ```rust
347 /// use deft_quick_js::{Context, JsValue};
348 /// let context = Context::new().unwrap();
349 ///
350 /// let res = context.call_function("encodeURIComponent", vec!["a=b"]);
351 /// assert_eq!(
352 /// res,
353 /// Ok(JsValue::String("a%3Db".to_string())),
354 /// );
355 /// ```
356 pub fn call_function(
357 &self,
358 function_name: &str,
359 args: impl IntoIterator<Item = impl Into<JsValue>>,
360 ) -> Result<JsValue, ExecutionError> {
361 let qargs = args
362 .into_iter()
363 .map(|arg| self.wrapper.serialize_value(arg.into()))
364 .collect::<Result<Vec<_>, _>>()?;
365
366 let global = self.wrapper.global()?;
367 let func = global
368 .property_require(function_name)?
369 .try_into_function()?;
370 let v = self.wrapper.call_function(func, qargs)?.to_value()?;
371 Ok(v)
372 }
373
374 /// Call a js function
375 pub fn call_js_function(
376 &self,
377 function: impl Into<JsValue>,
378 args: impl IntoIterator<Item = impl Into<JsValue>>,
379 ) -> Result<JsValue, ExecutionError> {
380 let qargs = args
381 .into_iter()
382 .map(|arg| self.wrapper.serialize_value(arg.into()))
383 .collect::<Result<Vec<_>, _>>()?;
384 let func = self.wrapper.serialize_value(function.into())?.try_into_function()?;
385 let v = self.wrapper.call_function(func, qargs)?.to_value()?;
386 Ok(v)
387 }
388
389 /// Add a global JS function that is backed by a Rust function or closure.
390 ///
391 /// The callback must satisfy several requirements:
392 /// * accepts 0 - 5 arguments
393 /// * each argument must be convertible from a JsValue
394 /// * must return a value
395 /// * the return value must either:
396 /// - be convertible to JsValue
397 /// - be a Result<T, E> where T is convertible to JsValue
398 /// if Err(e) is returned, a Javascript exception will be raised
399 ///
400 /// ```rust
401 /// use deft_quick_js::{Context, JsValue};
402 /// let context = Context::new().unwrap();
403 ///
404 /// // Register a closue as a callback under the "add" name.
405 /// // The 'add' function can now be called from Javascript code.
406 /// context.add_callback("add", |a: i32, b: i32| { a + b }).unwrap();
407 ///
408 /// // Now we try out the 'add' function via eval.
409 /// let output = context.eval_as::<i32>(" add( 3 , 4 ) ").unwrap();
410 /// assert_eq!(
411 /// output,
412 /// 7,
413 /// );
414 /// ```
415 pub fn add_callback<F>(
416 &self,
417 name: &str,
418 callback: impl Callback<F> + 'static,
419 ) -> Result<(), ExecutionError> {
420 self.wrapper.add_callback(name, callback)
421 }
422
423 pub fn execute_pending_job(&self) -> Result<bool, ExecutionError> {
424 self.wrapper.execute_pending_job()
425 }
426
427 /// Execute module
428 pub fn execute_module(&self, module_name: &str) -> Result<(), ExecutionError> {
429 self.wrapper.execute_module(module_name)
430 }
431
432 pub fn set_promise_rejection_tracker<F: HostPromiseRejectionTracker + 'static>(&mut self, tracker: F) {
433 self.wrapper.set_host_promise_rejection_tracker(tracker);
434 }
435
436}