ferridriver_script/bindings/
process.rs1use std::time::Instant;
11
12use rquickjs::function::{Func, Rest};
13use rquickjs::{Ctx, Object, Value};
14
15use crate::engine::ScriptCaps;
16
17pub fn install(ctx: &Ctx<'_>, caps: &ScriptCaps, cwd: &str) -> rquickjs::Result<()> {
21 let g = ctx.globals();
22 let p = Object::new(ctx.clone())?;
23
24 let env = Object::new(ctx.clone())?;
26 for (k, v) in &caps.env {
27 env.set(k.as_str(), v.as_str())?;
28 }
29 freeze(ctx, &env)?;
32 p.set("env", env)?;
33
34 p.set("platform", std::env::consts::OS)?; p.set("arch", std::env::consts::ARCH)?; let fv = env!("CARGO_PKG_VERSION");
38 p.set("version", format!("ferridriver-{fv}"))?;
39 let versions = Object::new(ctx.clone())?;
40 versions.set("ferridriver", fv)?;
41 versions.set("quickjs", "rquickjs-0.11")?;
42 freeze(ctx, &versions)?;
43 p.set("versions", versions)?;
44 let release = Object::new(ctx.clone())?;
45 release.set("name", "ferridriver")?;
46 freeze(ctx, &release)?;
47 p.set("release", release)?;
48
49 let argv = rquickjs::Array::new(ctx.clone())?;
52 argv.set(0, "ferridriver")?;
53 argv.set(1, "script")?;
54 p.set("argv", argv)?;
55 p.set("argv0", "ferridriver")?;
56 p.set("pid", i64::from(std::process::id()))?;
57
58 let root = cwd.to_string();
60 p.set("cwd", Func::from(move || root.clone()))?;
61
62 let next_tick = ctx.eval::<Value<'_>, _>(
64 "((cb, ...a) => { if (typeof cb !== 'function') throw new TypeError('callback required'); \
65 queueMicrotask(() => cb(...a)); })",
66 )?;
67 p.set("nextTick", next_tick)?;
68
69 for (name, level) in [("stdout", "log"), ("stderr", "error")] {
75 let stream = Object::new(ctx.clone())?;
76 let f = rquickjs::Function::new(
77 ctx.clone(),
78 move |c: Ctx<'_>, chunk: Value<'_>| -> rquickjs::Result<bool> {
79 let s = chunk
80 .as_string()
81 .and_then(|v| v.to_string().ok())
82 .or_else(|| chunk.as_number().map(|n| n.to_string()))
83 .unwrap_or_default();
84 let s = s.strip_suffix('\n').unwrap_or(&s).to_string();
85 let console: Object<'_> = c.globals().get("console")?;
86 let sink: rquickjs::Function<'_> = console.get(level)?;
87 sink.call::<_, ()>((s,))?;
88 Ok(true)
89 },
90 )?;
91 stream.set("write", f)?;
92 stream.set("isTTY", false)?;
93 p.set(name, stream)?;
94 }
95
96 let start = Instant::now();
99 let hrtime = rquickjs::Function::new(ctx.clone(), move |prev: Rest<Value<'_>>| -> Vec<i64> {
100 let now = start.elapsed();
101 let (mut s, mut n) = (
102 i64::try_from(now.as_secs()).unwrap_or(i64::MAX),
103 i64::from(now.subsec_nanos()),
104 );
105 if let Some(arr) = prev.0.first().and_then(|v| v.as_array()) {
106 let ps = arr.get::<i64>(0).unwrap_or(0);
107 let pn = arr.get::<i64>(1).unwrap_or(0);
108 s -= ps;
109 n -= pn;
110 if n < 0 {
111 s -= 1;
112 n += 1_000_000_000;
113 }
114 }
115 vec![s, n]
116 })?;
117 let bigint = rquickjs::Function::new(ctx.clone(), move |c| hrtime_bigint(c, start))?;
120 hrtime.set("bigint", bigint)?;
121 p.set("hrtime", hrtime)?;
122
123 p.set(
126 "exit",
127 Func::from(|code: Rest<Value<'_>>| -> rquickjs::Result<()> {
128 let c = code.0.first().and_then(rquickjs::Value::as_int).unwrap_or(0);
129 Err(rquickjs::Error::new_from_js_message(
130 "process.exit",
131 "Error",
132 format!("process.exit({c}) is not allowed in the ferridriver sandbox"),
133 ))
134 }),
135 )?;
136
137 g.set("process", p)?;
138 Ok(())
139}
140
141fn hrtime_bigint(ctx: Ctx<'_>, start: Instant) -> rquickjs::Result<Value<'_>> {
144 let nanos = u64::try_from(start.elapsed().as_nanos()).unwrap_or(u64::MAX);
145 Ok(rquickjs::BigInt::from_u64(ctx, nanos)?.into_value())
146}
147
148fn freeze<'js>(ctx: &Ctx<'js>, obj: &Object<'js>) -> rquickjs::Result<()> {
149 let freeze: rquickjs::Function<'js> = ctx.globals().get::<_, Object<'js>>("Object")?.get("freeze")?;
150 freeze.call::<_, Value<'js>>((obj.clone(),))?;
151 Ok(())
152}