use super::{CallbackKey, Completion, Registration};
use crate::{
permission_state::callback_key::CallbackSequence,
wasm::permissions::{WebPermissionError, WebPermissionOutcome, request::malformed},
};
use js_sys::Function;
use std::{
cell::RefCell,
collections::BTreeMap,
rc::{Rc, Weak},
};
use wasm_bindgen::{JsCast, JsValue, closure::Closure};
use web_sys::{Position, PositionError};
struct Pending {
registration: Rc<Registration>,
completion: Weak<Completion>,
}
struct Dispatchers {
position: Closure<dyn FnMut(JsValue, Position)>,
rejected: Closure<dyn FnMut(JsValue, PositionError)>,
}
thread_local! {
static DISPATCHERS: RefCell<Option<Dispatchers>> = const { RefCell::new(None) };
static SEQUENCE: RefCell<CallbackSequence> = RefCell::new(CallbackSequence::default());
static PENDING: RefCell<BTreeMap<CallbackKey, Pending>> = const { RefCell::new(BTreeMap::new()) };
}
fn take(key: &JsValue) -> Option<Pending> {
let key = key.as_string().and_then(|key| CallbackKey::decode(&key))?;
PENDING.with(|pending| pending.borrow_mut().remove(&key))
}
fn settle(pending: Pending, result: std::io::Result<WebPermissionOutcome>) {
pending.registration.clear();
if let Some(completion) = pending.completion.upgrade() {
completion.finish(result);
} else {
pending
.registration
.registry
.state
.borrow_mut()
.settle(pending.registration.token);
}
}
impl Dispatchers {
fn new() -> Self {
let position =
Closure::<dyn FnMut(JsValue, Position)>::new(|key: JsValue, position: Position| {
let Some(pending) = take(&key) else {
return;
};
let result = if position.timestamp().is_finite() {
Ok(WebPermissionOutcome::GeolocationGranted)
} else {
Err(malformed(
"Browser geolocation callback has a nonfinite timestamp",
))
};
settle(pending, result);
});
let rejected = Closure::<dyn FnMut(JsValue, PositionError)>::new(
|key: JsValue, error: PositionError| {
let Some(pending) = take(&key) else {
return;
};
let kind = pending.registration.token.kind;
let error = WebPermissionError {
code: Some(u32::from(error.code())),
name: None,
message: error.message(),
};
settle(pending, Ok(WebPermissionOutcome::Rejected { kind, error }));
},
);
Self { position, rejected }
}
fn bind(&self, key: CallbackKey) -> (Function, Function) {
let key = JsValue::from_str(&key.encode());
let position: &Function = self.position.as_ref().unchecked_ref();
let rejected: &Function = self.rejected.as_ref().unchecked_ref();
(
position.bind1(&JsValue::UNDEFINED, &key).unchecked_into(),
rejected.bind1(&JsValue::UNDEFINED, &key).unchecked_into(),
)
}
}
pub(super) fn register(
registration: &Rc<Registration>,
completion: &Rc<Completion>,
) -> std::io::Result<(CallbackKey, Function, Function)> {
let key = SEQUENCE.with(|sequence| sequence.borrow_mut().next())?;
let (position, rejected) = DISPATCHERS.with(|dispatchers| {
dispatchers
.borrow_mut()
.get_or_insert_with(Dispatchers::new)
.bind(key)
});
PENDING.with(|pending| {
let old = pending.borrow_mut().insert(
key,
Pending {
registration: Rc::clone(registration),
completion: Rc::downgrade(completion),
},
);
assert!(old.is_none(), "realm geolocation keys never repeat");
});
Ok((key, position, rejected))
}
pub(super) fn registration_failed(key: CallbackKey) {
PENDING.with(|pending| pending.borrow_mut().remove(&key));
}
pub(super) fn dispatcher_count() -> usize {
DISPATCHERS.with(|dispatchers| if dispatchers.borrow().is_some() { 2 } else { 0 })
}