use once_cell::sync::Lazy;
use rusty_v8 as v8;
use std::collections::HashMap;
use std::sync::atomic::AtomicU64;
use std::sync::atomic::Ordering;
use std::sync::{Arc, Mutex};
static TIMER_ID_COUNTER: AtomicU64 = AtomicU64::new(1);
fn next_timer_id() -> u64 {
TIMER_ID_COUNTER.fetch_add(1, Ordering::SeqCst)
}
static CLEARED_TIMERS: Lazy<Arc<Mutex<HashMap<u64, bool>>>> =
Lazy::new(|| Arc::new(Mutex::new(HashMap::new())));
#[allow(dead_code)]
fn is_timer_cleared(id: u64) -> bool {
CLEARED_TIMERS
.lock()
.unwrap()
.get(&id)
.copied()
.unwrap_or(false)
}
fn mark_timer_cleared(id: u64) {
CLEARED_TIMERS.lock().unwrap().insert(id, true);
}
pub fn setup_timer_api(
scope: &mut v8::ContextScope<v8::HandleScope>,
context: &v8::Local<v8::Context>,
) -> anyhow::Result<()> {
let global: _ = context.global(scope);
let set_timeout_template: _ = v8::FunctionTemplate::new(scope, set_timeout_callback);
let set_timeout_func: _ = set_timeout_template.get_function(scope).unwrap();
let set_timeout_key: _ = v8::String::new(scope, "setTimeout").unwrap();
global.set(scope, set_timeout_key.into(), set_timeout_func.into());
let set_interval_template: _ = v8::FunctionTemplate::new(scope, set_interval_callback);
let set_interval_func: _ = set_interval_template.get_function(scope).unwrap();
let set_interval_key: _ = v8::String::new(scope, "setInterval").unwrap();
global.set(scope, set_interval_key.into(), set_interval_func.into());
let clear_timeout_template: _ = v8::FunctionTemplate::new(scope, clear_timeout_callback);
let clear_timeout_func: _ = clear_timeout_template.get_function(scope).unwrap();
let clear_timeout_key: _ = v8::String::new(scope, "clearTimeout").unwrap();
global.set(scope, clear_timeout_key.into(), clear_timeout_func.into());
let clear_interval_template: _ = v8::FunctionTemplate::new(scope, clear_interval_callback);
let clear_interval_func: _ = clear_interval_template.get_function(scope).unwrap();
let clear_interval_key: _ = v8::String::new(scope, "clearInterval").unwrap();
global.set(scope, clear_interval_key.into(), clear_interval_func.into());
let queue_microtask_template: _ = v8::FunctionTemplate::new(scope, queue_microtask_callback);
let queue_microtask_func: _ = queue_microtask_template.get_function(scope).unwrap();
let queue_microtask_key: _ = v8::String::new(scope, "queueMicrotask").unwrap();
global.set(
scope,
queue_microtask_key.into(),
queue_microtask_func.into(),
);
let set_immediate_template: _ = v8::FunctionTemplate::new(scope, set_immediate_callback);
let set_immediate_func: _ = set_immediate_template.get_function(scope).unwrap();
let set_immediate_key: _ = v8::String::new(scope, "setImmediate").unwrap();
global.set(scope, set_immediate_key.into(), set_immediate_func.into());
let clear_immediate_template: _ = v8::FunctionTemplate::new(scope, clear_immediate_callback);
let clear_immediate_func: _ = clear_immediate_template.get_function(scope).unwrap();
let clear_immediate_key: _ = v8::String::new(scope, "clearImmediate").unwrap();
global.set(
scope,
clear_immediate_key.into(),
clear_immediate_func.into(),
);
Ok(())
}
fn set_timeout_callback(
scope: &mut v8::PinScope,
args: v8::FunctionCallbackArguments,
mut retval: v8::ReturnValue,
) {
let timer_id: _ = next_timer_id();
let callback: _ = args.get(0);
if !callback.is_function() {
let error: _ = v8::String::new(scope, "setTimeout: callback must be a function").unwrap();
let error_obj: _ = v8::Exception::type_error(scope, error);
scope.throw_exception(error_obj.into());
return;
}
let delay_ms: _ = args
.get(1)
.to_integer(scope)
.map(|i| i.value().max(0) as u64)
.unwrap_or(0);
if delay_ms == 0 {
let callback_func: _ = v8::Local::<v8::Function>::try_from(callback).unwrap();
let undefined: _ = v8::undefined(scope);
let _: _ = callback_func.call(scope, undefined.into(), &[]);
} else {
eprintln!(
"⚠️ setTimeout with delay {}ms - async timers require event loop integration",
delay_ms
);
}
let timer_id_val: _ = v8::Number::new(scope, timer_id as f64);
retval.set(timer_id_val.into());
}
fn set_interval_callback(
scope: &mut v8::PinScope,
args: v8::FunctionCallbackArguments,
mut retval: v8::ReturnValue,
) {
let timer_id: _ = next_timer_id();
let callback: _ = args.get(0);
if !callback.is_function() {
let error: _ = v8::String::new(scope, "setInterval: callback must be a function").unwrap();
let error_obj: _ = v8::Exception::type_error(scope, error);
scope.throw_exception(error_obj.into());
return;
}
let interval_ms: _ = args
.get(1)
.to_integer(scope)
.map(|i| i.value().max(4) as u64) .unwrap_or(4);
eprintln!(
"⚠️ setInterval with interval {}ms - async timers require event loop integration",
interval_ms
);
let timer_id_val: _ = v8::Number::new(scope, timer_id as f64);
retval.set(timer_id_val.into());
}
fn clear_timeout_callback(
scope: &mut v8::PinScope,
args: v8::FunctionCallbackArguments,
_retval: v8::ReturnValue,
) {
let timer_id: _ = args
.get(0)
.to_integer(scope)
.map(|i| i.value() as u64)
.unwrap_or(0);
if timer_id > 0 {
mark_timer_cleared(timer_id);
}
}
fn clear_interval_callback(
scope: &mut v8::PinScope,
args: v8::FunctionCallbackArguments,
_retval: v8::ReturnValue,
) {
let timer_id: _ = args
.get(0)
.to_integer(scope)
.map(|i| i.value() as u64)
.unwrap_or(0);
if timer_id > 0 {
mark_timer_cleared(timer_id);
}
}
fn queue_microtask_callback(
scope: &mut v8::PinScope,
args: v8::FunctionCallbackArguments,
_retval: v8::ReturnValue,
) {
let callback: _ = args.get(0);
if !callback.is_function() {
let error: _ =
v8::String::new(scope, "queueMicrotask: callback must be a function").unwrap();
let error_obj: _ = v8::Exception::type_error(scope, error);
scope.throw_exception(error_obj.into());
return;
}
let callback_func: _ = v8::Local::<v8::Function>::try_from(callback).unwrap();
let undefined: _ = v8::undefined(scope);
let _: _ = callback_func.call(scope, undefined.into(), &[]);
}
fn set_immediate_callback(
scope: &mut v8::PinScope,
args: v8::FunctionCallbackArguments,
mut retval: v8::ReturnValue,
) {
let timer_id: _ = next_timer_id();
let callback: _ = args.get(0);
if !callback.is_function() {
let error: _ = v8::String::new(scope, "setImmediate: callback must be a function").unwrap();
let error_obj: _ = v8::Exception::type_error(scope, error);
scope.throw_exception(error_obj.into());
return;
}
let callback_args: Vec<v8::Local<v8::Value>> =
(1..args.length()).map(|i| args.get(i)).collect();
let callback_func: _ = v8::Local::<v8::Function>::try_from(callback).unwrap();
let undefined: _ = v8::undefined(scope);
let _: _ = callback_func.call(scope, undefined.into(), &callback_args);
let timer_id_val: _ = v8::Number::new(scope, timer_id as f64);
retval.set(timer_id_val.into());
}
fn clear_immediate_callback(
scope: &mut v8::PinScope,
args: v8::FunctionCallbackArguments,
_retval: v8::ReturnValue,
) {
let timer_id: _ = args
.get(0)
.to_integer(scope)
.map(|i| i.value() as u64)
.unwrap_or(0);
if timer_id > 0 {
mark_timer_cleared(timer_id);
}
}
#[cfg(test)]
mod tests {
use super::{is_timer_cleared, mark_timer_cleared, next_timer_id};
#[test]
fn test_timer_id_generation() {
let id1: _ = next_timer_id();
let id2: _ = next_timer_id();
assert!(id2 > id1);
}
#[test]
fn test_timer_clearing() {
let id: _ = next_timer_id();
assert!(!is_timer_cleared(id));
mark_timer_cleared(id);
assert!(is_timer_cleared(id));
}
}