use self::thrown_store::{ThrownValueHandle, ThrownValueStore};
use crate::{
ClassSetup, FromJSValue, HostError, JSClass, JSObject, JSObjectOps, JSResult, JSRuntimeImpl,
JSTypeOf, JSValue, JSValueImpl, Promise, RongJSError,
source::{Source, SourceKind},
};
use crate::{JSRuntime, JSValueMapper};
use std::any::TypeId;
use std::cell::RefCell;
use std::collections::HashMap;
use std::rc::{Rc, Weak};
use std::sync::{LazyLock, RwLock};
pub(crate) mod thrown_store;
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub enum PromiseHandlerRegistration {
#[default]
JavaScriptOnly,
NativeOnly,
NativeWithJavaScriptFallbackIfPending,
}
pub trait JSContextImpl {
type RawContext;
type Runtime: JSRuntimeImpl<Context = Self>;
type Value: JSValueImpl<Context = Self>;
fn new(runtime: &Self::Runtime) -> Self;
fn as_raw(&self) -> &Self::RawContext;
fn context_id(ctx: &Self::RawContext) -> usize;
fn from_borrowed_raw(ctx: Self::RawContext) -> Self;
fn eval(&self, source: Source) -> Self::Value;
fn global(&self) -> Self::Value;
fn register_class<JC>(&self) -> Self::Value
where
JC: JSClass<Self::Value>;
fn call(&self, function: &Self::Value, this: Self::Value, argv: &[Self::Value]) -> Self::Value;
fn promise(&self) -> (Self::Value, Self::Value, Self::Value);
fn register_promise_handlers(
&self,
_promise: &Self::Value,
_on_fulfilled: &Self::Value,
_on_rejected: &Self::Value,
) -> PromiseHandlerRegistration {
PromiseHandlerRegistration::JavaScriptOnly
}
fn compile_to_bytecode(&self, source: Source) -> Result<Vec<u8>, RongJSError>;
fn run_bytecode(&self, bytes: &[u8]) -> Self::Value;
}
pub trait JSRawContext {
type RawContext;
}
pub struct JSContext<C: JSContextImpl> {
rc: Rc<JSContextInner<C>>,
}
struct JSContextInner<C: JSContextImpl> {
inner: C,
runtime: JSRuntime<C::Runtime>,
rong: C::Value,
services: ContextServiceContainer,
}
pub trait JSContextService: 'static {
fn on_shutdown(&self) {}
}
#[derive(Clone)]
struct ContextServiceContainer {
services: Rc<RefCell<HashMap<TypeId, Box<dyn JSContextService>>>>,
}
struct ContextState<T: 'static>(T);
impl<T: 'static> JSContextService for ContextState<T> {}
impl ContextServiceContainer {
fn new() -> Self {
Self {
services: Rc::new(RefCell::new(HashMap::new())),
}
}
fn register<T: JSContextService>(&self, service: T) {
let mut services = self.services.borrow_mut();
services.insert(TypeId::of::<T>(), Box::new(service));
}
fn get<T: JSContextService>(&self) -> Option<&T> {
unsafe {
let services = self.services.borrow();
services
.get(&TypeId::of::<T>())
.map(|svc| &*(svc.as_ref() as *const dyn JSContextService as *const T))
}
}
fn register_state<T: 'static>(&self, value: T) {
let mut services = self.services.borrow_mut();
services.insert(TypeId::of::<T>(), Box::new(ContextState(value)));
}
fn get_state<T: 'static>(&self) -> Option<&T> {
unsafe {
let services = self.services.borrow();
services
.get(&TypeId::of::<T>())
.map(|svc| {
&*(svc.as_ref() as *const dyn JSContextService as *const ContextState<T>)
})
.map(|state| &state.0)
}
}
fn shutdown(&self) {
let mut services = self.services.borrow_mut();
for (_, svc) in services.drain() {
svc.on_shutdown();
}
}
}
impl<C: JSContextImpl> AsRef<C> for JSContext<C> {
fn as_ref(&self) -> &C {
&self.rc.inner
}
}
impl<C: JSContextImpl> JSContext<C> {
pub(crate) fn new(runtime: &JSRuntime<C::Runtime>) -> Self
where
C::Value: JSObjectOps,
{
let raw_ctx = C::new(&runtime.inner);
let rong = C::Value::new_object(&raw_ctx);
let inner = JSContextInner {
inner: raw_ctx,
runtime: runtime.clone(),
rong,
services: ContextServiceContainer::new(),
};
let ctx = JSContext { rc: Rc::new(inner) };
let weak = Rc::downgrade(&ctx.rc);
let opaque = ContextOpaque::<C::Value>::new(weak);
let raw_ctx = ctx.rc.inner.as_raw();
let key = C::context_id(raw_ctx);
CTX_OPAQUE
.write()
.unwrap()
.insert(key, Box::into_raw(opaque) as usize);
ctx
}
pub fn host_namespace(&self) -> JSObject<C::Value> {
let value = JSValue::from_raw(self, self.rc.as_ref().rong.clone());
value.into()
}
#[doc(hidden)]
pub fn from_borrowed_raw_ptr(ptr: &C::RawContext) -> Self {
let data = Self::_get_opaque(ptr);
if data.is_null() {
panic!("[JSContext] opaque is empty");
} else {
let ctx_inner = unsafe { &(*data).ctx_inner };
if let Some(ctx) = ctx_inner.upgrade() {
Self { rc: ctx }
} else {
panic!("[JSContext] context has been dropped");
}
}
}
pub fn eval<T>(&self, source: Source) -> JSResult<T>
where
C::Value: JSObjectOps,
T: FromJSValue<C::Value>,
{
let result = match source.kind() {
SourceKind::ByteCode(code) => self.rc.inner.run_bytecode(code),
SourceKind::JavaScript(code) => self.rc.inner.eval(Source::from_bytes(code.clone())),
};
result.try_convert::<T>()
}
pub fn global(&self) -> JSObject<C::Value>
where
C::Value: JSTypeOf,
{
let raw = self.rc.inner.global();
JSObject::from_js_value(self, JSValue::from_raw(self, raw)).unwrap()
}
fn register_class_with_visibility<JC>(&self, expose_on_global: bool) -> JSResult<()>
where
JC: JSClass<C::Value>,
C::Value: JSObjectOps,
{
let registry = self
.get_class_registry()
.ok_or_else(|| HostError::new(crate::error::E_INTERNAL, "No Class registry!"))?;
if registry.borrow().contains_key(&TypeId::of::<JC>()) {
return Ok(());
}
let value = self.rc.inner.register_class::<JC>();
let constructor = JSValue::from_raw(self, value.clone());
JC::class_setup(&ClassSetup::new(constructor.clone().into(), self)?)?;
if expose_on_global {
let obj = self.global();
obj.set(JC::NAME, constructor)?;
}
registry.borrow_mut().insert(TypeId::of::<JC>(), value);
Ok(())
}
pub fn register_class<JC>(&self) -> JSResult<()>
where
JC: JSClass<C::Value>,
C::Value: JSObjectOps,
{
self.register_class_with_visibility::<JC>(true)
}
pub fn register_hidden_class<JC>(&self) -> JSResult<()>
where
JC: JSClass<C::Value>,
C::Value: JSObjectOps,
{
self.register_class_with_visibility::<JC>(false)
}
pub(crate) fn get_class_registry(&self) -> Option<&RefCell<HashMap<TypeId, C::Value>>> {
let data = self.get_opaque();
if data.is_null() {
None
} else {
unsafe { Some(&(*data).registry) }
}
}
pub(crate) fn capture_thrown(&self, value: JSValue<C::Value>) -> ThrownValueHandle {
let data = self.get_opaque();
if data.is_null() {
panic!("[JSContext] opaque is empty");
}
let context_id = C::context_id(self.as_ref().as_raw());
#[cfg(debug_assertions)]
tracing::debug!(target: "rong", context_id, "capturing thrown JS value");
let mut store = unsafe { (*data).thrown.try_borrow_mut() }
.expect("[JSContext] Fatal: ThrownValueStore already borrowed. Recursive error handling detected.");
store.insert(context_id, value.into_value())
}
pub(crate) fn resolve_thrown(&self, handle: ThrownValueHandle) -> Option<JSValue<C::Value>> {
let data = self.get_opaque();
if data.is_null() {
return None;
}
let context_id = C::context_id(self.as_ref().as_raw());
let store = unsafe { (*data).thrown.try_borrow().ok()? };
store
.get(context_id, handle)
.map(|v| JSValue::from_raw(self, v))
}
pub(crate) fn take_thrown(&self, handle: ThrownValueHandle) -> Option<JSValue<C::Value>> {
let data = self.get_opaque();
if data.is_null() {
return None;
}
let context_id = C::context_id(self.as_ref().as_raw());
let mut store = unsafe { (*data).thrown.try_borrow_mut().ok()? };
store
.take(context_id, handle)
.map(|v| JSValue::from_raw(self, v))
}
pub fn runtime(&self) -> &JSRuntime<C::Runtime> {
&self.rc.runtime
}
pub fn set_service<T: JSContextService>(&self, service: T) {
self.rc.services.register::<T>(service);
}
pub fn get_service<T: JSContextService>(&self) -> Option<&T> {
self.rc.services.get::<T>()
}
pub fn set_state<T: 'static>(&self, value: T) {
self.rc.services.register_state(value);
}
pub fn get_state<T: 'static>(&self) -> Option<&T> {
self.rc.services.get_state::<T>()
}
pub fn compile_to_bytecode<T: AsRef<[u8]>>(&self, code: T) -> JSResult<Source> {
self.rc
.inner
.compile_to_bytecode(Source::from_bytes(code.as_ref()))
.map(Source::from_bytecode)
}
pub async fn eval_async<T>(&self, source: Source) -> JSResult<T>
where
C::Value: JSTypeOf + JSObjectOps + 'static,
T: FromJSValue<C::Value> + 'static,
{
let result = match source.kind() {
SourceKind::ByteCode(code) => self.rc.inner.run_bytecode(code),
SourceKind::JavaScript(code) => self.rc.inner.eval(Source::from_bytes(code.clone())),
};
if result.is_promise() {
let promise = Promise::from_js_value(self, JSValue::from_raw(self, result))?;
promise.into_future::<T>().await
} else {
result.try_convert::<T>()
}
}
fn get_opaque(&self) -> *mut ContextOpaque<C::Value> {
let key = self.rc.inner.as_raw();
Self::_get_opaque(key)
}
fn _get_opaque(raw_ctx: &C::RawContext) -> *mut ContextOpaque<C::Value> {
let key = C::context_id(raw_ctx);
if let Some(opaque_ptr) = CTX_OPAQUE.read().unwrap().get(&key) {
*opaque_ptr as *mut ContextOpaque<C::Value>
} else {
std::ptr::null_mut()
}
}
}
struct ContextOpaque<V: JSValueImpl> {
registry: RefCell<HashMap<TypeId, V>>,
ctx_inner: Weak<JSContextInner<V::Context>>,
thrown: RefCell<ThrownValueStore<V>>,
}
impl<V: JSValueImpl> ContextOpaque<V> {
fn new(ctx_inner: Weak<JSContextInner<V::Context>>) -> Box<Self> {
Box::new(Self {
registry: RefCell::new(HashMap::new()),
ctx_inner,
thrown: RefCell::new(ThrownValueStore::new()),
})
}
}
static CTX_OPAQUE: LazyLock<RwLock<HashMap<usize, usize>>> =
LazyLock::new(|| RwLock::new(HashMap::new()));
impl<C: JSContextImpl> Drop for JSContext<C> {
fn drop(&mut self) {
if Rc::strong_count(&self.rc) == 1 {
self.rc.services.shutdown();
let raw_ctx = self.rc.inner.as_raw();
let key = C::context_id(raw_ctx);
let data = CTX_OPAQUE
.write()
.unwrap()
.remove(&key)
.map(|ptr| ptr as *mut ContextOpaque<C::Value>)
.unwrap_or(std::ptr::null_mut());
if !data.is_null() {
unsafe {
let registry = &(*data).registry;
registry.borrow_mut().clear();
let _ = Box::from_raw(data);
}
}
}
}
}
impl<C: JSContextImpl> Clone for JSContext<C> {
fn clone(&self) -> Self {
Self {
rc: Rc::clone(&self.rc),
}
}
}
impl<C: JSContextImpl> std::fmt::Debug for JSContext<C> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"JSContext {{ address: {:p}, ref_count: {} }}",
self as *const _,
Rc::strong_count(&self.rc)
)
}
}