1use std::cell::Cell;
10use std::mem;
11use std::rc::Rc;
12
13use base::id::PipelineId;
14use js::jsapi::{JSAutoRealm, JobQueueIsEmpty, JobQueueMayNotBeEmpty};
15
16use crate::dom::bindings::callback::ExceptionHandling;
17use crate::dom::bindings::cell::DomRefCell;
18use crate::dom::bindings::codegen::Bindings::PromiseBinding::PromiseJobCallback;
19use crate::dom::bindings::codegen::Bindings::VoidFunctionBinding::VoidFunction;
20use crate::dom::bindings::root::DomRoot;
21use crate::dom::byteteereadintorequest::ByteTeeReadIntoRequestMicrotask;
22use crate::dom::byteteereadrequest::ByteTeeReadRequestMicrotask;
23use crate::dom::defaultteereadrequest::DefaultTeeReadRequestMicrotask;
24use crate::dom::globalscope::GlobalScope;
25use crate::dom::html::htmlimageelement::ImageElementMicrotask;
26use crate::dom::html::htmlmediaelement::MediaElementMicrotask;
27use crate::dom::promise::WaitForAllSuccessStepsMicrotask;
28use crate::realms::enter_realm;
29use crate::script_runtime::{CanGc, JSContext, notify_about_rejected_promises};
30use crate::script_thread::ScriptThread;
31
32#[derive(Default, JSTraceable, MallocSizeOf)]
34pub(crate) struct MicrotaskQueue {
35 microtask_queue: DomRefCell<Vec<Microtask>>,
37 performing_a_microtask_checkpoint: Cell<bool>,
39}
40
41#[derive(JSTraceable, MallocSizeOf)]
42pub(crate) enum Microtask {
43 Promise(EnqueuedPromiseCallback),
44 User(UserMicrotask),
45 MediaElement(MediaElementMicrotask),
46 ImageElement(ImageElementMicrotask),
47 ReadableStreamTeeReadRequest(DefaultTeeReadRequestMicrotask),
48 WaitForAllSuccessSteps(WaitForAllSuccessStepsMicrotask),
49 ReadableStreamByteTeeReadRequest(ByteTeeReadRequestMicrotask),
50 ReadableStreamByteTeeReadIntoRequest(ByteTeeReadIntoRequestMicrotask),
51 CustomElementReaction,
52 NotifyMutationObservers,
53}
54
55pub(crate) trait MicrotaskRunnable {
56 fn handler(&self, _can_gc: CanGc) {}
57 fn enter_realm(&self) -> JSAutoRealm;
58}
59
60#[derive(JSTraceable, MallocSizeOf)]
62pub(crate) struct EnqueuedPromiseCallback {
63 #[conditional_malloc_size_of]
64 pub(crate) callback: Rc<PromiseJobCallback>,
65 #[no_trace]
66 pub(crate) pipeline: PipelineId,
67 pub(crate) is_user_interacting: bool,
68}
69
70#[derive(JSTraceable, MallocSizeOf)]
73pub(crate) struct UserMicrotask {
74 #[conditional_malloc_size_of]
75 pub(crate) callback: Rc<VoidFunction>,
76 #[no_trace]
77 pub(crate) pipeline: PipelineId,
78}
79
80impl MicrotaskQueue {
81 #[expect(unsafe_code)]
84 pub(crate) fn enqueue(&self, job: Microtask, cx: JSContext) {
85 self.microtask_queue.borrow_mut().push(job);
86 unsafe { JobQueueMayNotBeEmpty(*cx) };
87 }
88
89 #[expect(unsafe_code)]
92 pub(crate) fn checkpoint<F>(
93 &self,
94 cx: JSContext,
95 target_provider: F,
96 globalscopes: Vec<DomRoot<GlobalScope>>,
97 can_gc: CanGc,
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 { 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 _realm = enter_realm(&*target);
126 let _ = job
127 .callback
128 .Call_(&*target, ExceptionHandling::Report, can_gc);
129 }
130 },
131 Microtask::User(ref job) => {
132 if let Some(target) = target_provider(job.pipeline) {
133 let _realm = enter_realm(&*target);
134 let _ = job
135 .callback
136 .Call_(&*target, ExceptionHandling::Report, can_gc);
137 }
138 },
139 Microtask::MediaElement(ref task) => {
140 let _realm = task.enter_realm();
141 task.handler(can_gc);
142 },
143 Microtask::ImageElement(ref task) => {
144 let _realm = task.enter_realm();
145 task.handler(can_gc);
146 },
147 Microtask::ReadableStreamTeeReadRequest(ref task) => {
148 let _realm = task.enter_realm();
149 task.handler(can_gc);
150 },
151 Microtask::WaitForAllSuccessSteps(ref task) => {
152 let _realm = task.enter_realm();
153 task.handler(can_gc);
154 },
155 Microtask::CustomElementReaction => {
156 ScriptThread::invoke_backup_element_queue(can_gc);
157 },
158 Microtask::NotifyMutationObservers => {
159 ScriptThread::mutation_observers().notify_mutation_observers(can_gc);
160 },
161 Microtask::ReadableStreamByteTeeReadRequest(ref task) => {
162 task.microtask_chunk_steps(can_gc)
163 },
164 Microtask::ReadableStreamByteTeeReadIntoRequest(ref task) => {
165 task.microtask_chunk_steps(can_gc)
166 },
167 }
168 }
169 }
170
171 for global in globalscopes.into_iter() {
175 notify_about_rejected_promises(&global);
176 }
177
178 self.performing_a_microtask_checkpoint.set(false);
184 }
186
187 pub(crate) fn empty(&self) -> bool {
188 self.microtask_queue.borrow().is_empty()
189 }
190
191 pub(crate) fn clear(&self) {
192 self.microtask_queue.borrow_mut().clear();
193 }
194}