rquickjs_core/context/
base.rs1use super::{ctx::RefCountHeader, intrinsic, r#ref::ContextRef, ContextBuilder, Intrinsic};
2use crate::{qjs, Ctx, Error, Result, Runtime};
3use std::{mem, ptr::NonNull};
4
5pub(crate) struct Inner {
6 pub(crate) ctx: NonNull<qjs::JSContext>,
7 pub(crate) rt: Runtime,
8}
9
10impl Clone for Inner {
11 fn clone(&self) -> Inner {
12 let ctx = unsafe { NonNull::new_unchecked(qjs::JS_DupContext(self.ctx.as_ptr())) };
13 let rt = self.rt.clone();
14 Self { ctx, rt }
15 }
16}
17
18#[derive(Clone)]
22pub struct Context(pub(crate) ContextRef<Inner>);
23
24impl Context {
25 pub unsafe fn from_raw(ctx: NonNull<qjs::JSContext>, rt: Runtime) -> Self {
32 Context(ContextRef::new(Inner { ctx, rt }))
33 }
34
35 pub fn as_raw(&self) -> NonNull<qjs::JSContext> {
36 self.0.ctx
37 }
38
39 pub fn base(runtime: &Runtime) -> Result<Self> {
43 Self::custom::<intrinsic::None>(runtime)
44 }
45
46 pub fn custom<I: Intrinsic>(runtime: &Runtime) -> Result<Self> {
50 let guard = runtime.inner.lock();
51 let ctx = NonNull::new(unsafe { qjs::JS_NewContextRaw(guard.rt.as_ptr()) })
52 .ok_or_else(|| Error::Allocation)?;
53 unsafe { qjs::JS_AddIntrinsicBaseObjects(ctx.as_ptr()) };
55 unsafe { I::add_intrinsic(ctx) };
56 let res = Inner {
57 ctx,
58 rt: runtime.clone(),
59 };
60 mem::drop(guard);
61
62 Ok(Context(ContextRef::new(res)))
63 }
64
65 pub fn full(runtime: &Runtime) -> Result<Self> {
69 let guard = runtime.inner.lock();
70 let ctx = NonNull::new(unsafe { qjs::JS_NewContext(guard.rt.as_ptr()) })
71 .ok_or_else(|| Error::Allocation)?;
72 let res = Inner {
73 ctx,
74 rt: runtime.clone(),
75 };
76 mem::drop(guard);
78
79 Ok(Context(ContextRef::new(res)))
80 }
81
82 pub fn builder() -> ContextBuilder<()> {
84 ContextBuilder::default()
85 }
86
87 pub fn runtime(&self) -> &Runtime {
89 &self.0.rt
90 }
91
92 #[allow(dead_code)]
93 pub fn get_runtime_ptr(&self) -> *mut qjs::JSRuntime {
94 unsafe { qjs::JS_GetRuntime(self.0.ctx.as_ptr()) }
95 }
96
97 pub fn with<F, R>(&self, f: F) -> R
106 where
107 F: FnOnce(Ctx) -> R,
108 {
109 let guard = self.0.rt.inner.lock();
110 guard.update_stack_top();
111 let ctx = unsafe { Ctx::new(self) };
112 f(ctx)
113 }
114}
115
116impl Drop for Context {
117 fn drop(&mut self) {
118 let guard = match self.0.rt.inner.try_lock() {
120 Some(x) => x,
121 None => {
122 let p = unsafe { &mut *(self.0.ctx.as_ptr() as *mut RefCountHeader) };
123 if p.ref_count <= 1 {
124 assert!(std::thread::panicking());
129 }
130 unsafe { qjs::JS_FreeContext(self.0.ctx.as_ptr()) }
131 return;
132 }
133 };
134 guard.update_stack_top();
135 unsafe { qjs::JS_FreeContext(self.0.ctx.as_ptr()) }
136 mem::drop(guard);
138 }
139}
140
141#[cfg(feature = "parallel")]
144unsafe impl Send for Context {}
145
146#[cfg(feature = "parallel")]
149unsafe impl Sync for Context {}
150
151#[cfg(test)]
152mod test {
153 use super::*;
154 use crate::*;
155
156 #[test]
157 fn basic() {
158 test_with(|ctx| {
159 let val: Value = ctx.eval(r#"1+1"#).unwrap();
160
161 assert_eq!(val.type_of(), Type::Int);
162 assert_eq!(i32::from_js(&ctx, val).unwrap(), 2);
163 println!("{:?}", ctx.globals());
164 });
165 }
166
167 #[test]
168 fn minimal() {
169 let rt = Runtime::new().unwrap();
170 let ctx = Context::builder()
171 .with::<intrinsic::Eval>()
172 .build(&rt)
173 .unwrap();
174 ctx.with(|ctx| {
175 let val: i32 = ctx.eval(r#"1+1"#).unwrap();
176
177 assert_eq!(val, 2);
178 println!("{:?}", ctx.globals());
179 });
180 }
181
182 #[test]
183 fn base() {
184 let rt = Runtime::new().unwrap();
185 let _ = Context::base(&rt).unwrap();
186 }
187
188 #[test]
189 fn module() {
190 test_with(|ctx| {
191 Module::evaluate(
192 ctx,
193 "test_mod",
194 r#"
195 let t = "3";
196 let b = (a) => a + 3;
197 export { b, t}
198 "#,
199 )
200 .unwrap()
201 .finish::<()>()
202 .unwrap();
203 });
204 }
205
206 #[test]
207 #[cfg(feature = "parallel")]
208 fn parallel() {
209 use std::thread;
210
211 let rt = Runtime::new().unwrap();
212 let ctx = Context::full(&rt).unwrap();
213 ctx.with(|ctx| {
214 let _: () = ctx.eval("this.foo = 42").unwrap();
215 });
216 thread::spawn(move || {
217 ctx.with(|ctx| {
218 let i: i32 = ctx.eval("foo + 8").unwrap();
219 assert_eq!(i, 50);
220 });
221 })
222 .join()
223 .unwrap();
224 }
225
226 #[test]
227 #[cfg(feature = "parallel")]
228 fn parallel_drop() {
229 use std::{
230 sync::{Arc, Barrier},
231 thread,
232 };
233
234 let wait_for_entry = Arc::new(Barrier::new(2));
235
236 let rt = Runtime::new().unwrap();
237 let ctx_1 = Context::full(&rt).unwrap();
238 let ctx_2 = Context::full(&rt).unwrap();
239 let wait_for_entry_c = wait_for_entry.clone();
240 thread::spawn(move || {
241 wait_for_entry_c.wait();
242 std::mem::drop(ctx_1);
243 println!("done");
244 });
245
246 ctx_2.with(|ctx| {
247 wait_for_entry.wait();
248 let i: i32 = ctx.eval("2 + 8").unwrap();
249 assert_eq!(i, 10);
250 });
251 println!("done");
252 }
253
254 #[test]
256 #[should_panic(
257 expected = "Error: invalid first character of private name\n at eval_script:1:1\n"
258 )]
259 fn exception() {
260 test_with(|ctx| {
261 let val = ctx.eval::<(), _>("bla?#@!@ ").catch(&ctx);
262 if let Err(e) = val {
263 assert!(e.is_exception());
264 panic!("{}", e);
265 }
266 });
267 }
268}