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 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/// A collection of microtasks in FIFO order.
33#[derive(Default, JSTraceable, MallocSizeOf)]
34pub(crate) struct MicrotaskQueue {
35    /// The list of enqueued microtasks that will be invoked at the next microtask checkpoint.
36    microtask_queue: DomRefCell<Vec<Microtask>>,
37    /// <https://html.spec.whatwg.org/multipage/#performing-a-microtask-checkpoint>
38    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/// A promise callback scheduled to run during the next microtask checkpoint (#4283).
61#[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/// A microtask that comes from a queueMicrotask() Javascript call,
71/// identical to EnqueuedPromiseCallback once it's on the queue
72#[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    /// Add a new microtask to this queue. It will be invoked as part of the next
82    /// microtask checkpoint.
83    #[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    /// <https://html.spec.whatwg.org/multipage/#perform-a-microtask-checkpoint>
90    /// Perform a microtask checkpoint, executing all queued microtasks until the queue is empty.
91    #[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        // Step 1. If the event loop's performing a microtask checkpoint is true, then return.
102        if self.performing_a_microtask_checkpoint.get() {
103            return;
104        }
105
106        // Step 2. Set the event loop's performing a microtask checkpoint to true.
107        self.performing_a_microtask_checkpoint.set(true);
108
109        debug!("Now performing a microtask checkpoint");
110
111        // Step 3. While the event loop's microtask queue is not empty:
112        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        // Step 4. For each environment settings object settingsObject whose responsible
172        // event loop is this event loop, notify about rejected promises given
173        // settingsObject's global object.
174        for global in globalscopes.into_iter() {
175            notify_about_rejected_promises(&global);
176        }
177
178        // TODO: Step 5. Cleanup Indexed Database transactions.
179
180        // TODO: Step 6. Perform ClearKeptObjects().
181
182        // Step 7. Set the event loop's performing a microtask checkpoint to false.
183        self.performing_a_microtask_checkpoint.set(false);
184        // TODO: Step 8. Record timing info for microtask checkpoint.
185    }
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}