runtime/event_loop/
microtasks.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::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};