1use crate::*;
2use std::any::Any;
3use std::cell::RefCell;
4use std::collections::BTreeMap;
5use std::rc::Rc;
6use std::string::String as StdString;
7use std::sync::{Arc, Condvar, Mutex, Once};
8use std::thread;
9use std::time::Duration;
10
11#[derive(Clone)]
12pub struct MiniV8 {
13 interface: Interface,
14}
15
16impl MiniV8 {
17 pub fn new() -> MiniV8 {
18 initialize_v8();
19 let mut isolate = v8::Isolate::new(Default::default());
20 initialize_slots(&mut isolate);
21 MiniV8 { interface: Interface::new(isolate) }
22 }
23
24 pub fn global(&self) -> Object {
26 self.scope(|scope| {
27 let global = scope.get_current_context().global(scope);
28 Object {
29 mv8: self.clone(),
30 handle: v8::Global::new(scope, global),
31 }
32 })
33 }
34
35 pub fn eval<S, R>(&self, script: S) -> Result<R>
37 where
38 S: Into<Script>,
39 R: FromValue,
40 {
41 let script = script.into();
42 let isolate_handle = self.interface.isolate_handle();
43 match (self.interface.len() == 1, script.timeout) {
44 (true, Some(timeout)) => {
45 execute_with_timeout(
46 timeout,
47 || self.eval_inner(script),
48 move || { isolate_handle.terminate_execution(); },
49 )?.into(self)
50 },
51 (false, Some(_)) => Err(Error::InvalidTimeout),
52 (_, None) => self.eval_inner(script)?.into(self),
53 }
54 }
55
56 fn eval_inner(&self, script: Script) -> Result<Value> {
57 self.try_catch(|scope| {
58 let source = create_string(scope, &script.source);
59 let origin = script.origin.map(|o| {
60 let name = create_string(scope, &o.name).into();
61 let source_map_url = create_string(scope, "").into();
62 v8::ScriptOrigin::new(
63 scope,
64 name,
65 o.line_offset,
66 o.column_offset,
67 false,
68 0,
69 source_map_url,
70 true,
71 false,
72 false,
73 )
74 });
75 let script = v8::Script::compile(scope, source, origin.as_ref());
76 self.exception(scope)?;
77 let result = script.unwrap().run(scope);
78 self.exception(scope)?;
79 Ok(Value::from_v8_value(self, scope, result.unwrap()))
80 })
81 }
82
83 pub fn set_user_data<K, T>(&self, key: K, data: T) -> Option<Box<dyn Any>>
87 where
88 K: ToString,
89 T: Any,
90 {
91 self.interface.use_slot(|m: &AnyMap| m.0.borrow_mut().insert(key.to_string(), Box::new(data)))
92 }
93
94 pub fn use_user_data<F, T: Any, U>(&self, key: &str, func: F) -> U
98 where
99 F: FnOnce(Option<&T>) -> U + 'static,
100 {
101 self.interface.use_slot(|m: &AnyMap| {
102 func(m.0.borrow().get(key).and_then(|d| d.downcast_ref::<T>()))
103 })
104 }
105
106 pub fn remove_user_data(&self, key: &str) -> Option<Box<dyn Any>> {
109 self.interface.use_slot(|m: &AnyMap| m.0.borrow_mut().remove(key))
110 }
111
112 pub fn create_string(&self, value: &str) -> String {
118 self.scope(|scope| {
119 let string = create_string(scope, value);
120 String {
121 mv8: self.clone(),
122 handle: v8::Global::new(scope, string),
123 }
124 })
125 }
126
127 pub fn create_array(&self) -> Array {
129 self.scope(|scope| {
130 let array = v8::Array::new(scope, 0);
131 Array {
132 mv8: self.clone(),
133 handle: v8::Global::new(scope, array),
134 }
135 })
136 }
137
138 pub fn create_object(&self) -> Object {
140 self.scope(|scope| {
141 let object = v8::Object::new(scope);
142 Object {
143 mv8: self.clone(),
144 handle: v8::Global::new(scope, object),
145 }
146 })
147 }
148
149 pub fn create_object_from<K, V, I>(&self, iter: I) -> Result<Object>
155 where
156 K: ToValue,
157 V: ToValue,
158 I: IntoIterator<Item = (K, V)>,
159 {
160 let object = self.create_object();
161 for (k, v) in iter {
162 object.set(k, v)?;
163 }
164 Ok(object)
165 }
166
167 pub fn create_function<F, R>(&self, func: F) -> Function
179 where
180 F: Fn(Invocation) -> Result<R> + 'static,
181 R: ToValue,
182 {
183 let func = move |mv8: &MiniV8, this: Value, args: Values| {
184 func(Invocation { mv8: mv8.clone(), this, args })?.to_value(mv8)
185 };
186
187 self.scope(|scope| {
188 let callback = Box::new(func);
189 let callback_info = CallbackInfo { mv8: self.clone(), callback };
190 let ptr = Box::into_raw(Box::new(callback_info));
191 let ext = v8::External::new(scope, ptr as _);
192
193 let v8_func = |
194 scope: &mut v8::HandleScope,
195 fca: v8::FunctionCallbackArguments,
196 mut rv: v8::ReturnValue,
197 | {
198 let data = fca.data();
199 let ext = v8::Local::<v8::External>::try_from(data).unwrap();
200 let callback_info_ptr = ext.value() as *mut CallbackInfo;
201 let callback_info = unsafe { &mut *callback_info_ptr };
202 let CallbackInfo { mv8, callback } = callback_info;
203 let ptr = scope as *mut v8::HandleScope;
204 let ptr: *mut v8::HandleScope<'static> = unsafe { std::mem::transmute(ptr) };
207 mv8.interface.push(ptr);
208 let this = Value::from_v8_value(&mv8, scope, fca.this().into());
209 let len = fca.length();
210 let mut args = Vec::with_capacity(len as usize);
211 for i in 0..len {
212 args.push(Value::from_v8_value(&mv8, scope, fca.get(i)));
213 }
214 match callback(&mv8, this, Values::from_vec(args)) {
215 Ok(v) => {
216 rv.set(v.to_v8_value(scope));
217 },
218 Err(e) => {
219 let exception = e.to_value(&mv8).to_v8_value(scope);
220 scope.throw_exception(exception);
221 },
222 };
223 mv8.interface.pop();
224 };
225
226 let value = v8::Function::builder(v8_func).data(ext.into()).build(scope).unwrap();
227 let drop_ext = Box::new(move || drop(unsafe { Box::from_raw(ptr) }));
236 add_finalizer(scope, value, drop_ext);
237 Function {
238 mv8: self.clone(),
239 handle: v8::Global::new(scope, value),
240 }
241 })
242 }
243
244 pub fn create_function_mut<F, R>(&self, func: F) -> Function
249 where
250 F: FnMut(Invocation) -> Result<R> + 'static,
251 R: ToValue,
252 {
253 let func = RefCell::new(func);
254 self.create_function(move |invocation| {
255 (&mut *func.try_borrow_mut().map_err(|_| Error::RecursiveMutCallback)?)(invocation)
256 })
257 }
258
259 pub(crate) fn scope<F, T>(&self, func: F) -> T
262 where
263 F: FnOnce(&mut v8::ContextScope<v8::HandleScope>) -> T,
264 {
265 self.interface.scope(func)
266 }
267
268 pub(crate) fn try_catch<F, T>(&self, func: F) -> T
271 where
272 F: FnOnce(&mut v8::TryCatch<v8::HandleScope>) -> T,
273 {
274 self.interface.try_catch(func)
275 }
276
277 pub(crate) fn exception(&self, scope: &mut v8::TryCatch<v8::HandleScope>) -> Result<()> {
278 if scope.has_terminated() {
279 Err(Error::Timeout)
280 } else if let Some(exception) = scope.exception() {
281 Err(Error::Value(Value::from_v8_value(self, scope, exception)))
282 } else {
283 Ok(())
284 }
285 }
286}
287
288#[derive(Clone)]
289struct Interface(Rc<RefCell<Vec<Rc<RefCell<InterfaceEntry>>>>>);
290
291impl Interface {
292 fn len(&self) -> usize {
293 self.0.borrow().len()
294 }
295
296 fn isolate_handle(&self) -> v8::IsolateHandle {
297 self.top(|entry| entry.isolate_handle())
298 }
299
300 fn scope<F, T>(&self, func: F) -> T
302 where
303 F: FnOnce(&mut v8::ContextScope<v8::HandleScope>) -> T,
304 {
305 self.top(|entry| entry.scope(func))
306 }
307
308 fn try_catch<F, T>(&self, func: F) -> T
310 where
311 F: FnOnce(&mut v8::TryCatch<v8::HandleScope>) -> T,
312 {
313 self.scope(|scope| func(&mut v8::TryCatch::new(scope)))
314 }
315
316 fn new(isolate: v8::OwnedIsolate) -> Interface {
317 Interface(Rc::new(RefCell::new(vec![Rc::new(RefCell::new(InterfaceEntry::Isolate(isolate)))])))
318 }
319
320 fn push(&self, handle_scope: *mut v8::HandleScope<'static>) {
321 self.0.borrow_mut().push(Rc::new(RefCell::new(InterfaceEntry::HandleScope(handle_scope))));
322 }
323
324 fn pop(&self) {
325 self.0.borrow_mut().pop();
326 }
327
328 fn use_slot<F, T: 'static, U>(&self, func: F) -> U
329 where
330 F: FnOnce(&T) -> U,
331 {
332 self.top(|entry| func(entry.get_slot()))
333 }
334
335 fn top<F, T>(&self, func: F) -> T
336 where
337 F: FnOnce(&mut InterfaceEntry) -> T,
338 {
339 let top = self.0.borrow().last().unwrap().clone();
340 let mut top_mut = top.borrow_mut();
341 func(&mut top_mut)
342 }
343}
344
345enum InterfaceEntry {
346 Isolate(v8::OwnedIsolate),
347 HandleScope(*mut v8::HandleScope<'static>),
348}
349
350impl InterfaceEntry {
351 fn scope<F, T>(&mut self, func: F) -> T
352 where
353 F: FnOnce(&mut v8::ContextScope<v8::HandleScope>) -> T,
354 {
355 match self {
356 InterfaceEntry::Isolate(isolate) => {
357 let global_context = isolate.get_slot::<Global>().unwrap().context.clone();
358 let scope = &mut v8::HandleScope::new(isolate);
359 let context = v8::Local::new(scope, global_context);
360 let scope = &mut v8::ContextScope::new(scope, context);
361 func(scope)
362 },
363 InterfaceEntry::HandleScope(ref ptr) => {
364 let scope: &mut v8::HandleScope = unsafe { &mut **ptr };
365 let scope = &mut v8::ContextScope::new(scope, scope.get_current_context());
366 func(scope)
367 },
368 }
369 }
370
371 fn get_slot<T: 'static>(&self) -> &T {
372 match self {
373 InterfaceEntry::Isolate(isolate) => isolate.get_slot::<T>().unwrap(),
374 InterfaceEntry::HandleScope(ref ptr) => {
375 let scope: &mut v8::HandleScope = unsafe { &mut **ptr };
376 scope.get_slot::<T>().unwrap()
377 },
378 }
379 }
380
381 fn isolate_handle(&self) -> v8::IsolateHandle {
382 match self {
383 InterfaceEntry::Isolate(isolate) => isolate.thread_safe_handle(),
384 InterfaceEntry::HandleScope(ref ptr) => {
385 let scope: &mut v8::HandleScope = unsafe { &mut **ptr };
386 scope.thread_safe_handle()
387 },
388 }
389 }
390}
391
392struct Global {
393 context: v8::Global<v8::Context>,
394}
395
396static INIT: Once = Once::new();
397
398fn initialize_v8() {
399 INIT.call_once(|| {
400 let platform = v8::new_default_platform(0, false).make_shared();
401 v8::V8::initialize_platform(platform);
402 v8::V8::initialize();
403 });
404}
405
406fn initialize_slots(isolate: &mut v8::Isolate) {
407 let scope = &mut v8::HandleScope::new(isolate);
408 let context = v8::Context::new(scope);
409 let scope = &mut v8::ContextScope::new(scope, context);
410 let global_context = v8::Global::new(scope, context);
411 scope.set_slot(Global { context: global_context });
412 scope.set_slot(AnyMap(Rc::new(RefCell::new(BTreeMap::new()))));
413}
414
415fn create_string<'s>(scope: &mut v8::HandleScope<'s>, value: &str) -> v8::Local<'s, v8::String> {
416 v8::String::new(scope, value).expect("string exceeds maximum length")
417}
418
419fn add_finalizer<T: 'static>(
420 isolate: &mut v8::Isolate,
421 handle: impl v8::Handle<Data = T>,
422 finalizer: impl FnOnce() + 'static,
423) {
424 let rc = Rc::new(RefCell::new(None));
425 let weak = v8::Weak::with_guaranteed_finalizer(isolate, handle, Box::new({
426 let rc = rc.clone();
427 move || {
428 let weak = rc.replace(None).unwrap();
429 finalizer();
430 drop(weak);
431 }
432 }));
433 rc.replace(Some(weak));
434}
435
436type Callback = Box<dyn Fn(&MiniV8, Value, Values) -> Result<Value>>;
437
438struct CallbackInfo {
439 mv8: MiniV8,
440 callback: Callback,
441}
442
443struct AnyMap(Rc<RefCell<BTreeMap<StdString, Box<dyn Any>>>>);
444
445#[derive(Clone, Debug, Default)]
447pub struct Script {
448 pub source: StdString,
450 pub timeout: Option<Duration>,
458 pub origin: Option<ScriptOrigin>,
460}
461
462#[derive(Clone, Debug, Default)]
464pub struct ScriptOrigin {
465 pub name: StdString,
467 pub line_offset: i32,
469 pub column_offset: i32,
471}
472
473impl From<StdString> for Script {
474 fn from(source: StdString) -> Script {
475 Script { source, ..Default::default() }
476 }
477}
478
479impl<'a> From<&'a str> for Script {
480 fn from(source: &'a str) -> Script {
481 source.to_string().into()
482 }
483}
484
485fn execute_with_timeout<T>(
486 timeout: Duration,
487 execute_fn: impl FnOnce() -> T,
488 timed_out_fn: impl FnOnce() + Send + 'static,
489) -> T {
490 let wait = Arc::new((Mutex::new(true), Condvar::new()));
491 let timer_wait = wait.clone();
492 thread::spawn(move || {
493 let (mutex, condvar) = &*timer_wait;
494 let timer = condvar.wait_timeout_while(
495 mutex.lock().unwrap(),
496 timeout,
497 |&mut is_executing| is_executing,
498 ).unwrap();
499 if timer.1.timed_out() {
500 timed_out_fn();
501 }
502 });
503
504 let result = execute_fn();
505 let (mutex, condvar) = &*wait;
506 *mutex.lock().unwrap() = false;
507 condvar.notify_one();
508 result
509}