Skip to content

Commit f523b42

Browse files
committed
feat(ie-js): integrate Boa JS engine with console API #80
JsRuntime wrapping boa_engine::Context: - execute(source) — run JS, log errors via tracing - eval(source) — run JS, return string result - console.log/warn/error/info mapped to tracing targets console.rs: - register_console: creates console global with 4 methods - NativeFunction::from_copy_closure for each method - format_args_to_string: converts JsValue args to display strings execute_scripts helper: runs Vec<String> of script contents, collects errors without crashing. 7 tests: runtime creation, eval arithmetic, console methods, JS error handling, string operations, multi-script execution.
1 parent 35db724 commit f523b42

2 files changed

Lines changed: 157 additions & 5 deletions

File tree

crates/ie-js/src/console.rs

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
use boa_engine::{
2+
Context, JsValue, NativeFunction, js_string, object::ObjectInitializer, property::Attribute,
3+
};
4+
5+
/// Register the `console` global object with log/warn/error/info methods.
6+
pub fn register_console(context: &mut Context) {
7+
let log = NativeFunction::from_copy_closure(|_this, args, _ctx| {
8+
let msg = format_args_to_string(args);
9+
tracing::info!(target: "ie_js::console", "{msg}");
10+
Ok(JsValue::undefined())
11+
});
12+
13+
let warn = NativeFunction::from_copy_closure(|_this, args, _ctx| {
14+
let msg = format_args_to_string(args);
15+
tracing::warn!(target: "ie_js::console", "{msg}");
16+
Ok(JsValue::undefined())
17+
});
18+
19+
let error = NativeFunction::from_copy_closure(|_this, args, _ctx| {
20+
let msg = format_args_to_string(args);
21+
tracing::error!(target: "ie_js::console", "{msg}");
22+
Ok(JsValue::undefined())
23+
});
24+
25+
let info = NativeFunction::from_copy_closure(|_this, args, _ctx| {
26+
let msg = format_args_to_string(args);
27+
tracing::info!(target: "ie_js::console", "{msg}");
28+
Ok(JsValue::undefined())
29+
});
30+
31+
let console = ObjectInitializer::new(context)
32+
.function(log, js_string!("log"), 0)
33+
.function(warn, js_string!("warn"), 0)
34+
.function(error, js_string!("error"), 0)
35+
.function(info, js_string!("info"), 0)
36+
.build();
37+
38+
let _ = context.register_global_property(js_string!("console"), console, Attribute::all());
39+
}
40+
41+
fn format_args_to_string(args: &[JsValue]) -> String {
42+
args.iter()
43+
.map(|v| match v {
44+
JsValue::String(s) => s.to_std_string_escaped(),
45+
JsValue::Integer(n) => n.to_string(),
46+
JsValue::Rational(n) => n.to_string(),
47+
JsValue::Boolean(b) => b.to_string(),
48+
JsValue::Null => "null".to_string(),
49+
JsValue::Undefined => "undefined".to_string(),
50+
_ => format!("{v:?}"),
51+
})
52+
.collect::<Vec<_>>()
53+
.join(" ")
54+
}

crates/ie-js/src/lib.rs

Lines changed: 103 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,21 +3,119 @@
33
//! JavaScript engine integration using Boa.
44
//! Provides DOM bindings and Web API surface.
55
6+
mod console;
7+
68
use anyhow::Result;
7-
use boa_engine::Context;
9+
use boa_engine::{Context, Source};
810

911
pub struct JsRuntime {
10-
#[expect(dead_code)]
1112
context: Context,
1213
}
1314

1415
impl JsRuntime {
1516
pub fn new() -> Result<Self> {
16-
let context = Context::default();
17+
let mut context = Context::default();
18+
console::register_console(&mut context);
1719
Ok(Self { context })
1820
}
1921

20-
pub fn eval(&mut self, _source: &str) -> Result<()> {
21-
todo!("JS evaluation with DOM bindings")
22+
/// Execute a JavaScript source string. Returns Ok on success, Err on JS error.
23+
pub fn execute(&mut self, source: &str) -> Result<()> {
24+
match self.context.eval(Source::from_bytes(source)) {
25+
Ok(_) => Ok(()),
26+
Err(e) => {
27+
let msg = e.to_string();
28+
tracing::warn!("JS error: {msg}");
29+
Err(anyhow::anyhow!("JS error: {msg}"))
30+
}
31+
}
32+
}
33+
34+
/// Execute and return the string representation of the result.
35+
pub fn eval(&mut self, source: &str) -> Result<String> {
36+
match self.context.eval(Source::from_bytes(source)) {
37+
Ok(val) => {
38+
let result = val
39+
.to_string(&mut self.context)
40+
.map(|s| s.to_std_string_escaped())
41+
.unwrap_or_else(|_| "undefined".to_string());
42+
Ok(result)
43+
}
44+
Err(e) => {
45+
let msg = e.to_string();
46+
tracing::warn!("JS error: {msg}");
47+
Err(anyhow::anyhow!("JS error: {msg}"))
48+
}
49+
}
50+
}
51+
}
52+
53+
/// Execute all script contents from a parsed HTML page.
54+
pub fn execute_scripts(scripts: &[String]) -> Vec<String> {
55+
let mut errors = Vec::new();
56+
let Ok(mut runtime) = JsRuntime::new() else {
57+
errors.push("failed to create JS runtime".to_string());
58+
return errors;
59+
};
60+
for script in scripts {
61+
if let Err(e) = runtime.execute(script) {
62+
errors.push(e.to_string());
63+
}
64+
}
65+
errors
66+
}
67+
68+
#[cfg(test)]
69+
mod tests {
70+
use super::*;
71+
72+
#[test]
73+
fn runtime_creates() {
74+
JsRuntime::new().unwrap();
75+
}
76+
77+
#[test]
78+
fn simple_eval() {
79+
let mut rt = JsRuntime::new().unwrap();
80+
let result = rt.eval("1 + 2").unwrap();
81+
assert_eq!(result, "3");
82+
}
83+
84+
#[test]
85+
fn console_log_does_not_crash() {
86+
let mut rt = JsRuntime::new().unwrap();
87+
rt.execute("console.log('hello from JS')").unwrap();
88+
}
89+
90+
#[test]
91+
fn console_warn_error_info() {
92+
let mut rt = JsRuntime::new().unwrap();
93+
rt.execute("console.warn('warning'); console.error('error'); console.info('info')")
94+
.unwrap();
95+
}
96+
97+
#[test]
98+
fn js_error_does_not_panic() {
99+
let mut rt = JsRuntime::new().unwrap();
100+
let result = rt.execute("undefined_function()");
101+
assert!(result.is_err());
102+
}
103+
104+
#[test]
105+
fn string_operations() {
106+
let mut rt = JsRuntime::new().unwrap();
107+
let result = rt.eval("'hello' + ' ' + 'world'").unwrap();
108+
assert_eq!(result, "hello world");
109+
}
110+
111+
#[test]
112+
fn execute_scripts_helper() {
113+
let scripts = vec![
114+
"console.log('script 1')".to_string(),
115+
"var x = 42".to_string(),
116+
"bad syntax {{{{".to_string(),
117+
];
118+
let errors = execute_scripts(&scripts);
119+
assert_eq!(errors.len(), 1); // only the bad syntax one
22120
}
23121
}

0 commit comments

Comments
 (0)