use super::Compartment;
use super::JSInitializable;
use super::JSManaged;
use super::JSRoot;
use super::JSLifetime;
use super::JSTraceable;
use super::JSCompartmental;
use super::compartment::BOUND;
use super::ffi::JSEvaluateErr;
use super::ffi::JSInitializer;
use super::managed::JSManageable;
use super::root::trace_thread_local_roots;
use js::glue::NewCompileOptions;
use js::jsapi;
use js::jsapi::JS::Evaluate2;
use js::jsapi::JS::Handle;
use js::heap::Heap;
use js::jsapi::JSAutoCompartment;
use js::jsapi::JSObject;
use js::jsapi::JS_AddExtraGCRootsTracer;
use js::jsapi::JS_ClearPendingException;
use js::jsapi::JS_DefineFunctions;
use js::jsapi::JS_DefineProperties;
use js::jsapi::JS_GC;
use js::jsapi::JS_GetPendingException;
use js::jsapi::JS_GetReservedSlot;
use js::jsapi::JS_GetRuntime;
use js::jsapi::JS_IsExceptionPending;
use js::jsapi::JS_NewGlobalObject;
use js::jsapi::JS_SetReservedSlot;
use js::jsapi::JS::MutableHandle;
use js::jsapi::JS::Value;
use js::jsval::PrivateValue;
use js::jsval::UndefinedValue;
use js::rust::Runtime;
use libc;
use std::any::TypeId;
use std::cell::RefCell;
use std::collections::HashMap;
use std::marker::PhantomData;
use std::mem;
use std::ptr;
use std::str;
pub struct JSContext<S> {
jsapi_context: *mut jsapi::JSContext,
global_js_object: *mut Heap<*mut JSObject>,
global_raw: *mut (),
auto_compartment: Option<JSAutoCompartment>,
runtime: Option<Runtime>,
marker: PhantomData<S>,
}
pub struct Initialized<'a, C, T> (PhantomData<(&'a(), C, T)>);
pub struct Initializing<'a, C, T> (PhantomData<(&'a(), C, T)>);
pub struct Entered<'a, C, T, S> (PhantomData<(&'a(), C, T, S)>);
pub struct Owned (());
pub struct FromJS (());
pub struct Snapshotted<'a, S> (PhantomData<(&'a(), S)>);
pub trait InCompartment<C> {}
impl<'a, C, T> InCompartment<C> for Initializing<'a, C, T> {}
impl<'a, C, T> InCompartment<C> for Initialized<'a, C, T> {}
impl<'a, C, T, S> InCompartment<C> for Entered<'a, C, T, S> {}
impl<'a, C, S> InCompartment<C> for Snapshotted<'a, S> where S: InCompartment<C> {}
pub trait CanAccess {}
impl<'a, C, T> CanAccess for Initialized<'a, C, T> {}
impl CanAccess for FromJS {}
impl CanAccess for Owned {}
impl<'a, C, T, S> CanAccess for Entered<'a, C, T, S> where S: CanAccess {}
impl<'a, S> CanAccess for Snapshotted<'a, S> where S: CanAccess {}
pub trait IsSnapshot<'a> {}
impl<'a, S> IsSnapshot<'a> for Snapshotted<'a, S> {}
impl<'a, 'b, C, T, S> IsSnapshot<'a> for Entered<'b, C, T, S> where S: IsSnapshot<'a> {}
pub trait CanAlloc {}
impl<'a, C, T> CanAlloc for Initialized<'a, C, T> {}
impl<'a, C, T> CanAlloc for Initializing<'a, C, T> {}
impl CanAlloc for FromJS {}
impl CanAlloc for Owned {}
impl<'a, C, T, S> CanAlloc for Entered<'a, C, T, S> where S: CanAlloc {}
pub trait IsInitializing<'a, C, T> {}
impl<'a, C, T> IsInitializing<'a, C, T> for Initializing<'a, C, T> {}
pub trait IsInitialized<'a, C, T> {}
impl<'a, C, T> IsInitialized<'a, C, T> for Initialized<'a, C, T> {}
pub trait IsEntered<'a, C, T> {}
impl<'a, C, T, S> IsEntered<'a, C, T> for Entered<'a, C, T, S> {}
#[cfg(feature = "smup")]
thread_local!{ static RUNTIME: RefCell<Result<Runtime,()>> = RefCell::new(Runtime::new(true)) }
#[cfg(not(feature = "smup"))]
thread_local!{ static RUNTIME: RefCell<Result<Runtime,()>> = RefCell::new(Runtime::new()) }
impl JSContext<Owned> {
pub fn new() -> Result<JSContext<Owned>,()> {
let runtime = RUNTIME.with(|runtime| runtime.replace(Err(())))?;
let jsapi_context = runtime.cx();
#[cfg(feature = "smup")]
unsafe { JS_AddExtraGCRootsTracer(jsapi_context, Some(trace_thread_local_roots), ptr::null_mut()); }
#[cfg(not(feature = "smup"))]
unsafe { JS_AddExtraGCRootsTracer(runtime.rt(), Some(trace_thread_local_roots), ptr::null_mut()); }
Ok(JSContext {
jsapi_context: jsapi_context,
global_js_object: ptr::null_mut(),
global_raw: ptr::null_mut(),
auto_compartment: None,
runtime: Some(runtime),
marker: PhantomData,
})
}
}
impl<S> JSContext<S> {
pub fn snapshot<'a>(&'a mut self) -> JSContext<Snapshotted<'a, S>> {
debug!("Creating snapshot.");
JSContext {
jsapi_context: self.jsapi_context,
global_js_object: self.global_js_object,
global_raw: self.global_raw,
auto_compartment: None,
runtime: None,
marker: PhantomData,
}
}
pub fn enter_known_compartment<'a, 'b, C, T>(&'a mut self, managed: JSManaged<'b, C, T>) -> JSContext<Entered<'a, C, T::Aged, S>> where
T: JSLifetime<'a>,
C: Compartment,
{
debug!("Entering compartment.");
#[allow(unused_unsafe)]
let ac = unsafe { JSAutoCompartment::new(self.jsapi_context, managed.to_jsobject()) };
JSContext {
jsapi_context: self.jsapi_context,
global_js_object: managed.to_heap_object(),
global_raw: managed.to_raw_native() as *mut (),
auto_compartment: Some(ac),
runtime: None,
marker: PhantomData,
}
}
pub fn enter_unknown_compartment<'a, 'b, C, T>(&'a mut self, managed: JSManaged<'b, C, T>) -> JSContext<Entered<'a, BOUND<'a>, T::Aged, S>> where
T: JSLifetime<'a>,
{
debug!("Entering compartment.");
#[allow(unused_unsafe)]
let ac = unsafe { JSAutoCompartment::new(self.jsapi_context, managed.to_jsobject()) };
JSContext {
jsapi_context: self.jsapi_context,
global_js_object: managed.to_heap_object(),
global_raw: managed.to_raw_native() as *mut (),
auto_compartment: Some(ac),
runtime: None,
marker: PhantomData,
}
}
pub fn manage<'a, C, T>(&'a mut self, value: T) -> JSManaged<'a, C, T::Aged> where
S: CanAlloc + InCompartment<C>,
C: Compartment,
T: JSTraceable + JSInitializable + JSLifetime<'a>,
{
JSManaged::new(self, value)
}
pub fn snapshot_manage<'a, C, T>(&'a mut self, value: T) -> (JSContext<Snapshotted<'a, S>>, JSManaged<'a, C, T::Aged>) where
S: CanAlloc + InCompartment<C>,
C: Compartment,
T: JSTraceable + JSInitializable + JSLifetime<'a>,
{
let jsapi_context = self.jsapi_context;
let global_js_object = self.global_js_object;
let global_raw = self.global_raw;
let managed = self.manage(value);
debug!("Creating snapshot while managing.");
let snapshot = JSContext {
jsapi_context: jsapi_context,
global_js_object: global_js_object,
global_raw: global_raw,
auto_compartment: None,
runtime: None,
marker: PhantomData,
};
(snapshot, managed)
}
pub fn create_compartment<'a, T>(&'a mut self) -> JSContext<Initializing<'a, BOUND<'a>, T>> where
S: CanAlloc + CanAccess,
T: JSInitializable + JSTraceable,
{
debug!("Creating compartment.");
let value: Option<T> = None;
let boxed_value: Box<JSManageable> = Box::new(value);
let fat_value: [*const libc::c_void; 2] = unsafe { mem::transmute(boxed_value) };
let classp = unsafe { T::Init::global_classp() };
let principals = unsafe { T::Init::global_principals() };
let hook_options = unsafe { T::Init::global_hook_option() };
let options = unsafe { T::Init::global_options() };
let properties = unsafe { T::Init::properties() };
let functions = unsafe { T::Init::functions() };
let boxed_jsobject = Box::new(Heap::default());
debug!("Boxed global {:p}", boxed_jsobject);
let unboxed_jsobject = unsafe { JS_NewGlobalObject(self.jsapi_context, classp, principals, hook_options, &options) };
debug!("Unboxed global {:p}", unboxed_jsobject);
assert!(!unboxed_jsobject.is_null());
boxed_jsobject.set(unboxed_jsobject);
debug!("Entering compartment.");
#[allow(unused_unsafe)]
let ac = unsafe { JSAutoCompartment::new(self.jsapi_context, boxed_jsobject.get()) };
let prototypes: Box<HashMap<TypeId, Box<Heap<*mut JSObject>>>> = Box::new(HashMap::new());
#[cfg(feature = "smup")]
unsafe {
JS_SetReservedSlot(boxed_jsobject.get(), 0, &PrivateValue(fat_value[0]));
JS_SetReservedSlot(boxed_jsobject.get(), 1, &PrivateValue(fat_value[1]));
JS_SetReservedSlot(boxed_jsobject.get(), 2, &PrivateValue(Box::into_raw(prototypes) as *const _));
}
#[cfg(not(feature = "smup"))]
unsafe {
JS_SetReservedSlot(boxed_jsobject.get(), 0, PrivateValue(fat_value[0]));
JS_SetReservedSlot(boxed_jsobject.get(), 1, PrivateValue(fat_value[1]));
JS_SetReservedSlot(boxed_jsobject.get(), 2, PrivateValue(Box::into_raw(prototypes) as *const _));
}
if !properties.is_null() { unsafe { JS_DefineProperties(self.jsapi_context, boxed_jsobject.handle(), properties) }; }
if !functions.is_null() { unsafe { JS_DefineFunctions(self.jsapi_context, boxed_jsobject.handle(), functions) }; }
debug!("Initializing compartment.");
unsafe { T::Init::js_init_global(self.jsapi_context, boxed_jsobject.handle()) };
debug!("Created compartment.");
JSContext {
jsapi_context: self.jsapi_context,
global_js_object: Box::into_raw(boxed_jsobject),
global_raw: fat_value[0] as *mut (),
auto_compartment: Some(ac),
runtime: None,
marker: PhantomData,
}
}
pub fn global_manage<'a, C, T>(mut self, value: T) -> JSContext<Initialized<'a, C, T::Aged>> where
S: IsInitializing<'a, C, T>,
T: JSTraceable + JSLifetime<'a> + JSCompartmental<C, C, ChangeCompartment = T>,
{
debug!("Managing native global.");
let raw = self.global_raw as *mut Option<T>;
let uninitialized = unsafe { mem::replace(&mut *raw, Some(value)) };
mem::forget(uninitialized);
debug!("Initialized compartment.");
JSContext {
jsapi_context: self.jsapi_context,
global_js_object: self.global_js_object,
global_raw: self.global_raw,
auto_compartment: self.auto_compartment.take(),
runtime: self.runtime.take(),
marker: PhantomData,
}
}
pub fn entered<'a, C, T>(&self) -> JSManaged<'a, C, T> where
S: IsEntered<'a, C, T>
{
unsafe { JSManaged::from_raw(self.global_js_object, self.global_raw) }
}
pub fn global<'a, C, T>(&self) -> JSManaged<'a, C, T> where
S: IsInitialized<'a, C, T>
{
unsafe { JSManaged::from_raw(self.global_js_object, self.global_raw) }
}
pub fn new_root<'a, 'b, T>(&'b mut self) -> JSRoot<'a, T> {
JSRoot::new(self)
}
pub fn cx(&self) -> *mut jsapi::JSContext {
self.jsapi_context
}
pub fn rt(&self) -> *mut jsapi::JSRuntime {
unsafe { JS_GetRuntime(self.jsapi_context) }
}
pub fn gc(&mut self) where
S: CanAlloc,
{
#[cfg(feature = "smup")]
unsafe { JS_GC(self.cx()); }
#[cfg(not(feature = "smup"))]
unsafe { JS_GC(self.rt()); }
}
pub fn prototype_of<T>(&mut self) -> Handle<*mut JSObject> where
T: JSInitializable,
{
let global = unsafe { &*self.global_js_object }.handle();
let prototypes = unsafe { &mut *(JS_GetReservedSlot(global.get(), 2).to_private() as *mut HashMap<TypeId, Box<Heap<*mut JSObject>>>) };
prototypes.entry(TypeId::of::<T::Init>()).or_insert_with(|| {
let boxed = Box::new(Heap::default());
boxed.set(unsafe { T::Init::js_init_class(self.jsapi_context, global) });
boxed
}).handle()
}
pub fn evaluate<C>(&mut self, code: &str) -> Result<Value, JSEvaluateErr> where
S: InCompartment<C>,
{
let cx = self.jsapi_context;
let options = unsafe { NewCompileOptions(cx, &0, 0) };
let code_utf16: Vec<u16> = code.encode_utf16().collect();
let code_ptr = &code_utf16[0] as *const u16;
let code_len = code_utf16.len() as usize;
let mut result = UndefinedValue();
let result_mut = unsafe { MutableHandle::from_marked_location(&mut result) };
unsafe { Evaluate2(cx, options, code_ptr, code_len, result_mut) };
self.take_pending_exception()?;
Ok(result)
}
pub fn take_pending_exception(&mut self) -> Result<(), JSEvaluateErr> {
let cx = self.jsapi_context;
if !unsafe { JS_IsExceptionPending(cx) } { return Ok(()); }
let ref mut exn_ref = UndefinedValue();
let exn_mut = unsafe { MutableHandle::from_marked_location(exn_ref) };
let _exn_handle = exn_mut.handle();
unsafe { JS_GetPendingException(cx, exn_mut) };
unsafe { JS_ClearPendingException(cx) };
Err(JSEvaluateErr::JSException)
}
}
pub unsafe fn jscontext_called_from_js(cx: *mut jsapi::JSContext) -> JSContext<FromJS> {
JSContext {
jsapi_context: cx,
global_js_object: ptr::null_mut(),
global_raw: ptr::null_mut(),
auto_compartment: None,
runtime: None,
marker: PhantomData,
}
}