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