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::promise::WaitForAllSuccessStepsMicrotask;
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 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#[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#[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 #[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 #[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 if self.performing_a_microtask_checkpoint.get() {
99 return;
100 }
101
102 self.performing_a_microtask_checkpoint.set(true);
104
105 debug!("Now performing a microtask checkpoint");
106
107 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 for global in globalscopes.into_iter() {
165 notify_about_rejected_promises(&global);
166 }
167
168 self.performing_a_microtask_checkpoint.set(false);
174 }
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}