runtime/event_loop/
mod.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::VecDeque;
8use std::ffi::c_void;
9use std::future::poll_fn;
10use std::task;
11use std::task::Poll;
12
13use ion::format::{Config, format_value};
14use ion::{Context, ErrorReport, Local, Promise};
15use mozjs::jsapi::{Handle, Heap, JSContext, JSObject, PromiseRejectionHandlingState};
16
17use crate::ContextExt as _;
18use crate::event_loop::future::FutureQueue;
19use crate::event_loop::macrotasks::MacrotaskQueue;
20use crate::event_loop::microtasks::MicrotaskQueue;
21
22pub(crate) mod future;
23pub(crate) mod macrotasks;
24pub(crate) mod microtasks;
25
26#[derive(Default)]
27pub struct EventLoop {
28	pub(crate) futures: Option<FutureQueue>,
29	pub(crate) microtasks: Option<MicrotaskQueue>,
30	pub(crate) macrotasks: Option<MacrotaskQueue>,
31	pub(crate) unhandled_rejections: VecDeque<Box<Heap<*mut JSObject>>>,
32}
33
34impl EventLoop {
35	pub async fn run_event_loop(&mut self, cx: &Context) -> Result<(), Option<ErrorReport>> {
36		let mut complete = false;
37		poll_fn(|wcx| self.poll_event_loop(cx, wcx, &mut complete)).await
38	}
39
40	fn poll_event_loop(
41		&mut self, cx: &Context, wcx: &mut task::Context, complete: &mut bool,
42	) -> Poll<Result<(), Option<ErrorReport>>> {
43		if let Some(futures) = &mut self.futures
44			&& !futures.is_empty()
45		{
46			futures.run_futures(cx, wcx)?;
47		}
48
49		if let Some(microtasks) = &mut self.microtasks
50			&& !microtasks.is_empty()
51		{
52			microtasks.run_jobs(cx)?;
53		}
54
55		if let Some(macrotasks) = &mut self.macrotasks
56			&& !macrotasks.is_empty()
57		{
58			macrotasks.run_job(cx)?;
59		}
60
61		while let Some(promise) = self.unhandled_rejections.pop_front() {
62			let promise = Promise::from(unsafe { Local::from_heap(&promise) }).unwrap();
63			let result = promise.result(cx);
64			eprintln!(
65				"Unhandled Promise Rejection: {}",
66				format_value(cx, Config::default(), &result)
67			);
68		}
69
70		let empty = self.is_empty();
71		if empty && *complete {
72			Poll::Ready(Ok(()))
73		} else {
74			wcx.waker().wake_by_ref();
75			*complete = empty;
76			Poll::Pending
77		}
78	}
79
80	fn is_empty(&self) -> bool {
81		self.microtasks.as_ref().is_none_or(MicrotaskQueue::is_empty)
82			&& self.futures.as_ref().is_none_or(FutureQueue::is_empty)
83			&& self.macrotasks.as_ref().is_none_or(MacrotaskQueue::is_empty)
84	}
85}
86
87pub(crate) unsafe extern "C" fn promise_rejection_tracker_callback(
88	cx: *mut JSContext, _: bool, promise: Handle<*mut JSObject>, state: PromiseRejectionHandlingState, _: *mut c_void,
89) {
90	let cx = unsafe { &Context::new_unchecked(cx) };
91	let promise = Promise::from(unsafe { Local::from_raw_handle(promise) }).unwrap();
92	let unhandled = unsafe { &mut cx.get_private().event_loop.unhandled_rejections };
93	match state {
94		PromiseRejectionHandlingState::Unhandled => unhandled.push_back(Heap::boxed(promise.get())),
95		PromiseRejectionHandlingState::Handled => {
96			let idx = unhandled.iter().position(|unhandled| unhandled.get() == promise.get());
97			if let Some(idx) = idx {
98				unhandled.swap_remove_back(idx);
99			}
100		}
101	}
102}