runtime/event_loop/
microtasks.rs1use std::collections::vec_deque::VecDeque;
8use std::ffi::c_void;
9
10use ion::{Context, ErrorReport, Function, Object};
11use mozjs::glue::JobQueueTraps;
12use mozjs::jsapi::{Handle, JSContext, JSFunction, JSObject, JobQueueIsEmpty, JobQueueMayNotBeEmpty, MutableHandle};
13
14use crate::ContextExt as _;
15
16#[derive(Clone, Debug)]
17pub enum Microtask {
18 Promise(*mut JSObject),
19 User(*mut JSFunction),
20}
21
22#[derive(Clone, Debug, Default)]
23pub struct MicrotaskQueue {
24 queue: VecDeque<Microtask>,
25 draining: bool,
26}
27
28impl Microtask {
29 pub fn run(&self, cx: &Context) -> Result<(), Option<ErrorReport>> {
30 match self {
31 Microtask::Promise(job) => {
32 let object = cx.root(*job);
33 let function = Function::from_object(cx, &object).unwrap();
34
35 function.call(cx, &Object::null(cx), &[]).map(|_| ())
36 }
37 Microtask::User(callback) => {
38 let callback = Function::from(cx.root(*callback));
39 callback.call(cx, &Object::global(cx), &[]).map(|_| ())
40 }
41 }
42 }
43}
44
45impl MicrotaskQueue {
46 pub fn enqueue(&mut self, cx: &Context, microtask: Microtask) {
47 self.queue.push_back(microtask);
48 unsafe { JobQueueMayNotBeEmpty(cx.as_ptr()) }
49 }
50
51 pub fn run_jobs(&mut self, cx: &Context) -> Result<(), Option<ErrorReport>> {
52 if self.draining {
53 return Ok(());
54 }
55
56 self.draining = true;
57
58 while let Some(microtask) = self.queue.pop_front() {
59 microtask.run(cx)?;
60 }
61
62 self.draining = false;
63 unsafe { JobQueueIsEmpty(cx.as_ptr()) };
64
65 Ok(())
66 }
67
68 pub fn is_empty(&self) -> bool {
69 self.queue.is_empty()
70 }
71}
72
73unsafe extern "C" fn get_host_defined_data(
74 _: *const c_void, _: *mut JSContext, _: MutableHandle<*mut JSObject>,
75) -> bool {
76 true
77}
78
79unsafe extern "C" fn enqueue_promise_job(
80 _: *const c_void, cx: *mut JSContext, _: Handle<*mut JSObject>, job: Handle<*mut JSObject>,
81 _: Handle<*mut JSObject>, _: Handle<*mut JSObject>,
82) -> bool {
83 let cx = unsafe { &Context::new_unchecked(cx) };
84 let event_loop = unsafe { &mut cx.get_private().event_loop };
85 let microtasks = event_loop.microtasks.as_mut().unwrap();
86 if !job.is_null() {
87 microtasks.enqueue(cx, Microtask::Promise(job.get()));
88 }
89 true
90}
91
92unsafe extern "C" fn run_jobs(_: *const c_void, cx: *mut JSContext) {
93 let cx = unsafe { &Context::new_unchecked(cx) };
94 let event_loop = unsafe { &mut cx.get_private().event_loop };
95 let microtasks = event_loop.microtasks.as_mut().unwrap();
96 let _ = microtasks.run_jobs(cx);
97}
98
99unsafe extern "C" fn empty(extra: *const c_void) -> bool {
100 let queue = unsafe { &*extra.cast::<MicrotaskQueue>() };
101 queue.queue.is_empty()
102}
103
104pub(crate) static JOB_QUEUE_TRAPS: JobQueueTraps = JobQueueTraps {
105 getHostDefinedData: Some(get_host_defined_data),
106 enqueuePromiseJob: Some(enqueue_promise_job),
107 runJobs: Some(run_jobs),
108 empty: Some(empty),
109 pushNewInterruptQueue: None,
110 popInterruptQueue: None,
111 dropInterruptQueues: None,
112};