1use std::future::Future;
8use std::pin::{Pin, pin};
9use std::sync::Arc;
10use std::sync::atomic::{AtomicBool, Ordering};
11use std::task::Poll;
12use std::time::Duration;
13use std::{ptr, task};
14
15use ion::class::Reflector;
16use ion::conversions::{FromValue, ToValue as _};
17use ion::function::{Enforce, Opt};
18use ion::{ClassDefinition as _, Context, Error, ErrorKind, Exception, Object, Result, ResultExc, Value, js_class};
19use mozjs::jsapi::{Heap, JSObject};
20use mozjs::jsval::JSVal;
21use tokio::sync::watch::{Receiver, Sender, channel};
22
23use crate::ContextExt as _;
24use crate::event_loop::macrotasks::{Macrotask, SignalMacrotask};
25
26#[derive(Clone, Debug, Default)]
27pub enum Signal {
28 #[default]
29 None,
30 Abort(JSVal),
31 Receiver(Receiver<Option<JSVal>>),
32 Timeout(Receiver<Option<JSVal>>, Arc<AtomicBool>),
33}
34
35impl Signal {
36 pub fn poll(&self) -> SignalFuture {
37 SignalFuture { inner: self.clone() }
38 }
39}
40
41pub struct SignalFuture {
42 inner: Signal,
43}
44
45impl Future for SignalFuture {
46 type Output = JSVal;
47
48 fn poll(mut self: Pin<&mut SignalFuture>, cx: &mut task::Context) -> Poll<JSVal> {
49 match &mut self.inner {
50 Signal::None => Poll::Pending,
51 Signal::Abort(abort) => Poll::Ready(*abort),
52 Signal::Receiver(receiver) | Signal::Timeout(receiver, _) => {
53 if let Some(abort) = *receiver.borrow() {
54 return Poll::Ready(abort);
55 }
56 let changed = { pin!(receiver.changed()).poll(cx) };
57 match changed {
58 Poll::Ready(_) => match *receiver.borrow() {
59 Some(abort) => Poll::Ready(abort),
60 None => Poll::Pending,
61 },
62 Poll::Pending => Poll::Pending,
63 }
64 }
65 }
66 }
67}
68
69impl Drop for SignalFuture {
70 fn drop(&mut self) {
71 if let Signal::Timeout(receiver, terminate) = &self.inner
72 && receiver.borrow().is_none()
73 {
74 terminate.store(true, Ordering::SeqCst);
75 }
76 }
77}
78
79#[js_class]
80pub struct AbortController {
81 reflector: Reflector,
82 signal: Box<Heap<*mut JSObject>>,
83 #[trace(no_trace)]
84 sender: Sender<Option<JSVal>>,
85}
86
87#[js_class]
88impl AbortController {
89 #[ion(constructor)]
90 pub fn constructor(cx: &Context) -> AbortController {
91 let (sender, receiver) = channel(None);
92 let signal = Heap::boxed(AbortSignal::new_object(
93 cx,
94 Box::new(AbortSignal {
95 reflector: Reflector::default(),
96 signal: Signal::Receiver(receiver),
97 }),
98 ));
99 AbortController {
100 reflector: Reflector::default(),
101 signal,
102 sender,
103 }
104 }
105
106 #[ion(get)]
107 pub fn get_signal(&self) -> *mut JSObject {
108 self.signal.get()
109 }
110
111 pub fn abort<'cx>(&self, cx: &'cx Context, Opt(reason): Opt<Value<'cx>>) {
112 let reason = reason.unwrap_or_else(|| Error::new("AbortError", None).as_value(cx));
113 self.sender.send_replace(Some(reason.get()));
114 }
115}
116
117#[js_class]
118#[derive(Default)]
119pub struct AbortSignal {
120 reflector: Reflector,
121 #[trace(no_trace)]
122 pub(crate) signal: Signal,
123}
124
125#[js_class]
126impl AbortSignal {
127 #[ion(get)]
128 pub fn get_aborted(&self) -> bool {
129 self.get_reason().is_some()
130 }
131
132 #[ion(get)]
133 pub fn get_reason(&self) -> Option<JSVal> {
134 match &self.signal {
135 Signal::None => None,
136 Signal::Abort(abort) => Some(*abort),
137 Signal::Receiver(receiver) | Signal::Timeout(receiver, _) => *receiver.borrow(),
138 }
139 }
140
141 #[ion(name = "throwIfAborted")]
142 pub fn throw_if_aborted(&self) -> ResultExc<()> {
143 if let Some(reason) = self.get_reason() {
144 Err(Exception::Other(reason))
145 } else {
146 Ok(())
147 }
148 }
149
150 pub fn abort<'cx>(cx: &'cx Context, Opt(reason): Opt<Value<'cx>>) -> *mut JSObject {
151 let reason = reason.unwrap_or_else(|| Error::new("AbortError", None).as_value(cx));
152 AbortSignal::new_object(
153 cx,
154 Box::new(AbortSignal {
155 reflector: Reflector::default(),
156 signal: Signal::Abort(reason.get()),
157 }),
158 )
159 }
160
161 pub fn timeout(cx: &Context, Enforce(time): Enforce<u64>) -> *mut JSObject {
162 let (sender, receiver) = channel(None);
163 let terminate = Arc::new(AtomicBool::new(false));
164 let terminate2 = Arc::clone(&terminate);
165
166 let error = Error::new(format!("Timeout Error: {time}ms"), None).as_value(cx).get();
167 let callback = Box::new(move || {
168 sender.send_replace(Some(error));
169 });
170
171 let duration = Duration::from_millis(time);
172 let event_loop = unsafe { &mut cx.get_private().event_loop };
173 if let Some(queue) = &mut event_loop.macrotasks {
174 queue.enqueue(
175 Macrotask::Signal(SignalMacrotask::new(callback, terminate, duration)),
176 None,
177 );
178 AbortSignal::new_object(
179 cx,
180 Box::new(AbortSignal {
181 reflector: Reflector::default(),
182 signal: Signal::Timeout(receiver, terminate2),
183 }),
184 )
185 } else {
186 ptr::null_mut()
187 }
188 }
189}
190
191impl<'cx> FromValue<'cx> for AbortSignal {
192 type Config = ();
193 fn from_value(cx: &'cx Context, value: &Value, strict: bool, _: ()) -> Result<AbortSignal> {
194 let object = Object::from_value(cx, value, strict, ())?;
195 if AbortSignal::instance_of(cx, &object) {
196 Ok(AbortSignal {
197 reflector: Reflector::default(),
198 signal: AbortSignal::get_private(cx, &object)?.signal.clone(),
199 })
200 } else {
201 Err(Error::new("Expected AbortSignal", ErrorKind::Type))
202 }
203 }
204}
205
206pub fn define(cx: &Context, global: &Object) -> bool {
207 AbortController::init_class(cx, global).0 && AbortSignal::init_class(cx, global).0
208}