1use 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#[derive(Default, JSTraceable, MallocSizeOf)]
32pub(crate) struct MicrotaskQueue {
33 microtask_queue: DomRefCell<Vec<Microtask>>,
35 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#[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#[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 #[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 #[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 self.performing_a_microtask_checkpoint.set(true);
102
103 debug!("Now performing a microtask checkpoint");
104
105 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 for global in globalscopes.into_iter() {
159 notify_about_rejected_promises(&global);
160 }
161
162 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}