1use crate::jsutils::JsError;
2use crate::quickjs_utils;
3use crate::quickjs_utils::{functions, get_global, objects, parse_args, primitives};
4use crate::quickjsruntimeadapter::QuickJsRuntimeAdapter;
5use hirofa_utils::eventloop::EventLoop;
6use libquickjs_sys as q;
7use std::time::Duration;
8
9pub fn init(q_js_rt: &QuickJsRuntimeAdapter) -> Result<(), JsError> {
20 log::trace!("set_timeout::init");
21
22 q_js_rt.add_context_init_hook(|_q_js_rt, q_ctx| {
23 let global = unsafe { get_global(q_ctx.context) };
24 #[cfg(feature = "settimeout")]
25 {
26 let set_timeout_func =
27 functions::new_native_function_q(q_ctx, "setTimeout", Some(set_timeout), 2, false)?;
28 let clear_timeout_func = functions::new_native_function_q(
29 q_ctx,
30 "clearTimeout",
31 Some(clear_timeout),
32 1,
33 false,
34 )?;
35 objects::set_property2_q(q_ctx, &global, "setTimeout", &set_timeout_func, 0)?;
36 objects::set_property2_q(q_ctx, &global, "clearTimeout", &clear_timeout_func, 0)?;
37 }
38 #[cfg(feature = "setinterval")]
39 {
40 let set_interval_func = functions::new_native_function_q(
41 q_ctx,
42 "setInterval",
43 Some(set_interval),
44 2,
45 false,
46 )?;
47 let clear_interval_func = functions::new_native_function_q(
48 q_ctx,
49 "clearInterval",
50 Some(clear_interval),
51 1,
52 false,
53 )?;
54
55 objects::set_property2_q(q_ctx, &global, "setInterval", &set_interval_func, 0)?;
56 objects::set_property2_q(q_ctx, &global, "clearInterval", &clear_interval_func, 0)?;
57 }
58 Ok(())
59 })?;
60 Ok(())
61}
62
63#[cfg(feature = "settimeout")]
64unsafe extern "C" fn set_timeout(
65 context: *mut q::JSContext,
66 _this_val: q::JSValue,
67 argc: ::std::os::raw::c_int,
68 argv: *mut q::JSValue,
69) -> q::JSValue {
70 log::trace!("> set_timeout");
71
72 let args = parse_args(context, argc, argv);
73
74 QuickJsRuntimeAdapter::do_with(move |q_js_rt| {
75 let q_ctx = q_js_rt.get_quickjs_context(context);
76 if args.is_empty() {
77 return q_ctx.report_ex("setTimeout requires at least one argument");
78 }
79 if !functions::is_function(context, &args[0]) {
80 return q_ctx.report_ex("setTimeout requires a function as first arg");
81 }
82
83 if args.len() >= 2 && !args[1].is_i32() && !args[1].is_f64() {
84 return q_ctx.report_ex("setTimeout requires a number as second arg");
85 }
86
87 let delay_ms = if args.len() >= 2 {
88 let delay_ref = &args[1];
89 if delay_ref.is_i32() {
90 primitives::to_i32(delay_ref).ok().unwrap() as u64
91 } else {
92 primitives::to_f64(delay_ref).ok().unwrap() as u64
93 }
94 } else {
95 0
96 };
97
98 let q_ctx_id = q_ctx.id.clone();
99
100 let id = EventLoop::add_timeout(
101 move || {
102 QuickJsRuntimeAdapter::do_with(|q_js_rt| {
103 let func = &args[0];
104 if let Some(q_ctx) = q_js_rt.opt_context(q_ctx_id.as_str()) {
105 match functions::call_function_q(q_ctx, func, &args[2..], None) {
106 Ok(_) => {}
107 Err(e) => {
108 log::error!("setTimeout func failed: {}", e);
109 }
110 };
111 } else {
112 log::error!("setTimeout func failed: no such context: {}", q_ctx_id);
113 }
114 q_js_rt.run_pending_jobs_if_any();
115 })
116 },
117 Duration::from_millis(delay_ms),
118 );
119 log::trace!("set_timeout: {}", id);
120 primitives::from_i32(id).clone_value_incr_rc()
121 })
122}
123
124#[cfg(feature = "setinterval")]
125unsafe extern "C" fn set_interval(
126 context: *mut q::JSContext,
127 _this_val: q::JSValue,
128 argc: ::std::os::raw::c_int,
129 argv: *mut q::JSValue,
130) -> q::JSValue {
131 log::trace!("> set_interval");
132
133 let args = parse_args(context, argc, argv);
134
135 QuickJsRuntimeAdapter::do_with(|q_js_rt| {
136 let q_ctx = q_js_rt.get_quickjs_context(context);
137 if args.is_empty() {
138 return q_ctx.report_ex("setInterval requires at least one argument");
139 }
140 if !functions::is_function(context, &args[0]) {
141 return q_ctx.report_ex("setInterval requires a functions as first arg");
142 }
143
144 if args.len() >= 2 && !args[1].is_i32() && !args[1].is_f64() {
145 return q_ctx.report_ex("setInterval requires a number as second arg");
146 }
147
148 let delay_ms = if args.len() >= 2 {
149 let delay_ref = &args[1];
150 if delay_ref.is_i32() {
151 primitives::to_i32(delay_ref).ok().unwrap() as u64
152 } else {
153 primitives::to_f64(delay_ref).ok().unwrap() as u64
154 }
155 } else {
156 0
157 };
158
159 let q_ctx_id = q_ctx.id.clone();
160
161 let id = EventLoop::add_interval(
162 move || {
163 QuickJsRuntimeAdapter::do_with(|q_js_rt| {
164 if let Some(q_ctx) = q_js_rt.opt_context(q_ctx_id.as_str()) {
165 let func = &args[0];
166
167 match functions::call_function_q(q_ctx, func, &args[2..], None) {
168 Ok(_) => {}
169 Err(e) => {
170 log::error!("setInterval func failed: {}", e);
171 }
172 };
173 } else {
174 log::error!("setInterval func failed: no such context: {}", q_ctx_id);
175 }
176 q_js_rt.run_pending_jobs_if_any();
177 })
178 },
179 Duration::from_millis(delay_ms),
180 Duration::from_millis(delay_ms),
181 );
182 log::trace!("set_interval: {}", id);
183 primitives::from_i32(id).clone_value_incr_rc()
184 })
185}
186
187#[cfg(feature = "setinterval")]
188unsafe extern "C" fn clear_interval(
189 context: *mut q::JSContext,
190 _this_val: q::JSValue,
191 argc: ::std::os::raw::c_int,
192 argv: *mut q::JSValue,
193) -> q::JSValue {
194 log::trace!("> clear_interval");
195
196 let args = parse_args(context, argc, argv);
197 QuickJsRuntimeAdapter::do_with(|q_js_rt| {
198 let q_ctx = q_js_rt.get_quickjs_context(context);
199 if args.is_empty() {
200 return q_ctx.report_ex("clearInterval requires at least one argument");
201 }
202 if !&args[0].is_i32() {
203 return q_ctx.report_ex("clearInterval requires a number as first arg");
204 }
205 let id = primitives::to_i32(&args[0]).ok().unwrap();
206 log::trace!("clear_interval: {}", id);
207 EventLoop::clear_interval(id);
208 quickjs_utils::new_null()
209 })
210}
211
212#[cfg(feature = "settimeout")]
213unsafe extern "C" fn clear_timeout(
214 context: *mut q::JSContext,
215 _this_val: q::JSValue,
216 argc: ::std::os::raw::c_int,
217 argv: *mut q::JSValue,
218) -> q::JSValue {
219 log::trace!("> clear_timeout");
220
221 let args = parse_args(context, argc, argv);
222
223 QuickJsRuntimeAdapter::do_with(move |q_js_rt| {
224 let q_ctx = q_js_rt.get_quickjs_context(context);
225 if args.is_empty() {
226 return q_ctx.report_ex("clearTimeout requires at least one argument");
227 }
228 if !&args[0].is_i32() {
229 return q_ctx.report_ex("clearTimeout requires a number as first arg");
230 }
231 let id = primitives::to_i32(&args[0]).ok().unwrap();
232 log::trace!("clear_timeout: {}", id);
233
234 EventLoop::clear_timeout(id);
235
236 quickjs_utils::new_null()
237 })
238}
239
240#[cfg(test)]
241pub mod tests {
242 use crate::facades::tests::init_test_rt;
243 use crate::jsutils::Script;
244 use crate::quickjs_utils::get_global_q;
245 use crate::quickjs_utils::objects::get_property_q;
246 use crate::quickjs_utils::primitives::to_i32;
247 use crate::values::JsValueFacade;
248 use std::time::Duration;
249
250 #[test]
251 fn test_set_timeout_prom_res() {
252 let rt = init_test_rt();
253 let esvf = rt
254 .eval_sync(
255 None,
256 Script::new(
257 "test_set_timeout_prom_res.es",
258 "new Promise((resolve, reject) => {\
259 setTimeout(() => {resolve(123);}, 1000);\
260 }).then((res) => {\
261 console.log(\"got %s\", res);\
262 return res + \"abc\";\
263 }).catch((ex) => {\
264 throw Error(\"\" + ex);\
265 });\
266 ",
267 ),
268 )
269 .expect("script failed");
270 assert!(esvf.is_js_promise());
271
272 let res = match esvf {
273 JsValueFacade::JsPromise { cached_promise } => {
274 cached_promise.get_promise_result_sync().expect("timed out")
275 }
276 _ => Err(JsValueFacade::new_str("poof")),
277 };
278
279 assert!(res.is_ok());
280 let res_str_esvf = res.ok().unwrap();
281 let res_str = res_str_esvf.get_str();
282 assert_eq!(res_str, "123abc");
283 log::info!("done");
284 }
285
286 #[test]
287 fn test_set_timeout_prom_res_nested() {
288 let rt = init_test_rt();
289 let esvf = rt
290 .eval_sync(
291 None,
292 Script::new(
293 "test_set_timeout_prom_res_nested.es",
294 "new Promise((resolve, reject) => {\
295 setTimeout(() => {setTimeout(() => {resolve(123);}, 1000);}, 1000);\
296 }).then((res) => {\
297 console.log(\"got %s\", res);\
298 return res + \"abc\";\
299 }).catch((ex) => {\
300 throw Error(\"\" + ex);\
301 });\
302 ",
303 ),
304 )
305 .expect("script failed");
306 assert!(esvf.is_js_promise());
307
308 match esvf {
309 JsValueFacade::JsPromise { cached_promise } => {
310 let res = cached_promise.get_promise_result_sync().expect("timed out");
311 assert!(res.is_ok());
312 let res_str_esvf = res.ok().unwrap();
313 let res_str = res_str_esvf.get_str();
314 assert_eq!(res_str, "123abc");
315 }
316 _ => {}
317 }
318
319 log::info!("done");
320 }
321
322 #[test]
323 fn test_set_timeout() {
324 let rt = init_test_rt();
325
326 rt.eval_sync(None, Script::new("test_set_interval.es", "let t_id1 = setInterval((a, b) => {console.log('setInterval invoked with %s and %s', a, b);}, 500, 123, 456);")).expect("fail a");
327 rt.eval_sync(None, Script::new("test_set_timeout.es", "let t_id2 = setTimeout((a, b) => {console.log('setTimeout1 invoked with %s and %s', a, b);}, 500, 123, 456);")).expect("fail b");
328 rt.eval_sync(None, Script::new("test_set_timeout.es", "let t_id3 = setTimeout((a, b) => {console.log('setTimeout2 invoked with %s and %s', a, b);}, 600, 123, 456);")).expect("fail b");
329 rt.eval_sync(None, Script::new("test_set_timeout.es", "let t_id4 = setTimeout((a, b) => {console.log('setTimeout3 invoked with %s and %s', a, b);}, 900, 123, 456);")).expect("fail b");
330 std::thread::sleep(Duration::from_secs(3));
331 rt.eval_sync(
332 None,
333 Script::new("test_clearInterval.es", "clearInterval(t_id1);"),
334 )
335 .expect("fail c");
336 rt.eval_sync(
337 None,
338 Script::new("test_clearTimeout2.es", "clearTimeout(t_id2);"),
339 )
340 .expect("fail d");
341
342 rt.eval_sync(
343 None,
344 Script::new("test_set_timeout2.es", "this.__ti_num__ = 0;"),
345 )
346 .expect("fail qewr");
347
348 rt.eval_sync(
349 None,
350 Script::new("test_set_timeout2.es", "this.__it_num__ = 0;"),
351 )
352 .expect("fail qewr");
353
354 rt.eval_sync(
355 None,
356 Script::new(
357 "test_set_timeout3.es",
358 "setTimeout(() => {console.log('seto1');this.__ti_num__++;}, 455);",
359 ),
360 )
361 .expect("fail a1");
362 rt.eval_sync(
363 None,
364 Script::new(
365 "test_set_timeout3.es",
366 "setTimeout(() => {console.log('seto2');this.__ti_num__++;}, 366);",
367 ),
368 )
369 .expect("fail a2");
370 rt.eval_sync(
371 None,
372 Script::new(
373 "test_set_timeout3.es",
374 "setTimeout(() => {console.log('seto3');this.__ti_num__++;}, 1001);",
375 ),
376 )
377 .expect("fail a3");
378 rt.eval_sync(
379 None,
380 Script::new(
381 "test_set_timeout3.es",
382 "setTimeout(() => {console.log('seto4');this.__ti_num__++;}, 2002);",
383 ),
384 )
385 .expect("fail a4");
386
387 rt.eval_sync(
388 None,
389 Script::new(
390 "test_set_interval.es",
391 "setInterval(() => {this.__it_num__++;}, 1600);",
392 ),
393 )
394 .expect("fail a");
395 rt.eval_sync(
396 None,
397 Script::new(
398 "test_set_interval.es",
399 "setInterval(() => {this.__it_num__++;}, 2500);",
400 ),
401 )
402 .expect("fail a");
403
404 std::thread::sleep(Duration::from_secs(6));
405
406 let i = rt.exe_rt_task_in_event_loop(|q_js_rt| {
407 let q_ctx = q_js_rt.get_main_realm();
408 let global = get_global_q(q_ctx);
409 let ti_num = get_property_q(q_ctx, &global, "__ti_num__")
410 .ok()
411 .expect("could not get ti num prop from global");
412 let it_num = get_property_q(q_ctx, &global, "__it_num__")
413 .ok()
414 .expect("could not get it num prop from global");
415
416 (
417 to_i32(&ti_num)
418 .ok()
419 .expect("could not convert ti num to num"),
420 to_i32(&it_num)
421 .ok()
422 .expect("could not convert it num to num"),
423 )
424 });
425 assert_eq!(i.1, 5);
426 assert_eq!(i.0, 4);
427
428 rt.gc_sync();
429 }
430}