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::defaultteereadrequest::DefaultTeeReadRequestMicrotask;
22use crate::dom::globalscope::GlobalScope;
23use crate::dom::html::htmlimageelement::ImageElementMicrotask;
24use crate::dom::html::htmlmediaelement::MediaElementMicrotask;
25use crate::dom::promise::WaitForAllSuccessStepsMicrotask;
26use crate::realms::enter_realm;
27use crate::script_runtime::{CanGc, JSContext, notify_about_rejected_promises};
28use crate::script_thread::ScriptThread;
29
30/// A collection of microtasks in FIFO order.
31#[derive(Default, JSTraceable, MallocSizeOf)]
32pub(crate) struct MicrotaskQueue {
33    /// The list of enqueued microtasks that will be invoked at the next microtask checkpoint.
34    microtask_queue: DomRefCell<Vec<Microtask>>,
35    /// <https://html.spec.whatwg.org/multipage/#performing-a-microtask-checkpoint>
36    performing_a_microtask_checkpoint: Cell<bool>,
37}
38
39#[derive(JSTraceable, MallocSizeOf)]
40pub(crate) enum Microtask {
41    Promise(EnqueuedPromiseCallback),
42    User(UserMicrotask),
43    MediaElement(MediaElementMicrotask),
44    ImageElement(ImageElementMicrotask),
45    ReadableStreamTeeReadRequest(DefaultTeeReadRequestMicrotask),
46    WaitForAllSuccessSteps(WaitForAllSuccessStepsMicrotask),
47    CustomElementReaction,
48    NotifyMutationObservers,
49}
50
51pub(crate) trait MicrotaskRunnable {
52    fn handler(&self, _can_gc: CanGc) {}
53    fn enter_realm(&self) -> JSAutoRealm;
54}
55
56/// A promise callback scheduled to run during the next microtask checkpoint (#4283).
57#[derive(JSTraceable, MallocSizeOf)]
58pub(crate) struct EnqueuedPromiseCallback {
59    #[conditional_malloc_size_of]
60    pub(crate) callback: Rc<PromiseJobCallback>,
61    #[no_trace]
62    pub(crate) pipeline: PipelineId,
63    pub(crate) is_user_interacting: bool,
64}
65
66/// A microtask that comes from a queueMicrotask() Javascript call,
67/// identical to EnqueuedPromiseCallback once it's on the queue
68#[derive(JSTraceable, MallocSizeOf)]
69pub(crate) struct UserMicrotask {
70    #[conditional_malloc_size_of]
71    pub(crate) callback: Rc<VoidFunction>,
72    #[no_trace]
73    pub(crate) pipeline: PipelineId,
74}
75
76impl MicrotaskQueue {
77    /// Add a new microtask to this queue. It will be invoked as part of the next
78    /// microtask checkpoint.
79    #[allow(unsafe_code)]
80    pub(crate) fn enqueue(&self, job: Microtask, cx: JSContext) {
81        self.microtask_queue.borrow_mut().push(job);
82        unsafe { JobQueueMayNotBeEmpty(*cx) };
83    }
84
85    /// <https://html.spec.whatwg.org/multipage/#perform-a-microtask-checkpoint>
86    /// Perform a microtask checkpoint, executing all queued microtasks until the queue is empty.
87    #[allow(unsafe_code)]
88    pub(crate) fn checkpoint<F>(
89        &self,
90        cx: JSContext,
91        target_provider: F,
92        globalscopes: Vec<DomRoot<GlobalScope>>,
93        can_gc: CanGc,
94    ) where
95        F: Fn(PipelineId) -> Option<DomRoot<GlobalScope>>,
96    {
97        // Step 1. If the event loop's performing a microtask checkpoint is true, then return.
98        if self.performing_a_microtask_checkpoint.get() {
99            return;
100        }
101
102        // Step 2. Set the event loop's performing a microtask checkpoint to true.
103        self.performing_a_microtask_checkpoint.set(true);
104
105        debug!("Now performing a microtask checkpoint");
106
107        // Step 3. While the event loop's microtask queue is not empty:
108        while !self.microtask_queue.borrow().is_empty() {
109            rooted_vec!(let mut pending_queue);
110            mem::swap(&mut *pending_queue, &mut *self.microtask_queue.borrow_mut());
111
112            for (idx, job) in pending_queue.iter().enumerate() {
113                if idx == pending_queue.len() - 1 && self.microtask_queue.borrow().is_empty() {
114                    unsafe { JobQueueIsEmpty(*cx) };
115                }
116
117                match *job {
118                    Microtask::Promise(ref job) => {
119                        if let Some(target) = target_provider(job.pipeline) {
120                            let _guard = ScriptThread::user_interacting_guard();
121                            let _realm = enter_realm(&*target);
122                            let _ = job
123                                .callback
124                                .Call_(&*target, ExceptionHandling::Report, can_gc);
125                        }
126                    },
127                    Microtask::User(ref job) => {
128                        if let Some(target) = target_provider(job.pipeline) {
129                            let _realm = enter_realm(&*target);
130                            let _ = job
131                                .callback
132                                .Call_(&*target, ExceptionHandling::Report, can_gc);
133                        }
134                    },
135                    Microtask::MediaElement(ref task) => {
136                        let _realm = task.enter_realm();
137                        task.handler(can_gc);
138                    },
139                    Microtask::ImageElement(ref task) => {
140                        let _realm = task.enter_realm();
141                        task.handler(can_gc);
142                    },
143                    Microtask::ReadableStreamTeeReadRequest(ref task) => {
144                        let _realm = task.enter_realm();
145                        task.handler(can_gc);
146                    },
147                    Microtask::WaitForAllSuccessSteps(ref task) => {
148                        let _realm = task.enter_realm();
149                        task.handler(can_gc);
150                    },
151                    Microtask::CustomElementReaction => {
152                        ScriptThread::invoke_backup_element_queue(can_gc);
153                    },
154                    Microtask::NotifyMutationObservers => {
155                        ScriptThread::mutation_observers().notify_mutation_observers(can_gc);
156                    },
157                }
158            }
159        }
160
161        // Step 4. For each environment settings object settingsObject whose responsible
162        // event loop is this event loop, notify about rejected promises given
163        // settingsObject's global object.
164        for global in globalscopes.into_iter() {
165            notify_about_rejected_promises(&global);
166        }
167
168        // TODO: Step 5. Cleanup Indexed Database transactions.
169
170        // TODO: Step 6. Perform ClearKeptObjects().
171
172        // Step 7. Set the event loop's performing a microtask checkpoint to false.
173        self.performing_a_microtask_checkpoint.set(false);
174        // TODO: Step 8. Record timing info for microtask checkpoint.
175    }
176
177    pub(crate) fn empty(&self) -> bool {
178        self.microtask_queue.borrow().is_empty()
179    }
180
181    pub(crate) fn clear(&self) {
182        self.microtask_queue.borrow_mut().clear();
183    }
184}