use super::bindings;
use crate::error::exception_to_err_result;
use crate::joinset::JoinSet;
use crate::modules::ModuleCode;
use crate::ops::OpCtx;
use crate::runtime::JsRuntimeState;
use crate::JsRuntime;
use crate::OpId;
use crate::OpResult;
use crate::PromiseId;
use anyhow::Error;
use std::cell::RefCell;
use std::collections::HashSet;
use std::collections::VecDeque;
use std::hash::BuildHasherDefault;
use std::hash::Hasher;
use std::option::Option;
use std::rc::Rc;
use v8::HandleScope;
use v8::Local;
#[derive(Default)]
pub(crate) struct IdentityHasher(u64);
impl Hasher for IdentityHasher {
fn write_i32(&mut self, i: i32) {
self.0 = i as u64;
}
fn finish(&self) -> u64 {
self.0
}
fn write(&mut self, _bytes: &[u8]) {
unreachable!()
}
}
#[derive(Default)]
pub(crate) struct ContextState {
pub(crate) js_event_loop_tick_cb: Option<Rc<v8::Global<v8::Function>>>,
pub(crate) js_build_custom_error_cb: Option<Rc<v8::Global<v8::Function>>>,
pub(crate) js_promise_reject_cb: Option<Rc<v8::Global<v8::Function>>>,
pub(crate) js_format_exception_cb: Option<Rc<v8::Global<v8::Function>>>,
pub(crate) js_wasm_streaming_cb: Option<Rc<v8::Global<v8::Function>>>,
pub(crate) pending_promise_rejections:
VecDeque<(v8::Global<v8::Promise>, v8::Global<v8::Value>)>,
pub(crate) unrefed_ops: HashSet<i32, BuildHasherDefault<IdentityHasher>>,
pub(crate) pending_ops: JoinSet<(PromiseId, OpId, OpResult)>,
pub(crate) op_ctxs: Box<[OpCtx]>,
pub(crate) isolate: Option<*mut v8::OwnedIsolate>,
}
#[derive(Clone)]
#[repr(transparent)]
pub struct JsRealm(pub(crate) JsRealmInner);
#[derive(Clone)]
pub(crate) struct JsRealmInner {
context_state: Rc<RefCell<ContextState>>,
context: Rc<v8::Global<v8::Context>>,
runtime_state: Rc<RefCell<JsRuntimeState>>,
is_global: bool,
}
impl JsRealmInner {
pub(crate) fn new(
context_state: Rc<RefCell<ContextState>>,
context: v8::Global<v8::Context>,
runtime_state: Rc<RefCell<JsRuntimeState>>,
is_global: bool,
) -> Self {
Self {
context_state,
context: context.into(),
runtime_state,
is_global,
}
}
pub fn num_pending_ops(&self) -> usize {
self.context_state.borrow().pending_ops.len()
}
pub fn num_unrefed_ops(&self) -> usize {
self.context_state.borrow().unrefed_ops.len()
}
#[inline(always)]
pub fn context(&self) -> &v8::Global<v8::Context> {
&self.context
}
#[inline(always)]
pub(crate) fn state(&self) -> Rc<RefCell<ContextState>> {
self.context_state.clone()
}
#[inline(always)]
pub fn handle_scope<'s>(
&self,
isolate: &'s mut v8::Isolate,
) -> v8::HandleScope<'s> {
v8::HandleScope::with_context(isolate, &*self.context)
}
pub(crate) fn check_promise_rejections(
&self,
scope: &mut v8::HandleScope,
) -> Result<(), Error> {
let Some((_, handle)) = self.context_state.borrow_mut().pending_promise_rejections.pop_front() else {
return Ok(());
};
let exception = v8::Local::new(scope, handle);
let state_rc = JsRuntime::state_from(scope);
let state = state_rc.borrow();
if let Some(inspector) = &state.inspector {
let inspector = inspector.borrow();
inspector.exception_thrown(scope, exception, true);
if inspector.has_blocking_sessions() {
return Ok(());
}
}
exception_to_err_result(scope, exception, true)
}
pub(crate) fn is_same(&self, other: &Rc<v8::Global<v8::Context>>) -> bool {
Rc::ptr_eq(&self.context, other)
}
pub fn destroy(self) {
let state = self.state();
let raw_ptr = self.state().borrow().isolate.unwrap();
let isolate = unsafe { raw_ptr.as_mut().unwrap() };
let mut realm_state = state.borrow_mut();
std::mem::take(&mut realm_state.js_event_loop_tick_cb);
std::mem::take(&mut realm_state.js_build_custom_error_cb);
std::mem::take(&mut realm_state.js_promise_reject_cb);
std::mem::take(&mut realm_state.js_format_exception_cb);
std::mem::take(&mut realm_state.js_wasm_streaming_cb);
std::mem::take(&mut realm_state.op_ctxs);
self.context().open(isolate).clear_all_slots(isolate);
}
}
impl JsRealm {
pub(crate) fn new(inner: JsRealmInner) -> Self {
Self(inner)
}
#[inline(always)]
pub(crate) fn state_from_scope(
scope: &mut v8::HandleScope,
) -> Rc<RefCell<ContextState>> {
let context = scope.get_current_context();
context
.get_slot::<Rc<RefCell<ContextState>>>(scope)
.unwrap()
.clone()
}
#[inline(always)]
pub fn num_pending_ops(&self) -> usize {
self.0.num_pending_ops()
}
#[inline(always)]
pub fn num_unrefed_ops(&self) -> usize {
self.0.num_unrefed_ops()
}
#[inline(always)]
pub fn handle_scope<'s>(
&self,
isolate: &'s mut v8::Isolate,
) -> v8::HandleScope<'s> {
self.0.handle_scope(isolate)
}
#[inline(always)]
pub fn context(&self) -> &v8::Global<v8::Context> {
self.0.context()
}
pub fn global_object<'s>(
&self,
isolate: &'s mut v8::Isolate,
) -> v8::Local<'s, v8::Object> {
let scope = &mut self.0.handle_scope(isolate);
self.0.context.open(scope).global(scope)
}
fn string_from_code<'a>(
scope: &mut HandleScope<'a>,
code: &ModuleCode,
) -> Option<Local<'a, v8::String>> {
if let Some(code) = code.try_static_ascii() {
v8::String::new_external_onebyte_static(scope, code)
} else {
v8::String::new_from_utf8(
scope,
code.as_bytes(),
v8::NewStringType::Normal,
)
}
}
pub fn execute_script_static(
&self,
isolate: &mut v8::Isolate,
name: &'static str,
source_code: &'static str,
) -> Result<v8::Global<v8::Value>, Error> {
self.execute_script(isolate, name, ModuleCode::from_static(source_code))
}
pub fn execute_script(
&self,
isolate: &mut v8::Isolate,
name: &'static str,
source_code: ModuleCode,
) -> Result<v8::Global<v8::Value>, Error> {
let scope = &mut self.0.handle_scope(isolate);
let source = Self::string_from_code(scope, &source_code).unwrap();
debug_assert!(name.is_ascii());
let name =
v8::String::new_external_onebyte_static(scope, name.as_bytes()).unwrap();
let origin = bindings::script_origin(scope, name);
let tc_scope = &mut v8::TryCatch::new(scope);
let script = match v8::Script::compile(tc_scope, source, Some(&origin)) {
Some(script) => script,
None => {
let exception = tc_scope.exception().unwrap();
return exception_to_err_result(tc_scope, exception, false);
}
};
match script.run(tc_scope) {
Some(value) => {
let value_handle = v8::Global::new(tc_scope, value);
Ok(value_handle)
}
None => {
assert!(tc_scope.has_caught());
let exception = tc_scope.exception().unwrap();
exception_to_err_result(tc_scope, exception, false)
}
}
}
}
impl Drop for JsRealm {
fn drop(&mut self) {
if self.0.is_global {
return;
}
if Rc::strong_count(&self.0.context) == 2 {
self
.0
.runtime_state
.borrow_mut()
.remove_realm(&self.0.context);
assert_eq!(Rc::strong_count(&self.0.context), 1);
self.0.clone().destroy();
assert_eq!(Rc::strong_count(&self.0.context_state), 1);
}
}
}