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::realms::enter_auto_realm;
26use crate::script_runtime::notify_about_rejected_promises;
27use crate::script_thread::ScriptThread;
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 _guard = ScriptThread::user_interacting_guard();
86 let mut realm = enter_auto_realm(cx, &*self.global);
87 let cx = &mut realm;
88 let _ = self
89 .callback
90 .Call_(cx, &*self.global, ExceptionHandling::Report);
91 }
92}
93
94#[derive(JSTraceable, MallocSizeOf)]
97#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
98pub(crate) struct UserMicrotask {
99 #[conditional_malloc_size_of]
100 pub(crate) callback: Rc<VoidFunction>,
101 pub(crate) global: Dom<GlobalScope>,
102}
103
104impl MicrotaskRunnable for UserMicrotask {
105 fn handler(&self, cx: &mut JSContext) {
106 let mut realm = enter_auto_realm(cx, &*self.global);
107 let cx = &mut realm;
108 let _ = self
109 .callback
110 .Call_(cx, &*self.global, ExceptionHandling::Report);
111 }
112}
113
114impl MicrotaskQueue {
115 #[expect(unsafe_code)]
118 pub(crate) fn enqueue(&self, cx: &JSContext, task: Box<dyn MicrotaskRunnable>) {
119 self.microtask_queue.borrow_mut().push(task);
120 unsafe { JobQueueMayNotBeEmpty(cx) };
121 }
122
123 #[expect(unsafe_code)]
126 pub(crate) fn checkpoint(&self, cx: &mut JSContext, globalscopes: Vec<DomRoot<GlobalScope>>) {
127 if self.performing_a_microtask_checkpoint.get() {
129 return;
130 }
131
132 self.performing_a_microtask_checkpoint.set(true);
134
135 debug!("Now performing a microtask checkpoint");
136
137 while !self.microtask_queue.borrow().is_empty() {
139 rooted_vec!(let mut pending_queue);
140 mem::swap(&mut *pending_queue, &mut *self.microtask_queue.borrow_mut());
141
142 for (idx, job) in pending_queue.iter().enumerate() {
143 if idx == pending_queue.len() - 1 && self.microtask_queue.borrow().is_empty() {
144 unsafe { js::rust::wrappers2::JobQueueIsEmpty(cx) };
145 }
146
147 job.handler(cx);
148 }
149 }
150
151 for global in globalscopes.clone().into_iter() {
155 notify_about_rejected_promises(cx, &global);
156 }
157
158 for global in globalscopes.iter() {
164 if let Some(factory) = global.indexeddb_factory() {
165 let _ = factory.cleanup_indexeddb_transactions(cx);
166 }
167 }
168
169 self.performing_a_microtask_checkpoint.set(false);
173 }
175
176 pub(crate) fn empty(&self) -> bool {
177 self.microtask_queue.borrow().is_empty()
178 }
179
180 pub(crate) fn clear(&self) {
181 self.microtask_queue.borrow_mut().clear();
182 }
183}