Skip to main content

script/
microtask.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5//! Implementation of [microtasks](https://html.spec.whatwg.org/multipage/#microtask) and
6//! microtask queues. It is up to implementations of event loops to store a queue and
7//! perform checkpoints at appropriate times, as well as enqueue microtasks as required.
8
9use 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/// A collection of microtasks in FIFO order.
30#[derive(Default, JSTraceable, MallocSizeOf)]
31pub(crate) struct MicrotaskQueue {
32    /// The list of enqueued microtasks that will be invoked at the next microtask checkpoint.
33    microtask_queue: DomRefCell<Vec<Box<dyn MicrotaskRunnable>>>,
34    /// <https://html.spec.whatwg.org/multipage/#performing-a-microtask-checkpoint>
35    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    // must also take care of entering the realm
70    fn handler(&self, _cx: &mut JSContext) {}
71}
72
73/// A promise callback scheduled to run during the next microtask checkpoint (#4283).
74#[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/// A microtask that comes from a queueMicrotask() Javascript call,
99/// identical to EnqueuedPromiseCallback once it's on the queue
100#[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    /// Add a new microtask to this queue. It will be invoked as part of the next
120    /// microtask checkpoint.
121    #[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    /// <https://html.spec.whatwg.org/multipage/#perform-a-microtask-checkpoint>
128    /// Perform a microtask checkpoint, executing all queued microtasks until the queue is empty.
129    #[expect(unsafe_code)]
130    pub(crate) fn checkpoint(&self, cx: &mut JSContext, globalscopes: Vec<DomRoot<GlobalScope>>) {
131        // Step 1. If the event loop's performing a microtask checkpoint is true, then return.
132        if self.performing_a_microtask_checkpoint.get() {
133            return;
134        }
135
136        // Step 2. Set the event loop's performing a microtask checkpoint to true.
137        self.performing_a_microtask_checkpoint.set(true);
138
139        debug!("Now performing a microtask checkpoint");
140
141        // Step 3. While the event loop's microtask queue is not empty:
142        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        // Step 4. For each environment settings object settingsObject whose responsible
156        // event loop is this event loop, notify about rejected promises given
157        // settingsObject's global object.
158        for global in globalscopes.clone().into_iter() {
159            notify_about_rejected_promises(cx, &global);
160        }
161
162        // https://html.spec.whatwg.org/multipage/#perform-a-microtask-checkpoint
163        // Step 5. Cleanup Indexed Database transactions.
164        // https://w3c.github.io/IndexedDB/#cleanup-indexed-database-transactions
165        // “These steps are invoked by [HTML]. They ensure that transactions created by a script call
166        // to transaction() are deactivated once the task that invoked the script has completed.”
167        for global in globalscopes.iter() {
168            if let Some(factory) = global.indexeddb_factory() {
169                let _ = factory.cleanup_indexeddb_transactions(cx);
170            }
171        }
172
173        // TODO: Step 6. Perform ClearKeptObjects().
174
175        // Step 7. Set the event loop's performing a microtask checkpoint to false.
176        self.performing_a_microtask_checkpoint.set(false);
177        // TODO: Step 8. Record timing info for microtask checkpoint.
178    }
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}