1use std::cell::Cell;
10use std::mem;
11use std::rc::Rc;
12
13use js::context::JSContext;
14use js::rust::wrappers2::JobQueueMayNotBeEmpty;
15use malloc_size_of::MallocSizeOf;
16use script_bindings::cell::DomRefCell;
17use script_bindings::root::Dom;
18
19use crate::JSTraceable;
20use crate::dom::bindings::callback::ExceptionHandling;
21use crate::dom::bindings::codegen::Bindings::PromiseBinding::PromiseJobCallback;
22use crate::dom::bindings::codegen::Bindings::VoidFunctionBinding::VoidFunction;
23use crate::dom::bindings::root::DomRoot;
24use crate::dom::globalscope::GlobalScope;
25use crate::event_loop::script_thread::ScriptThread;
26use crate::realms::enter_auto_realm;
27use crate::script_runtime::notify_about_rejected_promises;
28
29#[derive(Default, JSTraceable, MallocSizeOf)]
31pub(crate) struct MicrotaskQueue {
32 microtask_queue: DomRefCell<Vec<Box<dyn MicrotaskRunnable>>>,
34 performing_a_microtask_checkpoint: Cell<bool>,
36}
37
38#[derive(JSTraceable, MallocSizeOf)]
39pub struct NotifyMutationObserversMicrotask;
40
41impl NotifyMutationObserversMicrotask {
42 pub(crate) fn new() -> Self {
43 Self
44 }
45}
46
47impl MicrotaskRunnable for NotifyMutationObserversMicrotask {
48 fn handler(&self, cx: &mut JSContext) {
49 ScriptThread::mutation_observers().notify_mutation_observers(cx);
50 }
51}
52
53#[derive(JSTraceable, MallocSizeOf)]
54pub struct CustomElementReactionMicrotask;
55
56impl CustomElementReactionMicrotask {
57 pub(crate) fn new() -> Self {
58 Self
59 }
60}
61
62impl MicrotaskRunnable for CustomElementReactionMicrotask {
63 fn handler(&self, cx: &mut JSContext) {
64 ScriptThread::invoke_backup_element_queue(cx);
65 }
66}
67
68pub(crate) trait MicrotaskRunnable: JSTraceable + MallocSizeOf {
69 fn handler(&self, _cx: &mut JSContext) {}
71}
72
73#[derive(JSTraceable, MallocSizeOf)]
75#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
76pub(crate) struct EnqueuedPromiseCallback {
77 #[conditional_malloc_size_of]
78 pub(crate) callback: Rc<PromiseJobCallback>,
79 pub(crate) global: Dom<GlobalScope>,
80 pub(crate) is_user_interacting: bool,
81}
82
83impl MicrotaskRunnable for EnqueuedPromiseCallback {
84 fn handler(&self, cx: &mut JSContext) {
85 let _maybe_user_interacting_guard = if self.is_user_interacting {
86 Some(ScriptThread::user_interacting_guard())
87 } else {
88 None
89 };
90 let mut realm = enter_auto_realm(cx, &*self.global);
91 let cx = &mut realm;
92 let _ = self
93 .callback
94 .Call_(cx, &*self.global, ExceptionHandling::Report);
95 }
96}
97
98#[derive(JSTraceable, MallocSizeOf)]
101#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
102pub(crate) struct UserMicrotask {
103 #[conditional_malloc_size_of]
104 pub(crate) callback: Rc<VoidFunction>,
105 pub(crate) global: Dom<GlobalScope>,
106}
107
108impl MicrotaskRunnable for UserMicrotask {
109 fn handler(&self, cx: &mut JSContext) {
110 let mut realm = enter_auto_realm(cx, &*self.global);
111 let cx = &mut realm;
112 let _ = self
113 .callback
114 .Call_(cx, &*self.global, ExceptionHandling::Report);
115 }
116}
117
118impl MicrotaskQueue {
119 #[expect(unsafe_code)]
122 pub(crate) fn enqueue(&self, cx: &JSContext, task: Box<dyn MicrotaskRunnable>) {
123 self.microtask_queue.borrow_mut().push(task);
124 unsafe { JobQueueMayNotBeEmpty(cx) };
125 }
126
127 #[expect(unsafe_code)]
130 pub(crate) fn checkpoint(&self, cx: &mut JSContext, globalscopes: Vec<DomRoot<GlobalScope>>) {
131 if self.performing_a_microtask_checkpoint.get() {
133 return;
134 }
135
136 self.performing_a_microtask_checkpoint.set(true);
138
139 debug!("Now performing a microtask checkpoint");
140
141 while !self.microtask_queue.borrow().is_empty() {
143 rooted_vec!(let mut pending_queue);
144 mem::swap(&mut *pending_queue, &mut *self.microtask_queue.borrow_mut());
145
146 for (idx, job) in pending_queue.iter().enumerate() {
147 if idx == pending_queue.len() - 1 && self.microtask_queue.borrow().is_empty() {
148 unsafe { js::rust::wrappers2::JobQueueIsEmpty(cx) };
149 }
150
151 job.handler(cx);
152 }
153 }
154
155 for global in globalscopes.clone().into_iter() {
159 notify_about_rejected_promises(cx, &global);
160 }
161
162 for global in globalscopes.iter() {
168 if let Some(factory) = global.indexeddb_factory() {
169 let _ = factory.cleanup_indexeddb_transactions(cx);
170 }
171 }
172
173 self.performing_a_microtask_checkpoint.set(false);
177 }
179
180 pub(crate) fn empty(&self) -> bool {
181 self.microtask_queue.borrow().is_empty()
182 }
183
184 pub(crate) fn clear(&self) {
185 self.microtask_queue.borrow_mut().clear();
186 }
187}