1use std::cell::RefCell;
38use std::rc::Rc;
39
40use boa_engine::object::ObjectInitializer;
41use boa_engine::property::Attribute;
42use boa_engine::{js_string, Context, JsError, JsResult, JsString, JsValue, NativeFunction, Source};
43use serde_json::Value;
44
45pub use boa_engine;
49
50pub const LOOP_ITERATION_LIMIT: u64 = 10_000_000;
52
53pub const RECURSION_LIMIT: usize = 512;
55
56pub type Logs = Rc<RefCell<Vec<String>>>;
59
60#[derive(Debug, Clone)]
62pub struct Success {
63 pub value: Value,
65 pub logs: Vec<String>,
66}
67
68#[derive(Debug, Clone)]
72pub struct Failure {
73 pub message: String,
74 pub logs: Vec<String>,
75}
76
77pub fn call(
83 source: &str,
84 file: &str,
85 function: &str,
86 input: &Value,
87 install: &dyn Fn(&mut Context, &Logs) -> JsResult<()>,
88) -> Result<Success, Failure> {
89 let mut context = Context::default();
90 let limits = context.runtime_limits_mut();
91 limits.set_loop_iteration_limit(LOOP_ITERATION_LIMIT);
92 limits.set_recursion_limit(RECURSION_LIMIT);
93
94 let logs: Logs = Rc::new(RefCell::new(Vec::new()));
95 let fail = |message: String, logs: &Logs| Failure {
96 message,
97 logs: logs.borrow().clone(),
98 };
99
100 if let Err(error) = install_console(&mut context, &logs).and_then(|()| install(&mut context, &logs)) {
101 return Err(fail(format!("setting up the script host failed: {}", describe(error, &mut context)), &logs));
102 }
103
104 let path = std::path::Path::new(file);
105 if let Err(error) = context.eval(Source::from_bytes(source).with_path(path)) {
106 let message = describe(error, &mut context);
107 return Err(fail(format!("{file}: {message}"), &logs));
108 }
109
110 let global = context.global_object();
111 let callee = match global.get(JsString::from(function), &mut context) {
112 Ok(value) => value,
113 Err(error) => return Err(fail(describe(error, &mut context), &logs)),
114 };
115 let Some(callee) = callee.as_callable() else {
116 return Err(fail(format!("{file} defines no function {function}()"), &logs));
117 };
118
119 let argument = match JsValue::from_json(input, &mut context) {
120 Ok(value) => value,
121 Err(error) => return Err(fail(describe(error, &mut context), &logs)),
122 };
123 let returned = match callee.call(&JsValue::undefined(), &[argument], &mut context) {
124 Ok(value) => value,
125 Err(error) => return Err(fail(describe(error, &mut context), &logs)),
126 };
127 let _ = context.run_jobs();
130
131 let value = match returned.to_json(&mut context) {
132 Ok(json) => json.unwrap_or(Value::Null),
133 Err(error) => {
134 return Err(fail(
135 format!("{function}() returned a value that is not JSON: {}", describe(error, &mut context)),
136 &logs,
137 ))
138 }
139 };
140 let logs = logs.borrow().clone();
141 Ok(Success { value, logs })
142}
143
144pub fn describe(error: JsError, context: &mut Context) -> String {
146 if let Some(value) = error.as_opaque() {
147 if let Some(text) = value.as_string() {
148 return text.to_std_string_escaped();
149 }
150 }
151 match error.try_native(context) {
152 Ok(native) if !native.message().is_empty() => native.message().to_string(),
153 Ok(native) => native.to_string(),
154 Err(_) => {
155 if let Some(value) = error.as_opaque() {
157 if let Ok(Some(json)) = value.to_json(context) {
158 return json.to_string();
159 }
160 }
161 error.to_string()
162 }
163 }
164}
165
166pub fn text_of(value: &JsValue, context: &mut Context) -> String {
169 if let Some(text) = value.as_string() {
170 return text.to_std_string_escaped();
171 }
172 match value.to_json(context) {
173 Ok(Some(json)) => json.to_string(),
174 Ok(None) => "undefined".to_string(),
175 Err(_) => value.display().to_string(),
176 }
177}
178
179fn install_console(context: &mut Context, logs: &Logs) -> JsResult<()> {
181 let make = |prefix: &'static str| {
182 let logs = logs.clone();
183 unsafe {
187 NativeFunction::from_closure(move |_, args, context| {
188 let line = args
189 .iter()
190 .map(|value| text_of(value, context))
191 .collect::<Vec<_>>()
192 .join(" ");
193 logs.borrow_mut().push(format!("{prefix}{line}"));
194 Ok(JsValue::undefined())
195 })
196 }
197 };
198 let console = ObjectInitializer::new(context)
199 .function(make(""), js_string!("log"), 0)
200 .function(make(""), js_string!("info"), 0)
201 .function(make("warn: "), js_string!("warn"), 0)
202 .function(make("error: "), js_string!("error"), 0)
203 .build();
204 context.register_global_property(js_string!("console"), console, Attribute::all())
205}
206
207
208pub mod modules;
209pub mod worker;