use core::cell::RefCell;
use js_sys::Function;
use std::fmt::Debug;
use std::future::Future;
use std::rc::Rc;
use std::task::Poll;
use std::task::Waker;
use wasm_bindgen::prelude::Closure;
use wasm_bindgen::JsValue;
#[derive(Debug)]
pub struct CallbackPair<A, B>
where
A: 'static + ?Sized,
B: 'static + ?Sized,
{
inner: Rc<RefCell<CallbackPairInner<A, B>>>,
}
impl<A, B> CallbackPair<A, B>
where
A: 'static + ?Sized,
B: 'static + ?Sized,
{
pub fn new<X, Y>(x: X, y: Y) -> CallbackPair<A, B>
where
Self: From<(X, Y)>,
{
Self::from((x, y))
}
pub fn as_functions(&self) -> (Function, Function) {
let left: JsValue = self
.inner
.borrow()
.cb
.as_ref()
.unwrap()
.as_ref()
.0
.as_ref()
.into();
let right: JsValue = self
.inner
.borrow()
.cb
.as_ref()
.unwrap()
.as_ref()
.1
.as_ref()
.into();
(left.into(), right.into())
}
pub fn as_closures(&self) -> Rc<(Closure<A>, Closure<B>)> {
Rc::clone(self.inner.borrow().cb.as_ref().unwrap())
}
}
impl Default for CallbackPair<dyn FnMut(JsValue), dyn FnMut(JsValue)> {
fn default() -> Self {
Self::from((|data| Ok(data), |err| Err(err)))
}
}
impl<A, B> Future for CallbackPair<A, B>
where
A: 'static + ?Sized,
B: 'static + ?Sized,
{
type Output = Result<JsValue, JsValue>;
fn poll(
self: std::pin::Pin<&mut Self>,
cx: &mut std::task::Context<'_>,
) -> std::task::Poll<Self::Output> {
let mut inner = self.inner.borrow_mut();
if let Some(val) = inner.result.take() {
return Poll::Ready(val);
}
inner.task = Some(cx.waker().clone());
Poll::Pending
}
}
macro_rules! from_impl {
(($($a:ty),*), ($($b:ty),*), ($($alist:ident),*), ($($blist:ident),*)) => {
impl<A, B> From<(A, B)> for CallbackPair<dyn FnMut($($a,)*), dyn FnMut($($b,)*)>
where
A: 'static + FnOnce($($a,)*) -> Result<JsValue, JsValue>,
B: 'static + FnOnce($($b,)*) -> Result<JsValue, JsValue>,
{
fn from(cb: (A, B)) -> Self {
let inner = CallbackPairInner::new();
let state = Rc::clone(&inner);
let cb0 = cb.0;
let left = Closure::once(move |$($alist),*| CallbackPairInner::finish(&state, cb0($($alist),*)));
let state = Rc::clone(&inner);
let cb1 = cb.1;
let right = Closure::once(move |$($blist),*| CallbackPairInner::finish(&state, cb1($($blist),*)));
let ptr = Rc::new((left, right));
inner.borrow_mut().cb = Some(ptr);
CallbackPair { inner }
}
}
};
(($($a:ident,)*), ($($b:ident,)*)) => {
from_impl!(($(from_impl!(@rep $a JsValue)),*), ($(from_impl!(@rep $b JsValue)),*), ($($a),*), ($($b),*));
};
(@left ($($a:ident,)*); $head:ident $($tail:tt)*) => {
from_impl!(($($a,)*), ($head, $($tail,)*));
from_impl!(@left ($($a,)*); $($tail)*);
};
(@right ($($b:ident,)*); $head:ident $($tail:tt)*) => {
from_impl!(($head, $($tail,)*), ($($b,)*));
from_impl!(@right ($($b,)*); $($tail)*);
};
($head:ident $($tail:tt)*) => {
from_impl!(($head, $($tail,)*), ($head, $($tail,)*));
from_impl!(@left ($head, $($tail,)*); $($tail)*);
from_impl!(($head, $($tail,)*), ());
from_impl!(@right ($head, $($tail,)*); $($tail)*);
from_impl!((), ($head, $($tail,)*));
from_impl!($($tail)*);
};
(@rep $_t:tt $sub:ty) => {
$sub
};
() => {
from_impl!((), ());
};
(@left ($($a:ident,)*); ) => {};
(@right ($($b:ident,)*); ) => {};
}
from_impl!(a0 a1 a2 a3 a4 a5 a6);
#[derive(Debug)]
pub struct CallbackPairInner<A, B>
where
A: 'static + ?Sized,
B: 'static + ?Sized,
{
cb: Option<Rc<(Closure<A>, Closure<B>)>>,
result: Option<Result<JsValue, JsValue>>,
task: Option<Waker>,
}
impl<A, B> CallbackPairInner<A, B>
where
A: 'static + ?Sized,
B: 'static + ?Sized,
{
pub fn new() -> Rc<RefCell<CallbackPairInner<A, B>>> {
Rc::new(RefCell::new(CallbackPairInner {
cb: None,
task: None,
result: None,
}))
}
pub fn finish(state: &RefCell<CallbackPairInner<A, B>>, val: Result<JsValue, JsValue>) {
let task = {
let mut state = state.borrow_mut();
debug_assert!(state.result.is_none());
debug_assert!(state.cb.is_some());
drop(state.cb.take());
state.result = Some(val);
state.task.take()
};
if let Some(task) = task {
task.wake()
}
}
}
#[cfg(test)]
mod tests {
use crate::CallbackPair;
use std::rc::Rc;
use wasm_bindgen::JsCast;
use wasm_bindgen_test::*;
use web_sys::{window, IdbOpenDbRequest};
wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser);
#[wasm_bindgen_test]
#[rustfmt::skip]
fn should_compile_with_any_args() {
let _r = CallbackPair::new(|| Ok("".into()), || Err("".into()));
let _r = CallbackPair::new(|_a| Ok("".into()), || Err("".into()));
let _r = CallbackPair::new(|_a, _b| Ok("".into()), || Err("".into()));
let _r = CallbackPair::new(|_a, _b, _c| Ok("".into()), || Err("".into()));
let _r = CallbackPair::new(|_a, _b, _c, _d| Ok("".into()), || Err("".into()));
let _r = CallbackPair::new(|_a, _b, _c, _d, _e| Ok("".into()), || Err("".into()));
let _r = CallbackPair::new(|_a, _b, _c, _d, _e, _f| Ok("".into()), || Err("".into()));
let _r = CallbackPair::new(|_a, _b, _c, _d, _e, _f, _g| Ok("".into()), || Err("".into()));
let _r = CallbackPair::new(|| Ok("".into()), |_a| Err("".into()));
let _r = CallbackPair::new(|_a| Ok("".into()), |_a| Err("".into()));
let _r = CallbackPair::new(|_a, _b| Ok("".into()), |_a| Err("".into()));
let _r = CallbackPair::new(|_a, _b, _c| Ok("".into()), |_a| Err("".into()));
let _r = CallbackPair::new(|_a, _b, _c, _d| Ok("".into()), |_a| Err("".into()));
let _r = CallbackPair::new(|_a, _b, _c, _d, _e| Ok("".into()), |_a| Err("".into()));
let _r = CallbackPair::new(|_a, _b, _c, _d, _e, _f| Ok("".into()), |_a| Err("".into()));
let _r = CallbackPair::new(|_a, _b, _c, _d, _e, _f, _g| Ok("".into()), |_a| Err("".into()));
let _r = CallbackPair::new(|| Ok("".into()), |_a, _b| Err("".into()));
let _r = CallbackPair::new(|_a| Ok("".into()), |_a, _b| Err("".into()));
let _r = CallbackPair::new(|_a, _b| Ok("".into()), |_a, _b| Err("".into()));
let _r = CallbackPair::new(|_a, _b, _c| Ok("".into()), |_a, _b| Err("".into()));
let _r = CallbackPair::new(|_a, _b, _c, _d| Ok("".into()), |_a, _b| Err("".into()));
let _r = CallbackPair::new(|_a, _b, _c, _d, _e| Ok("".into()), |_a, _b| Err("".into()));
let _r = CallbackPair::new(|_a, _b, _c, _d, _e, _f| Ok("".into()), |_a, _b| Err("".into()));
let _r = CallbackPair::new(|_a, _b, _c, _d, _e, _f, _g| Ok("".into()), |_a, _b| Err("".into()));
let _r = CallbackPair::new(|| Ok("".into()), |_a, _b, _c| Err("".into()));
let _r = CallbackPair::new(|_a| Ok("".into()), |_a, _b, _c| Err("".into()));
let _r = CallbackPair::new(|_a, _b| Ok("".into()), |_a, _b, _c| Err("".into()));
let _r = CallbackPair::new(|_a, _b, _c| Ok("".into()), |_a, _b, _c| Err("".into()));
let _r = CallbackPair::new(|_a, _b, _c, _d| Ok("".into()), |_a, _b, _c| Err("".into()));
let _r = CallbackPair::new(|_a, _b, _c, _d, _e| Ok("".into()), |_a, _b, _c| Err("".into()));
let _r = CallbackPair::new(|_a, _b, _c, _d, _e, _f| Ok("".into()), |_a, _b, _c| Err("".into()));
let _r = CallbackPair::new(|_a, _b, _c, _d, _e, _f, _g| Ok("".into()), |_a, _b, _c| Err("".into()));
let _r = CallbackPair::new(|| Ok("".into()), |_a, _b, _c, _d| Err("".into()));
let _r = CallbackPair::new(|_a| Ok("".into()), |_a, _b, _c, _d| Err("".into()));
let _r = CallbackPair::new(|_a, _b| Ok("".into()), |_a, _b, _c, _d| Err("".into()));
let _r = CallbackPair::new(|_a, _b, _c| Ok("".into()), |_a, _b, _c, _d| Err("".into()));
let _r = CallbackPair::new(|_a, _b, _c, _d| Ok("".into()), |_a, _b, _c, _d| Err("".into()));
let _r = CallbackPair::new(|_a, _b, _c, _d, _e| Ok("".into()), |_a, _b, _c, _d| Err("".into()));
let _r = CallbackPair::new(|_a, _b, _c, _d, _e, _f| Ok("".into()), |_a, _b, _c, _d| Err("".into()));
let _r = CallbackPair::new(|_a, _b, _c, _d, _e, _f, _g| Ok("".into()), |_a, _b, _c, _d| Err("".into()));
let _r = CallbackPair::new(|| Ok("".into()), |_a, _b, _c, _d, _e| Err("".into()));
let _r = CallbackPair::new(|_a| Ok("".into()), |_a, _b, _c, _d, _e| Err("".into()));
let _r = CallbackPair::new(|_a, _b| Ok("".into()), |_a, _b, _c, _d, _e| Err("".into()));
let _r = CallbackPair::new(|_a, _b, _c| Ok("".into()), |_a, _b, _c, _d, _e| Err("".into()));
let _r = CallbackPair::new(|_a, _b, _c, _d| Ok("".into()), |_a, _b, _c, _d, _e| Err("".into()));
let _r = CallbackPair::new(|_a, _b, _c, _d, _e| Ok("".into()), |_a, _b, _c, _d, _e| Err("".into()));
let _r = CallbackPair::new(|_a, _b, _c, _d, _e, _f| Ok("".into()), |_a, _b, _c, _d, _e| Err("".into()));
let _r = CallbackPair::new(|_a, _b, _c, _d, _e, _f, _g| Ok("".into()), |_a, _b, _c, _d, _e| Err("".into()));
let _r = CallbackPair::new(|| Ok("".into()), |_a, _b, _c, _d, _e, _f| Err("".into()));
let _r = CallbackPair::new(|_a| Ok("".into()), |_a, _b, _c, _d, _e, _f| Err("".into()));
let _r = CallbackPair::new(|_a, _b| Ok("".into()), |_a, _b, _c, _d, _e, _f| Err("".into()));
let _r = CallbackPair::new(|_a, _b, _c| Ok("".into()), |_a, _b, _c, _d, _e, _f| Err("".into()));
let _r = CallbackPair::new(|_a, _b, _c, _d| Ok("".into()), |_a, _b, _c, _d, _e, _f| Err("".into()));
let _r = CallbackPair::new(|_a, _b, _c, _d, _e| Ok("".into()), |_a, _b, _c, _d, _e, _f| Err("".into()));
let _r = CallbackPair::new(|_a, _b, _c, _d, _e, _f| Ok("".into()), |_a, _b, _c, _d, _e, _f| Err("".into()));
let _r = CallbackPair::new(|_a, _b, _c, _d, _e, _f, _g| Ok("".into()), |_a, _b, _c, _d, _e, _f| Err("".into()));
let _r = CallbackPair::new(|| Ok("".into()), |_a, _b, _c, _d, _e, _f, _g| Err("".into()));
let _r = CallbackPair::new(|_a| Ok("".into()), |_a, _b, _c, _d, _e, _f, _g| Err("".into()));
let _r = CallbackPair::new(|_a, _b| Ok("".into()), |_a, _b, _c, _d, _e, _f, _g| Err("".into()));
let _r = CallbackPair::new(|_a, _b, _c| Ok("".into()), |_a, _b, _c, _d, _e, _f, _g| Err("".into()));
let _r = CallbackPair::new(|_a, _b, _c, _d| Ok("".into()), |_a, _b, _c, _d, _e, _f, _g| Err("".into()));
let _r = CallbackPair::new(|_a, _b, _c, _d, _e| Ok("".into()), |_a, _b, _c, _d, _e, _f, _g| Err("".into()));
let _r = CallbackPair::new(|_a, _b, _c, _d, _e, _f| Ok("".into()), |_a, _b, _c, _d, _e, _f, _g| Err("".into()));
let _r = CallbackPair::new(|_a, _b, _c, _d, _e, _f, _g| Ok("".into()), |_a, _b, _c, _d, _e, _f, _g| Err("".into()));
}
#[wasm_bindgen_test]
async fn inner_dropped_after_await() {
let future = CallbackPair::new(|| Ok("".into()), || Err("".into()));
let req: IdbOpenDbRequest = window()
.expect("window not available")
.indexed_db()
.unwrap()
.expect("idb not available")
.open("my_db")
.expect("Failed to get idb request");
let functions = future.as_functions();
req.set_onerror(Some(&functions.1));
let inner_ref = {
let weak_ref = Rc::downgrade(&future.inner);
req.set_onsuccess(Some(&functions.0));
assert_eq!(weak_ref.upgrade().is_some(), true); weak_ref
};
assert_eq!(inner_ref.upgrade().is_some(), true); future.await.unwrap();
assert_eq!(inner_ref.upgrade().is_none(), true); }
#[wasm_bindgen_test]
async fn closure_dropped_after_await() {
let future = CallbackPair::new(|| Ok("".into()), || Err("".into()));
let req: IdbOpenDbRequest = window()
.expect("window not available")
.indexed_db()
.unwrap()
.expect("idb not available")
.open("my_db")
.expect("Failed to get idb request");
let wref = {
let closures = future.as_closures();
let weak_ref = Rc::downgrade(&closures);
req.set_onsuccess(Some(closures.0.as_ref().as_ref().unchecked_ref()));
req.set_onerror(Some(closures.1.as_ref().as_ref().unchecked_ref()));
assert_eq!(weak_ref.upgrade().is_some(), true); weak_ref
};
assert_eq!(wref.upgrade().is_some(), true); future.await.unwrap();
assert_eq!(wref.upgrade().is_none(), true); }
#[wasm_bindgen_test]
async fn new_promise_left_resolve() {
let future = CallbackPair::default();
web_sys::window()
.unwrap()
.set_timeout_with_callback_and_timeout_and_arguments_0(future.as_functions().0.as_ref(), 200)
.unwrap();
let result = future.await;
assert_eq!(result.is_ok(), true); }
#[wasm_bindgen_test]
async fn new_promise_right_reject() {
let future = CallbackPair::default();
web_sys::window()
.unwrap()
.set_timeout_with_callback_and_timeout_and_arguments_0(future.as_functions().1.as_ref(), 200)
.unwrap();
let result = future.await;
assert_eq!(result.is_err(), true); }
}