use std::cell::Cell;
use std::mem;
use std::rc::Rc;
use js::context::JSContext;
use js::rust::wrappers2::JobQueueMayNotBeEmpty;
use malloc_size_of::MallocSizeOf;
use script_bindings::cell::DomRefCell;
use script_bindings::root::Dom;
use crate::JSTraceable;
use crate::dom::bindings::callback::ExceptionHandling;
use crate::dom::bindings::codegen::Bindings::PromiseBinding::PromiseJobCallback;
use crate::dom::bindings::codegen::Bindings::VoidFunctionBinding::VoidFunction;
use crate::dom::bindings::root::DomRoot;
use crate::dom::globalscope::GlobalScope;
use crate::realms::enter_auto_realm;
use crate::script_runtime::notify_about_rejected_promises;
use crate::script_thread::ScriptThread;
#[derive(Default, JSTraceable, MallocSizeOf)]
pub(crate) struct MicrotaskQueue {
microtask_queue: DomRefCell<Vec<Box<dyn MicrotaskRunnable>>>,
performing_a_microtask_checkpoint: Cell<bool>,
}
#[derive(JSTraceable, MallocSizeOf)]
pub struct NotifyMutationObserversMicrotask;
impl NotifyMutationObserversMicrotask {
pub(crate) fn new() -> Self {
Self
}
}
impl MicrotaskRunnable for NotifyMutationObserversMicrotask {
fn handler(&self, cx: &mut JSContext) {
ScriptThread::mutation_observers().notify_mutation_observers(cx);
}
}
#[derive(JSTraceable, MallocSizeOf)]
pub struct CustomElementReactionMicrotask;
impl CustomElementReactionMicrotask {
pub(crate) fn new() -> Self {
Self
}
}
impl MicrotaskRunnable for CustomElementReactionMicrotask {
fn handler(&self, cx: &mut JSContext) {
ScriptThread::invoke_backup_element_queue(cx);
}
}
pub(crate) trait MicrotaskRunnable: JSTraceable + MallocSizeOf {
fn handler(&self, _cx: &mut JSContext) {}
}
#[derive(JSTraceable, MallocSizeOf)]
#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
pub(crate) struct EnqueuedPromiseCallback {
#[conditional_malloc_size_of]
pub(crate) callback: Rc<PromiseJobCallback>,
pub(crate) global: Dom<GlobalScope>,
pub(crate) is_user_interacting: bool,
}
impl MicrotaskRunnable for EnqueuedPromiseCallback {
fn handler(&self, cx: &mut JSContext) {
let _guard = ScriptThread::user_interacting_guard();
let mut realm = enter_auto_realm(cx, &*self.global);
let cx = &mut realm;
let _ = self
.callback
.Call_(cx, &*self.global, ExceptionHandling::Report);
}
}
#[derive(JSTraceable, MallocSizeOf)]
#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
pub(crate) struct UserMicrotask {
#[conditional_malloc_size_of]
pub(crate) callback: Rc<VoidFunction>,
pub(crate) global: Dom<GlobalScope>,
}
impl MicrotaskRunnable for UserMicrotask {
fn handler(&self, cx: &mut JSContext) {
let mut realm = enter_auto_realm(cx, &*self.global);
let cx = &mut realm;
let _ = self
.callback
.Call_(cx, &*self.global, ExceptionHandling::Report);
}
}
impl MicrotaskQueue {
#[expect(unsafe_code)]
pub(crate) fn enqueue(&self, cx: &JSContext, task: Box<dyn MicrotaskRunnable>) {
self.microtask_queue.borrow_mut().push(task);
unsafe { JobQueueMayNotBeEmpty(cx) };
}
#[expect(unsafe_code)]
pub(crate) fn checkpoint(&self, cx: &mut JSContext, globalscopes: Vec<DomRoot<GlobalScope>>) {
if self.performing_a_microtask_checkpoint.get() {
return;
}
self.performing_a_microtask_checkpoint.set(true);
debug!("Now performing a microtask checkpoint");
while !self.microtask_queue.borrow().is_empty() {
rooted_vec!(let mut pending_queue);
mem::swap(&mut *pending_queue, &mut *self.microtask_queue.borrow_mut());
for (idx, job) in pending_queue.iter().enumerate() {
if idx == pending_queue.len() - 1 && self.microtask_queue.borrow().is_empty() {
unsafe { js::rust::wrappers2::JobQueueIsEmpty(cx) };
}
job.handler(cx);
}
}
for global in globalscopes.clone().into_iter() {
notify_about_rejected_promises(cx, &global);
}
for global in globalscopes.iter() {
if let Some(factory) = global.indexeddb_factory() {
let _ = factory.cleanup_indexeddb_transactions(cx);
}
}
self.performing_a_microtask_checkpoint.set(false);
}
pub(crate) fn empty(&self) -> bool {
self.microtask_queue.borrow().is_empty()
}
pub(crate) fn clear(&self) {
self.microtask_queue.borrow_mut().clear();
}
}