use std::sync::Arc;
use std::sync::atomic::{AtomicBool, Ordering};
use ferrijs_std::abort::AbortSignal;
use ferrijs_std::events::Emitter;
use ferrijs_std::exceptions::{DOMException, DOMExceptionName};
use rquickjs::function::{Opt, This};
use rquickjs::{Class, Ctx, Function, Value};
pub struct AbortInner {
aborted: AtomicBool,
notify: tokio::sync::Notify,
message: std::sync::Mutex<Option<String>>,
}
impl AbortInner {
fn new() -> Arc<Self> {
Arc::new(Self {
aborted: AtomicBool::new(false),
notify: tokio::sync::Notify::new(),
message: std::sync::Mutex::new(None),
})
}
pub fn is_aborted(&self) -> bool {
self.aborted.load(Ordering::Acquire)
}
pub fn reason_message(&self) -> String {
self
.message
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.clone()
.unwrap_or_else(|| "This operation was aborted".to_string())
}
fn mark(&self, message: Option<String>) {
*self.message.lock().unwrap_or_else(std::sync::PoisonError::into_inner) = message;
self.aborted.store(true, Ordering::Release);
self.notify.notify_waiters();
}
pub async fn aborted(&self) {
self.notify.notified().await;
}
}
fn reason_message(reason: Option<&Value<'_>>) -> Option<String> {
let r = reason?;
if let Some(s) = r.as_string().and_then(|s| s.to_string().ok()) {
return Some(s);
}
r.as_object()
.and_then(|o| o.get::<_, String>("message").ok())
.or(Some("This operation was aborted".to_string()))
}
pub fn native_channel<'js>(ctx: &Ctx<'js>, signal: &Class<'js, AbortSignal<'js>>) -> rquickjs::Result<Arc<AbortInner>> {
let inner = AbortInner::new();
{
let b = signal.borrow();
if b.aborted {
inner.mark(reason_message(b.reason().as_ref()));
return Ok(inner);
}
}
let sink = inner.clone();
let cb = Function::new(ctx.clone(), move |this: This<Value<'js>>| {
let reason = Class::<AbortSignal<'js>>::from_value(&this.0)
.ok()
.and_then(|s| s.borrow().reason());
sink.mark(reason_message(reason.as_ref()));
})?;
AbortSignal::add_event_listener_str(signal.clone(), ctx, "abort", cb, false, true)?;
Ok(inner)
}
pub fn fresh_instance<'js>(ctx: &Ctx<'js>) -> rquickjs::Result<Class<'js, AbortSignal<'js>>> {
Class::instance(ctx.clone(), AbortSignal::new())
}
pub fn abort_native<'js>(
signal: &Class<'js, AbortSignal<'js>>,
ctx: &Ctx<'js>,
name: DOMExceptionName,
message: &str,
) -> rquickjs::Result<()> {
let reason = Class::instance(
ctx.clone(),
DOMException::new_with_name(ctx, name, message.to_string())?,
)?;
signal.borrow_mut().set_reason(Opt(Some(reason.into_value())));
AbortSignal::send_aborted(This(signal.clone()), ctx.clone())
}