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::mutationobserver::MutationObserver;
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    CustomElementReaction,
47    NotifyMutationObservers,
48}
49
50pub(crate) trait MicrotaskRunnable {
51    fn handler(&self, _can_gc: CanGc) {}
52    fn enter_realm(&self) -> JSAutoRealm;
53}
54
55/// A promise callback scheduled to run during the next microtask checkpoint (#4283).
56#[derive(JSTraceable, MallocSizeOf)]
57pub(crate) struct EnqueuedPromiseCallback {
58    #[conditional_malloc_size_of]
59    pub(crate) callback: Rc<PromiseJobCallback>,
60    #[no_trace]
61    pub(crate) pipeline: PipelineId,
62    pub(crate) is_user_interacting: bool,
63}
64
65/// A microtask that comes from a queueMicrotask() Javascript call,
66/// identical to EnqueuedPromiseCallback once it's on the queue
67#[derive(JSTraceable, MallocSizeOf)]
68pub(crate) struct UserMicrotask {
69    #[conditional_malloc_size_of]
70    pub(crate) callback: Rc<VoidFunction>,
71    #[no_trace]
72    pub(crate) pipeline: PipelineId,
73}
74
75impl MicrotaskQueue {
76    /// Add a new microtask to this queue. It will be invoked as part of the next
77    /// microtask checkpoint.
78    #[allow(unsafe_code)]
79    pub(crate) fn enqueue(&self, job: Microtask, cx: JSContext) {
80        self.microtask_queue.borrow_mut().push(job);
81        unsafe { JobQueueMayNotBeEmpty(*cx) };
82    }
83
84    /// <https://html.spec.whatwg.org/multipage/#perform-a-microtask-checkpoint>
85    /// Perform a microtask checkpoint, executing all queued microtasks until the queue is empty.
86    #[allow(unsafe_code)]
87    pub(crate) fn checkpoint<F>(
88        &self,
89        cx: JSContext,
90        target_provider: F,
91        globalscopes: Vec<DomRoot<GlobalScope>>,
92        can_gc: CanGc,
93    ) where
94        F: Fn(PipelineId) -> Option<DomRoot<GlobalScope>>,
95    {
96        if self.performing_a_microtask_checkpoint.get() {
97            return;
98        }
99
100        // Step 1
101        self.performing_a_microtask_checkpoint.set(true);
102
103        debug!("Now performing a microtask checkpoint");
104
105        // Steps 2
106        while !self.microtask_queue.borrow().is_empty() {
107            rooted_vec!(let mut pending_queue);
108            mem::swap(&mut *pending_queue, &mut *self.microtask_queue.borrow_mut());
109
110            for (idx, job) in pending_queue.iter().enumerate() {
111                if idx == pending_queue.len() - 1 && self.microtask_queue.borrow().is_empty() {
112                    unsafe { JobQueueIsEmpty(*cx) };
113                }
114
115                match *job {
116                    Microtask::Promise(ref job) => {
117                        if let Some(target) = target_provider(job.pipeline) {
118                            let was_interacting = ScriptThread::is_user_interacting();
119                            ScriptThread::set_user_interacting(job.is_user_interacting);
120                            let _realm = enter_realm(&*target);
121                            let _ = job
122                                .callback
123                                .Call_(&*target, ExceptionHandling::Report, can_gc);
124                            ScriptThread::set_user_interacting(was_interacting);
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::CustomElementReaction => {
144                        ScriptThread::invoke_backup_element_queue(can_gc);
145                    },
146                    Microtask::NotifyMutationObservers => {
147                        MutationObserver::notify_mutation_observers(can_gc);
148                    },
149                    Microtask::ReadableStreamTeeReadRequest(ref task) => {
150                        let _realm = task.enter_realm();
151                        task.handler(can_gc);
152                    },
153                }
154            }
155        }
156
157        // Step 3
158        for global in globalscopes.into_iter() {
159            notify_about_rejected_promises(&global);
160        }
161
162        // TODO: Step 4 - Cleanup Indexed Database transactions.
163
164        // Step 5
165        self.performing_a_microtask_checkpoint.set(false);
166    }
167
168    pub(crate) fn empty(&self) -> bool {
169        self.microtask_queue.borrow().is_empty()
170    }
171
172    pub(crate) fn clear(&self) {
173        self.microtask_queue.borrow_mut().clear();
174    }
175}