1use std::cell::Cell;
10use std::mem;
11use std::rc::Rc;
12
13use base::id::PipelineId;
14use js::jsapi::JobQueueMayNotBeEmpty;
15use js::realm::AutoRealm;
16
17use crate::dom::bindings::callback::ExceptionHandling;
18use crate::dom::bindings::cell::DomRefCell;
19use crate::dom::bindings::codegen::Bindings::PromiseBinding::PromiseJobCallback;
20use crate::dom::bindings::codegen::Bindings::VoidFunctionBinding::VoidFunction;
21use crate::dom::bindings::root::DomRoot;
22use crate::dom::globalscope::GlobalScope;
23use crate::dom::html::htmlimageelement::ImageElementMicrotask;
24use crate::dom::html::htmlmediaelement::MediaElementMicrotask;
25use crate::dom::promise::WaitForAllSuccessStepsMicrotask;
26use crate::dom::stream::byteteereadintorequest::ByteTeeReadIntoRequestMicrotask;
27use crate::dom::stream::byteteereadrequest::ByteTeeReadRequestMicrotask;
28use crate::dom::stream::defaultteereadrequest::DefaultTeeReadRequestMicrotask;
29use crate::realms::enter_auto_realm;
30use crate::script_runtime::{CanGc, JSContext, notify_about_rejected_promises};
31use crate::script_thread::ScriptThread;
32
33#[derive(Default, JSTraceable, MallocSizeOf)]
35pub(crate) struct MicrotaskQueue {
36 microtask_queue: DomRefCell<Vec<Microtask>>,
38 performing_a_microtask_checkpoint: Cell<bool>,
40}
41
42#[derive(JSTraceable, MallocSizeOf)]
43pub(crate) enum Microtask {
44 Promise(EnqueuedPromiseCallback),
45 User(UserMicrotask),
46 MediaElement(MediaElementMicrotask),
47 ImageElement(ImageElementMicrotask),
48 ReadableStreamTeeReadRequest(DefaultTeeReadRequestMicrotask),
49 WaitForAllSuccessSteps(WaitForAllSuccessStepsMicrotask),
50 ReadableStreamByteTeeReadRequest(ByteTeeReadRequestMicrotask),
51 ReadableStreamByteTeeReadIntoRequest(ByteTeeReadIntoRequestMicrotask),
52 CustomElementReaction,
53 NotifyMutationObservers,
54}
55
56pub(crate) trait MicrotaskRunnable {
57 fn handler(&self, _cx: &mut js::context::JSContext) {}
58 fn enter_realm<'cx>(&self, cx: &'cx mut js::context::JSContext) -> AutoRealm<'cx>;
59}
60
61#[derive(JSTraceable, MallocSizeOf)]
63pub(crate) struct EnqueuedPromiseCallback {
64 #[conditional_malloc_size_of]
65 pub(crate) callback: Rc<PromiseJobCallback>,
66 #[no_trace]
67 pub(crate) pipeline: PipelineId,
68 pub(crate) is_user_interacting: bool,
69}
70
71#[derive(JSTraceable, MallocSizeOf)]
74pub(crate) struct UserMicrotask {
75 #[conditional_malloc_size_of]
76 pub(crate) callback: Rc<VoidFunction>,
77 #[no_trace]
78 pub(crate) pipeline: PipelineId,
79}
80
81impl MicrotaskQueue {
82 #[expect(unsafe_code)]
85 pub(crate) fn enqueue(&self, job: Microtask, cx: JSContext) {
86 self.microtask_queue.borrow_mut().push(job);
87 unsafe { JobQueueMayNotBeEmpty(*cx) };
88 }
89
90 #[expect(unsafe_code)]
93 pub(crate) fn checkpoint<F>(
94 &self,
95 cx: &mut js::context::JSContext,
96 target_provider: F,
97 globalscopes: Vec<DomRoot<GlobalScope>>,
98 ) where
99 F: Fn(PipelineId) -> Option<DomRoot<GlobalScope>>,
100 {
101 if self.performing_a_microtask_checkpoint.get() {
103 return;
104 }
105
106 self.performing_a_microtask_checkpoint.set(true);
108
109 debug!("Now performing a microtask checkpoint");
110
111 while !self.microtask_queue.borrow().is_empty() {
113 rooted_vec!(let mut pending_queue);
114 mem::swap(&mut *pending_queue, &mut *self.microtask_queue.borrow_mut());
115
116 for (idx, job) in pending_queue.iter().enumerate() {
117 if idx == pending_queue.len() - 1 && self.microtask_queue.borrow().is_empty() {
118 unsafe { js::rust::wrappers2::JobQueueIsEmpty(cx) };
119 }
120
121 match *job {
122 Microtask::Promise(ref job) => {
123 if let Some(target) = target_provider(job.pipeline) {
124 let _guard = ScriptThread::user_interacting_guard();
125 let mut realm = enter_auto_realm(cx, &*target);
126 let cx = &mut realm;
127 let _ = job.callback.Call_(
128 &*target,
129 ExceptionHandling::Report,
130 CanGc::from_cx(cx),
131 );
132 }
133 },
134 Microtask::User(ref job) => {
135 if let Some(target) = target_provider(job.pipeline) {
136 let mut realm = enter_auto_realm(cx, &*target);
137 let cx = &mut realm;
138 let _ = job.callback.Call_(
139 &*target,
140 ExceptionHandling::Report,
141 CanGc::from_cx(cx),
142 );
143 }
144 },
145 Microtask::MediaElement(ref task) => {
146 let mut realm = task.enter_realm(cx);
147 let cx = &mut realm;
148 task.handler(cx);
149 },
150 Microtask::ImageElement(ref task) => {
151 let mut realm = task.enter_realm(cx);
152 let cx = &mut realm;
153 task.handler(cx);
154 },
155 Microtask::ReadableStreamTeeReadRequest(ref task) => {
156 let mut realm = task.enter_realm(cx);
157 let cx = &mut realm;
158 task.handler(cx);
159 },
160 Microtask::WaitForAllSuccessSteps(ref task) => {
161 let mut realm = task.enter_realm(cx);
162 let cx = &mut realm;
163 task.handler(cx);
164 },
165 Microtask::CustomElementReaction => {
166 ScriptThread::invoke_backup_element_queue(CanGc::from_cx(cx));
167 },
168 Microtask::NotifyMutationObservers => {
169 ScriptThread::mutation_observers()
170 .notify_mutation_observers(CanGc::from_cx(cx));
171 },
172 Microtask::ReadableStreamByteTeeReadRequest(ref task) => {
173 task.microtask_chunk_steps(cx)
174 },
175 Microtask::ReadableStreamByteTeeReadIntoRequest(ref task) => {
176 task.microtask_chunk_steps(cx)
177 },
178 }
179 }
180 }
181
182 for global in globalscopes.clone().into_iter() {
186 notify_about_rejected_promises(&global);
187 }
188
189 for global in globalscopes.iter() {
195 let _ = global.get_indexeddb().cleanup_indexeddb_transactions();
196 }
197
198 self.performing_a_microtask_checkpoint.set(false);
202 }
204
205 pub(crate) fn empty(&self) -> bool {
206 self.microtask_queue.borrow().is_empty()
207 }
208
209 pub(crate) fn clear(&self) {
210 self.microtask_queue.borrow_mut().clear();
211 }
212}