runtime/globals/
timers.rs

1/*
2 * This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
5 */
6
7use std::time::Duration;
8
9use ion::function::{Clamp, Enforce, Opt, Rest};
10use ion::{Context, Error, Function, Object, Result, function_spec, js_fn};
11use mozjs::jsapi::JSFunctionSpec;
12use mozjs::jsval::JSVal;
13
14use crate::ContextExt as _;
15use crate::event_loop::macrotasks::{Macrotask, TimerMacrotask, UserMacrotask};
16
17const MINIMUM_DELAY: i32 = 1;
18const MINIMUM_DELAY_NESTED: i32 = 4;
19
20fn set_timer(
21	cx: &Context, callback: &Function, duration: Option<Clamp<i32>>, arguments: Box<[JSVal]>, repeat: bool,
22) -> Result<u32> {
23	let event_loop = unsafe { &mut cx.get_private().event_loop };
24	if let Some(queue) = &mut event_loop.macrotasks {
25		let minimum = if queue.nesting > 5 {
26			MINIMUM_DELAY_NESTED
27		} else {
28			MINIMUM_DELAY
29		};
30
31		let duration = duration.map_or(minimum, |t| t.0.max(minimum));
32		let timer = TimerMacrotask::new(
33			callback,
34			arguments,
35			repeat,
36			Duration::from_millis(u64::try_from(duration).unwrap()),
37		);
38		Ok(queue.enqueue(Macrotask::Timer(timer), None))
39	} else {
40		Err(Error::new("Macrotask Queue has not been initialised.", None))
41	}
42}
43
44fn clear_timer(cx: &Context, id: Option<Enforce<u32>>) -> Result<()> {
45	if let Some(id) = id {
46		let event_loop = unsafe { &mut cx.get_private().event_loop };
47		if let Some(queue) = &mut event_loop.macrotasks {
48			queue.remove(id.0);
49			Ok(())
50		} else {
51			Err(Error::new("Macrotask Queue has not been initialised.", None))
52		}
53	} else {
54		Ok(())
55	}
56}
57
58#[js_fn]
59fn set_timeout(
60	cx: &Context, callback: Function, Opt(duration): Opt<Clamp<i32>>, Rest(arguments): Rest<JSVal>,
61) -> Result<u32> {
62	set_timer(cx, &callback, duration, arguments, false)
63}
64
65#[js_fn]
66fn set_interval(
67	cx: &Context, callback: Function, Opt(duration): Opt<Clamp<i32>>, Rest(arguments): Rest<JSVal>,
68) -> Result<u32> {
69	set_timer(cx, &callback, duration, arguments, true)
70}
71
72#[js_fn]
73fn clear_timeout(cx: &Context, Opt(id): Opt<Enforce<u32>>) -> Result<()> {
74	clear_timer(cx, id)
75}
76
77#[js_fn]
78fn clear_interval(cx: &Context, Opt(id): Opt<Enforce<u32>>) -> Result<()> {
79	clear_timer(cx, id)
80}
81
82#[js_fn]
83fn queue_macrotask(cx: &Context, callback: Function) -> Result<()> {
84	let event_loop = unsafe { &mut cx.get_private().event_loop };
85	if let Some(queue) = &mut event_loop.macrotasks {
86		queue.enqueue(Macrotask::User(UserMacrotask::new(&callback)), None);
87		Ok(())
88	} else {
89		Err(Error::new("Macrotask Queue has not been initialised.", None))
90	}
91}
92
93const FUNCTIONS: &[JSFunctionSpec] = &[
94	function_spec!(set_timeout, c"setTimeout", 1),
95	function_spec!(set_interval, c"setInterval", 1),
96	function_spec!(clear_timeout, c"clearTimeout", 0),
97	function_spec!(clear_interval, c"clearInterval", 0),
98	function_spec!(queue_macrotask, c"queueMacrotask", 1),
99	JSFunctionSpec::ZERO,
100];
101
102pub fn define(cx: &Context, global: &Object) -> bool {
103	unsafe { global.define_methods(cx, FUNCTIONS) }
104}