use crate::{ArkJSContext, ArkJSValue, arkjs};
use rong_core::{JSEngine, JSRuntimeImpl};
use std::cell::RefCell;
use std::ffi::CStr;
use std::ptr;
use std::sync::Once;
struct UnhandledRejection {
env: arkjs::JSVM_Env,
promise_ref: arkjs::JSVM_Ref,
value_ref: arkjs::JSVM_Ref,
}
thread_local! {
static UNHANDLED_REJECTIONS: RefCell<Vec<UnhandledRejection>> = RefCell::new(Vec::new());
}
fn store_unhandled_rejection(
env: arkjs::JSVM_Env,
promise: arkjs::JSVM_Value,
value: arkjs::JSVM_Value,
) {
unsafe {
let mut promise_ref: arkjs::JSVM_Ref = ptr::null_mut();
let mut value_ref: arkjs::JSVM_Ref = ptr::null_mut();
let s1 = arkjs::OH_JSVM_CreateReference(env, promise, 1, &mut promise_ref);
let s2 = arkjs::OH_JSVM_CreateReference(env, value, 1, &mut value_ref);
if s1 == arkjs::JSVM_Status_JSVM_OK && s2 == arkjs::JSVM_Status_JSVM_OK {
UNHANDLED_REJECTIONS.with(|v| {
v.borrow_mut().push(UnhandledRejection {
env,
promise_ref,
value_ref,
});
});
} else {
if !promise_ref.is_null() {
arkjs::OH_JSVM_DeleteReference(env, promise_ref);
}
if !value_ref.is_null() {
arkjs::OH_JSVM_DeleteReference(env, value_ref);
}
}
}
}
fn remove_unhandled_rejection_entry(
env: arkjs::JSVM_Env,
promise: arkjs::JSVM_Value,
) -> Option<UnhandledRejection> {
UNHANDLED_REJECTIONS.with(|v| {
let mut rejections = v.borrow_mut();
let mut found_idx = None;
for (i, entry) in rejections.iter().enumerate() {
if entry.env != env {
continue;
}
unsafe {
let mut stored_promise: arkjs::JSVM_Value = ptr::null_mut();
let status =
arkjs::OH_JSVM_GetReferenceValue(env, entry.promise_ref, &mut stored_promise);
if status == arkjs::JSVM_Status_JSVM_OK && !stored_promise.is_null() {
let mut is_equal = false;
let status =
arkjs::OH_JSVM_StrictEquals(env, promise, stored_promise, &mut is_equal);
if status == arkjs::JSVM_Status_JSVM_OK && is_equal {
found_idx = Some(i);
break;
}
}
}
}
found_idx.map(|idx| rejections.remove(idx))
})
}
fn delete_unhandled_rejection_entry(
env: arkjs::JSVM_Env,
entry: UnhandledRejection,
) -> Option<arkjs::JSVM_Value> {
unsafe {
let mut value: arkjs::JSVM_Value = ptr::null_mut();
let status = arkjs::OH_JSVM_GetReferenceValue(env, entry.value_ref, &mut value);
arkjs::OH_JSVM_DeleteReference(env, entry.promise_ref);
arkjs::OH_JSVM_DeleteReference(env, entry.value_ref);
if status == arkjs::JSVM_Status_JSVM_OK && !value.is_null() {
Some(value)
} else {
None
}
}
}
pub(crate) fn take_unhandled_rejection(
env: arkjs::JSVM_Env,
promise: arkjs::JSVM_Value,
) -> Option<arkjs::JSVM_Value> {
remove_unhandled_rejection_entry(env, promise)
.and_then(|entry| delete_unhandled_rejection_entry(env, entry))
}
fn discard_unhandled_rejection(env: arkjs::JSVM_Env, promise: arkjs::JSVM_Value) {
if let Some(entry) = remove_unhandled_rejection_entry(env, promise) {
let _ = delete_unhandled_rejection_entry(env, entry);
}
}
pub(crate) fn clear_unhandled_rejections(env: arkjs::JSVM_Env) {
UNHANDLED_REJECTIONS.with(|v| {
let mut rejections = v.borrow_mut();
let mut idx = 0;
while idx < rejections.len() {
if rejections[idx].env != env {
idx += 1;
continue;
}
let entry = rejections.remove(idx);
unsafe {
arkjs::OH_JSVM_DeleteReference(env, entry.promise_ref);
arkjs::OH_JSVM_DeleteReference(env, entry.value_ref);
}
}
});
}
unsafe extern "C" fn fatal_error_handler(
location: *const std::os::raw::c_char,
message: *const std::os::raw::c_char,
) {
unsafe {
let loc = if location.is_null() {
"<unknown>"
} else {
CStr::from_ptr(location)
.to_str()
.unwrap_or("<invalid utf8>")
};
let msg = if message.is_null() {
"<no message>"
} else {
CStr::from_ptr(message).to_str().unwrap_or("<invalid utf8>")
};
eprintln!("[ArkJS FATAL] {}: {}", loc, msg);
}
}
unsafe extern "C" fn oom_error_handler(
location: *const std::os::raw::c_char,
detail: *const std::os::raw::c_char,
is_heap_oom: bool,
) {
unsafe {
let loc = if location.is_null() {
"<unknown>"
} else {
CStr::from_ptr(location)
.to_str()
.unwrap_or("<invalid utf8>")
};
let det = if detail.is_null() {
"<no detail>"
} else {
CStr::from_ptr(detail).to_str().unwrap_or("<invalid utf8>")
};
eprintln!("[ArkJS OOM] {}: {} (heap_oom={})", loc, det, is_heap_oom);
}
}
unsafe extern "C" fn promise_reject_handler(
env: arkjs::JSVM_Env,
reject_event: arkjs::JSVM_PromiseRejectEvent,
reject_info: arkjs::JSVM_Value,
) {
if reject_info.is_null() {
return;
}
unsafe {
let mut promise: arkjs::JSVM_Value = ptr::null_mut();
let mut value: arkjs::JSVM_Value = ptr::null_mut();
arkjs::OH_JSVM_GetNamedProperty(env, reject_info, c"promise".as_ptr() as _, &mut promise);
arkjs::OH_JSVM_GetNamedProperty(env, reject_info, c"value".as_ptr() as _, &mut value);
if promise.is_null() {
return;
}
match reject_event {
arkjs::JSVM_PromiseRejectEvent_JSVM_PROMISE_REJECT_WITH_NO_HANDLER => {
if !value.is_null() {
store_unhandled_rejection(env, promise, value);
}
}
arkjs::JSVM_PromiseRejectEvent_JSVM_PROMISE_ADD_HANDLER_AFTER_REJECTED => {
discard_unhandled_rejection(env, promise);
}
_ => {}
}
}
}
static JSVM_INIT: Once = Once::new();
fn ensure_jsvm_initialized() {
JSVM_INIT.call_once(|| {
let init_options = arkjs::JSVM_InitOptions {
externalReferences: ptr::null(),
argc: ptr::null_mut(),
argv: ptr::null_mut(),
removeFlags: false,
};
unsafe {
let status = arkjs::OH_JSVM_Init(&init_options);
if status != arkjs::JSVM_Status_JSVM_OK {
panic!("Failed to initialize Ark JS VM: {:?}", status);
}
}
});
}
pub struct ArkJSRuntime {
raw: arkjs::JSVM_VM,
vm_scope: arkjs::JSVM_VMScope,
}
impl JSRuntimeImpl for ArkJSRuntime {
type RawRuntime = arkjs::JSVM_VM;
type Context = ArkJSContext;
fn new() -> Self {
ensure_jsvm_initialized();
let mut vm: arkjs::JSVM_VM = ptr::null_mut();
let mut vm_scope: arkjs::JSVM_VMScope = ptr::null_mut();
unsafe {
let create_options = arkjs::JSVM_CreateVMOptions {
maxOldGenerationSize: 0,
maxYoungGenerationSize: 0,
initialOldGenerationSize: 0,
initialYoungGenerationSize: 0,
snapshotBlobData: ptr::null(),
snapshotBlobSize: 0,
isForSnapshotting: false,
};
let status = arkjs::OH_JSVM_CreateVM(&create_options, &mut vm);
if status != arkjs::JSVM_Status_JSVM_OK {
panic!("Failed to create Ark JS VM: {:?}", status);
}
let status = arkjs::OH_JSVM_OpenVMScope(vm, &mut vm_scope);
if status != arkjs::JSVM_Status_JSVM_OK {
arkjs::OH_JSVM_DestroyVM(vm);
panic!("Failed to open VM scope: {:?}", status);
}
arkjs::OH_JSVM_SetHandlerForFatalError(vm, Some(fatal_error_handler));
arkjs::OH_JSVM_SetHandlerForOOMError(vm, Some(oom_error_handler));
arkjs::OH_JSVM_SetHandlerForPromiseReject(vm, Some(promise_reject_handler));
}
Self { raw: vm, vm_scope }
}
fn to_raw(&self) -> Self::RawRuntime {
self.raw
}
fn run_pending_jobs(&self) -> i32 {
unsafe {
let _ = arkjs::OH_JSVM_PerformMicrotaskCheckpoint(self.raw);
}
0
}
fn run_gc(&self) {
}
}
impl Drop for ArkJSRuntime {
fn drop(&mut self) {
if !self.raw.is_null() {
unsafe {
if !self.vm_scope.is_null() {
arkjs::OH_JSVM_CloseVMScope(self.raw, self.vm_scope);
}
arkjs::OH_JSVM_DestroyVM(self.raw);
}
}
}
}
pub struct HarmonyArkJS;
impl JSEngine for HarmonyArkJS {
type Value = ArkJSValue;
type Context = ArkJSContext;
type Runtime = ArkJSRuntime;
fn name() -> &'static str {
"HarmonyArkJS"
}
fn version() -> String {
String::from("ArkJS 1.0.0")
}
}