script/runtime/
job_queue.rs1use std::ffi::c_void;
10use std::ptr::NonNull;
11
12use js::context::JSContext;
13use js::glue::{CreateJobQueue, DeleteJobQueue, JobQueueTraps, RustJobQueue};
14use js::jsapi::{
15 GetExecutionGlobalFromJSMicroTask, GetPromiseUserInputEventHandlingState, IsJSMicroTask,
16 JSContext as RawJSContext, JSTracer, MaybeGetPromiseFromJSMicroTask, MutableHandleObject,
17 PromiseUserInputEventHandlingState, ToMaybeWrappedJSMicroTask,
18};
19use js::jsval::{JSVal, PrivateValue};
20use js::panic::wrap_panic;
21use js::realm::AutoRealm;
22use js::rust::wrappers2::{
23 EnqueueMicroTask, GetJobQueue, HasAnyMicroTasks, JS_DequeueNextMicroTask, JobQueueIsEmpty,
24 JobQueueMayNotBeEmpty, MaybeGetHostDefinedDataFromJSMicroTask, RunJSMicroTask, SetJobQueue,
25};
26use malloc_size_of::MallocSizeOf;
27use script_bindings::callback::TracedCallback;
28use script_bindings::reflector::DomObject as _;
29use script_bindings::root::Dom;
30use script_bindings::settings_stack::{run_a_callback, run_a_script};
31
32use crate::dom::bindings::callback::ExceptionHandling;
33use crate::dom::bindings::codegen::Bindings::VoidFunctionBinding::VoidFunction;
34use crate::dom::bindings::root::DomRoot;
35use crate::dom::globalscope::GlobalScope;
36use crate::event_loop::script_thread::ScriptThread;
37use crate::realms::enter_auto_realm;
38use crate::runtime::script_runtime::notify_about_rejected_promises;
39use crate::{DomTypeHolder, JSTraceable};
40
41pub(crate) static JOB_QUEUE_TRAPS: JobQueueTraps = JobQueueTraps {
42 getHostDefinedData: Some(get_host_defined_data),
43 getHostDefinedGlobal: Some(get_host_defined_global),
44 runJobs: Some(run_jobs),
45 traceNonGCThingMicroTask: Some(trace_non_gc_things_micro_task),
46};
47
48pub(crate) struct JobQueue(*mut RustJobQueue);
49
50#[expect(unsafe_code)]
51unsafe impl JSTraceable for JobQueue {
52 unsafe fn trace(&self, _trc: *mut JSTracer) {
53 }
55}
56
57impl MallocSizeOf for JobQueue {
58 fn size_of(&self, _ops: &mut malloc_size_of::MallocSizeOfOps) -> usize {
59 0
61 }
62}
63
64impl JobQueue {
65 #[expect(unsafe_code)]
66 pub(crate) fn new() -> Self {
67 JobQueue(unsafe { CreateJobQueue(&JOB_QUEUE_TRAPS) })
68 }
69
70 #[expect(unsafe_code)]
71 pub(crate) fn set_on_context(&self, cx: &JSContext) {
72 unsafe { SetJobQueue(cx, self.0 as *mut _) };
73 }
74}
75
76impl Drop for JobQueue {
77 #[expect(unsafe_code)]
78 fn drop(&mut self) {
79 unsafe {
80 DeleteJobQueue(self.0);
81 }
82 }
83}
84
85#[expect(unsafe_code)]
87unsafe extern "C" fn get_host_defined_data(
88 cx: *mut RawJSContext,
89 incumbent_global: MutableHandleObject,
90 data: MutableHandleObject,
91) -> bool {
92 incumbent_global.set(std::ptr::null_mut());
93 data.set(std::ptr::null_mut());
94 if !unsafe { get_host_defined_global(cx, incumbent_global) } {
95 return false;
96 }
97
98 if incumbent_global.is_null() {
99 return true;
100 }
101
102 true
105}
106
107#[allow(unsafe_code)]
108unsafe extern "C" fn get_host_defined_global(
110 _cx: *mut RawJSContext,
111 out: MutableHandleObject,
112) -> bool {
113 wrap_panic(&mut || {
114 let Some(incumbent_global) = GlobalScope::incumbent() else {
115 return;
116 };
117
118 out.set(incumbent_global.reflector().get_jsobject().get());
119 });
120
121 true
122}
123
124#[expect(unsafe_code)]
125unsafe extern "C" fn run_jobs(cx: *mut RawJSContext) {
126 let mut cx = unsafe {
127 JSContext::from_ptr(NonNull::new(cx).expect("JSContext should not be null in SM hook"))
129 };
130 wrap_panic(&mut || {
131 job_queue_microtask_checkpoint(&mut cx, vec![]);
134 });
135}
136
137#[derive(JSTraceable, MallocSizeOf)]
138pub struct NotifyMutationObserversMicrotask;
139
140impl NotifyMutationObserversMicrotask {
141 pub(crate) fn new() -> Self {
142 Self
143 }
144}
145
146impl MicrotaskRunnable for NotifyMutationObserversMicrotask {
147 fn handler(&self, cx: &mut JSContext) {
148 ScriptThread::mutation_observers().notify_mutation_observers(cx);
149 }
150}
151
152#[derive(JSTraceable, MallocSizeOf)]
153pub struct CustomElementReactionMicrotask;
154
155impl CustomElementReactionMicrotask {
156 pub(crate) fn new() -> Self {
157 Self
158 }
159}
160
161impl MicrotaskRunnable for CustomElementReactionMicrotask {
162 fn handler(&self, cx: &mut JSContext) {
163 ScriptThread::invoke_backup_element_queue(cx);
164 }
165}
166
167pub(crate) trait MicrotaskRunnable: JSTraceable + MallocSizeOf {
168 fn handler(&self, _cx: &mut JSContext) {}
170}
171
172#[derive(JSTraceable, MallocSizeOf)]
174#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
175pub(crate) struct UserMicrotask {
176 pub(crate) callback: TracedCallback<VoidFunction>,
177 pub(crate) global: Dom<GlobalScope>,
178}
179
180impl MicrotaskRunnable for UserMicrotask {
181 fn handler(&self, cx: &mut JSContext) {
182 let mut realm = enter_auto_realm(cx, &*self.global);
183 let cx = &mut realm;
184 let _ = self
185 .callback
186 .Call_(cx, &*self.global, ExceptionHandling::Report);
187 }
188}
189
190fn microtask_from_jsval(val: JSVal) -> *mut Box<dyn MicrotaskRunnable> {
191 val.to_private() as *const Box<dyn MicrotaskRunnable> as *mut Box<dyn MicrotaskRunnable>
192}
193
194#[expect(unsafe_code)]
197pub(crate) fn enqueue(cx: &JSContext, task: Box<dyn MicrotaskRunnable>) {
198 let task = Box::new(task);
199 let raw = Box::into_raw(task);
200 unsafe { JobQueueMayNotBeEmpty(cx) };
201 assert!(unsafe { EnqueueMicroTask(cx, &PrivateValue(raw as *const c_void)) });
202}
203
204#[expect(unsafe_code)]
207pub(crate) fn job_queue_microtask_checkpoint(
208 cx: &mut JSContext,
209 globalscopes: Vec<DomRoot<GlobalScope>>,
210) {
211 let job_queue: *mut RustJobQueue = unsafe { GetJobQueue(cx) } as _;
212 if unsafe { (*job_queue).draining } {
214 return;
215 }
216
217 unsafe {
219 (*job_queue).draining = true;
220 }
221
222 debug!("Now performing a microtask checkpoint");
223
224 rooted!(&in(cx) let mut generic_task: js::jsapi::GenericMicroTask);
225 rooted!(&in(cx) let mut js_micro_task: *mut js::jsapi::JSMicroTask);
226 rooted!(&in(cx) let mut execution_global: *mut js::jsapi::JSObject);
227 rooted!(&in(cx) let mut incumbent_global: *mut js::jsapi::JSObject);
228 rooted!(&in(cx) let mut data: *mut js::jsapi::JSObject);
229
230 while unsafe { HasAnyMicroTasks(cx) } {
235 unsafe { JS_DequeueNextMicroTask(cx, generic_task.handle_mut()) };
236
237 if !unsafe { HasAnyMicroTasks(cx) } {
240 unsafe { JobQueueIsEmpty(cx) };
241 }
242
243 if !unsafe { IsJSMicroTask(generic_task.as_ptr()) } {
245 rooted!(&in(cx) let task = unsafe {
246 Box::from_raw(
247 microtask_from_jsval(*generic_task),
248 )
249 });
250 task.handler(cx);
251 continue;
252 }
253
254 js_micro_task.set(unsafe { ToMaybeWrappedJSMicroTask(generic_task.as_ptr()) });
255 execution_global.set(unsafe { GetExecutionGlobalFromJSMicroTask(js_micro_task.get()) });
256 if execution_global.get().is_null() {
257 continue;
258 }
259 if !unsafe {
260 MaybeGetHostDefinedDataFromJSMicroTask(
261 js_micro_task.get(),
262 incumbent_global.handle_mut(),
263 data.handle_mut(),
264 )
265 } {
266 continue;
267 }
268
269 let interaction = if let Some(promise) =
270 NonNull::new(unsafe { MaybeGetPromiseFromJSMicroTask(js_micro_task.get()) })
271 {
272 unsafe { GetPromiseUserInputEventHandlingState(promise.as_ptr()) }
273 } else {
274 PromiseUserInputEventHandlingState::DontCare
275 };
276 let _maybe_user_interacting_guard =
277 if interaction == PromiseUserInputEventHandlingState::HadUserInteractionAtCreation {
278 Some(ScriptThread::user_interacting_guard())
279 } else {
280 None
281 };
282 let global_scope = unsafe { GlobalScope::from_object(execution_global.get()) };
283 run_a_script::<DomTypeHolder, _, _>(cx, &global_scope, |cx| {
284 let mut r = || {
285 let mut realm = AutoRealm::new_from_handle(cx, execution_global.handle());
286 let _ = unsafe { RunJSMicroTask(&mut realm, js_micro_task.handle()) };
287 };
288 if incumbent_global.get().is_null() {
289 r();
290 } else {
291 let global_scope = unsafe { GlobalScope::from_object(incumbent_global.get()) };
292 run_a_callback::<DomTypeHolder, _>(&global_scope, r);
293 }
294 });
295 }
296
297 for global in globalscopes.clone().into_iter() {
301 notify_about_rejected_promises(cx, &global);
302 }
303
304 for global in globalscopes.iter() {
310 if let Some(factory) = global.indexeddb_factory() {
311 let _ = factory.cleanup_indexeddb_transactions(cx);
312 }
313 }
314
315 unsafe {
319 (*job_queue).draining = false;
320 }
321 }
323
324#[expect(unsafe_code)]
325pub(crate) fn job_queue_clear(cx: &JSContext) {
326 rooted!(&in(cx) let mut generic_task: js::jsapi::GenericMicroTask);
327 while unsafe { HasAnyMicroTasks(cx) } {
328 unsafe { JS_DequeueNextMicroTask(cx, generic_task.handle_mut()) };
329 if !unsafe { IsJSMicroTask(generic_task.as_ptr()) } {
330 let task = unsafe { Box::from_raw(microtask_from_jsval(*generic_task)) };
331 drop(task);
332 }
333 }
334}
335
336#[expect(unsafe_code)]
337unsafe extern "C" fn trace_non_gc_things_micro_task(trc: *mut JSTracer, val: *mut JSVal) {
338 wrap_panic(&mut || {
339 let task = microtask_from_jsval(unsafe { *val });
340 unsafe { (**task).trace(trc) };
341 })
342}