runtime/globals/
abort.rs

1/*
2 * This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
5 */
6
7use 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}