1use super::JsObject;
17use std::cell::{Cell, RefCell};
18use std::collections::HashMap;
19use std::fmt;
20use std::future::Future;
21use std::pin::Pin;
22use std::rc::Rc;
23use std::task::{Context, Poll, Waker};
24use wasm_bindgen::prelude::*;
25
26#[wasm_bindgen(raw_module = "./ferroui.js")]
27extern "C" {
28 #[wasm_bindgen(js_namespace = CompletionHelper, js_name = track)]
29 fn js_track(promise: &JsObject, request_id: u32);
30}
31
32#[derive(Clone, Debug, PartialEq, Eq)]
36pub struct PromiseError {
37 name: String,
38 message: String,
39}
40
41impl PromiseError {
42 pub fn new(name: impl Into<String>, message: impl Into<String>) -> Self {
44 Self { name: name.into(), message: message.into() }
45 }
46
47 pub fn name(&self) -> &str {
49 &self.name
50 }
51
52 pub fn message(&self) -> &str {
54 &self.message
55 }
56}
57
58impl fmt::Display for PromiseError {
59 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
60 if self.name.is_empty() {
61 f.write_str(&self.message)
62 } else {
63 write!(f, "{}: {}", self.name, self.message)
64 }
65 }
66}
67
68impl std::error::Error for PromiseError {}
69
70impl From<PromiseError> for std::io::Error {
71 fn from(error: PromiseError) -> Self {
77 let kind = match error.name() {
78 "NotFoundError" => std::io::ErrorKind::NotFound,
79 "NotAllowedError" | "SecurityError" => std::io::ErrorKind::PermissionDenied,
80 "AbortError" => std::io::ErrorKind::Interrupted,
81 _ => std::io::ErrorKind::Other,
82 };
83 std::io::Error::new(kind, error)
84 }
85}
86
87pub type PromiseOutcome<V> = Result<V, PromiseError>;
89
90trait IPromiseTracker<V> {
93 fn track(&self, promise: &V, request_id: u32);
94}
95
96struct PagePromiseTracker;
97
98impl IPromiseTracker<JsObject> for PagePromiseTracker {
99 fn track(&self, promise: &JsObject, request_id: u32) {
100 js_track(promise, request_id);
101 }
102}
103
104struct PendingRequest<V> {
105 outcome: Option<PromiseOutcome<V>>,
106 waker: Option<Waker>,
107}
108
109struct Completions<V> {
111 last_request_id: Cell<u32>,
112 pending: RefCell<HashMap<u32, PendingRequest<V>>>,
113}
114
115impl<V> Completions<V> {
116 fn new() -> Rc<Self> {
117 Rc::new(Self { last_request_id: Cell::new(0), pending: RefCell::new(HashMap::new()) })
118 }
119
120 fn await_promise(self: &Rc<Self>, tracker: &dyn IPromiseTracker<V>, promise: &V) -> PromiseFuture<V> {
123 let mut request_id = self.last_request_id.get();
124 loop {
125 request_id = request_id.wrapping_add(1);
127 if !self.pending.borrow().contains_key(&request_id) {
128 break;
129 }
130 }
131 self.last_request_id.set(request_id);
132 self.pending.borrow_mut().insert(request_id, PendingRequest { outcome: None, waker: None });
133
134 tracker.track(promise, request_id);
135
136 PromiseFuture { completions: self.clone(), request_id }
137 }
138
139 fn complete(&self, request_id: u32, outcome: PromiseOutcome<V>) -> bool {
142 let waker = {
143 let mut pending = self.pending.borrow_mut();
144 let Some(request) = pending.get_mut(&request_id) else {
145 return false;
146 };
147 if request.outcome.is_some() {
148 return false;
149 }
150 request.outcome = Some(outcome);
151 request.waker.take()
152 };
153
154 if let Some(waker) = waker {
156 waker.wake();
157 }
158 true
159 }
160
161 fn pending_count(&self) -> usize {
162 self.pending.borrow().len()
163 }
164}
165
166pub struct PromiseFuture<V> {
173 completions: Rc<Completions<V>>,
174 request_id: u32,
175}
176
177impl<V> PromiseFuture<V> {
178 pub fn request_id(&self) -> u32 {
180 self.request_id
181 }
182}
183
184impl<V> Future for PromiseFuture<V> {
185 type Output = PromiseOutcome<V>;
186
187 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
188 let mut pending = self.completions.pending.borrow_mut();
189 let Some(request) = pending.get_mut(&self.request_id) else {
190 panic!("The request {} was polled after it completed.", self.request_id);
191 };
192
193 if request.outcome.is_some() {
194 let request = pending.remove(&self.request_id);
195 return Poll::Ready(request.and_then(|request| request.outcome).expect("the outcome of the request"));
196 }
197
198 request.waker = Some(cx.waker().clone());
199 Poll::Pending
200 }
201}
202
203impl<V> Drop for PromiseFuture<V> {
204 fn drop(&mut self) {
205 let removed = self.completions.pending.borrow_mut().remove(&self.request_id);
206 drop(removed);
207 }
208}
209
210ferroui_base::ferro_thread_local! {
211 static PAGE_COMPLETIONS: Rc<Completions<JsObject>> = Completions::new();
212}
213
214pub fn await_promise(promise: &JsObject) -> PromiseFuture<JsObject> {
216 PAGE_COMPLETIONS.with(|completions| completions.await_promise(&PagePromiseTracker, promise))
217}
218
219pub fn pending_request_count() -> usize {
221 PAGE_COMPLETIONS.with(|completions| completions.pending_count())
222}
223
224#[wasm_bindgen(js_name = CompletionHelper_OnResolved)]
227pub fn on_resolved(request_id: u32, value: JsObject) {
228 PAGE_COMPLETIONS.with(|completions| completions.complete(request_id, Ok(value)));
229}
230
231#[wasm_bindgen(js_name = CompletionHelper_OnRejected)]
234pub fn on_rejected(request_id: u32, name: Option<String>, message: Option<String>) {
235 let error = PromiseError::new(name.unwrap_or_default(), message.unwrap_or_default());
236 PAGE_COMPLETIONS.with(|completions| completions.complete(request_id, Err(error)));
237}
238
239#[cfg(test)]
240mod tests {
241 use super::*;
242 use std::sync::atomic::{AtomicUsize, Ordering};
243 use std::sync::Arc;
244 use std::task::Wake;
245
246 #[derive(Default)]
248 struct RecordingTracker {
249 tracked: RefCell<Vec<(String, u32)>>,
250 }
251
252 impl IPromiseTracker<String> for RecordingTracker {
253 fn track(&self, promise: &String, request_id: u32) {
254 self.tracked.borrow_mut().push((promise.clone(), request_id));
255 }
256 }
257
258 #[derive(Default)]
259 struct CountingWaker {
260 wakes: AtomicUsize,
261 }
262
263 impl Wake for CountingWaker {
264 fn wake(self: Arc<Self>) {
265 self.wakes.fetch_add(1, Ordering::SeqCst);
266 }
267 }
268
269 fn poll<V>(future: &mut PromiseFuture<V>, waker: &Arc<CountingWaker>) -> Poll<PromiseOutcome<V>> {
270 let waker = Waker::from(waker.clone());
271 let mut context = Context::from_waker(&waker);
272 Pin::new(future).poll(&mut context)
273 }
274
275 #[test]
276 fn the_promise_is_handed_to_the_page_with_its_request_id() {
277 let completions = Completions::new();
278 let tracker = RecordingTracker::default();
279
280 let first = completions.await_promise(&tracker, &"first".to_string());
281 let second = completions.await_promise(&tracker, &"second".to_string());
282
283 assert_eq!(
284 vec![("first".to_string(), first.request_id()), ("second".to_string(), second.request_id())],
285 *tracker.tracked.borrow()
286 );
287 assert_ne!(first.request_id(), second.request_id());
288 assert_eq!(2, completions.pending_count());
289 }
290
291 #[test]
292 fn the_future_waits_until_the_page_answers_and_then_has_the_value() {
293 let completions = Completions::new();
294 let tracker = RecordingTracker::default();
295 let waker = Arc::new(CountingWaker::default());
296
297 let mut future = completions.await_promise(&tracker, &"promise".to_string());
298 assert_eq!(Poll::Pending, poll(&mut future, &waker));
299 assert_eq!(0, waker.wakes.load(Ordering::SeqCst));
300
301 assert!(completions.complete(future.request_id(), Ok("value".to_string())));
302 assert_eq!(1, waker.wakes.load(Ordering::SeqCst));
303
304 assert_eq!(Poll::Ready(Ok("value".to_string())), poll(&mut future, &waker));
305 assert_eq!(0, completions.pending_count());
306 }
307
308 #[test]
309 fn a_rejection_is_the_outcome_of_the_future() {
310 let completions = Completions::new();
311 let tracker = RecordingTracker::default();
312 let waker = Arc::new(CountingWaker::default());
313
314 let mut future = completions.await_promise(&tracker, &"promise".to_string());
315 assert_eq!(Poll::Pending, poll(&mut future, &waker));
316 let error = PromiseError::new("NotAllowedError", "Read permission denied.");
317 assert!(completions.complete(future.request_id(), Err(error.clone())));
318
319 assert_eq!(Poll::Ready(Err(error)), poll(&mut future, &waker));
320 }
321
322 #[test]
323 fn an_answer_that_comes_before_the_first_poll_is_kept() {
324 let completions = Completions::new();
325 let tracker = RecordingTracker::default();
326 let waker = Arc::new(CountingWaker::default());
327
328 let mut future = completions.await_promise(&tracker, &"promise".to_string());
329 assert!(completions.complete(future.request_id(), Ok("early".to_string())));
330
331 assert_eq!(Poll::Ready(Ok("early".to_string())), poll(&mut future, &waker));
332 assert_eq!(0, waker.wakes.load(Ordering::SeqCst));
333 }
334
335 #[test]
336 fn answers_complete_their_own_request_in_any_order() {
337 let completions = Completions::new();
338 let tracker = RecordingTracker::default();
339 let waker = Arc::new(CountingWaker::default());
340
341 let mut first = completions.await_promise(&tracker, &"first".to_string());
342 let mut second = completions.await_promise(&tracker, &"second".to_string());
343 assert_eq!(Poll::Pending, poll(&mut first, &waker));
344 assert_eq!(Poll::Pending, poll(&mut second, &waker));
345
346 assert!(completions.complete(second.request_id(), Ok("2".to_string())));
347 assert_eq!(Poll::Pending, poll(&mut first, &waker));
348 assert_eq!(Poll::Ready(Ok("2".to_string())), poll(&mut second, &waker));
349
350 assert!(completions.complete(first.request_id(), Ok("1".to_string())));
351 assert_eq!(Poll::Ready(Ok("1".to_string())), poll(&mut first, &waker));
352 }
353
354 #[test]
355 fn a_dropped_future_forgets_its_request_and_a_later_answer_is_ignored() {
356 let completions = Completions::new();
357 let tracker = RecordingTracker::default();
358
359 let future = completions.await_promise(&tracker, &"promise".to_string());
360 let request_id = future.request_id();
361 drop(future);
362
363 assert_eq!(0, completions.pending_count());
364 assert!(!completions.complete(request_id, Ok("late".to_string())));
365 }
366
367 #[test]
368 fn an_answer_to_an_unknown_or_answered_request_is_ignored() {
369 let completions = Completions::new();
370 let tracker = RecordingTracker::default();
371 let waker = Arc::new(CountingWaker::default());
372
373 assert!(!completions.complete(7, Ok("stray".to_string())));
374
375 let mut future = completions.await_promise(&tracker, &"promise".to_string());
376 assert!(completions.complete(future.request_id(), Ok("first".to_string())));
377 assert!(!completions.complete(future.request_id(), Ok("second".to_string())));
378 assert_eq!(Poll::Ready(Ok("first".to_string())), poll(&mut future, &waker));
379 }
380
381 #[test]
382 fn a_request_id_still_waiting_is_not_given_again() {
383 let completions = Completions::new();
384 let tracker = RecordingTracker::default();
385
386 let waiting = completions.await_promise(&tracker, &"waiting".to_string());
387 completions.last_request_id.set(waiting.request_id().wrapping_sub(1));
389
390 let next = completions.await_promise(&tracker, &"next".to_string());
391 assert_ne!(waiting.request_id(), next.request_id());
392 }
393
394 #[test]
395 #[should_panic(expected = "polled after it completed")]
396 fn polling_again_after_the_outcome_panics() {
397 let completions = Completions::new();
398 let tracker = RecordingTracker::default();
399 let waker = Arc::new(CountingWaker::default());
400
401 let mut future = completions.await_promise(&tracker, &"promise".to_string());
402 completions.complete(future.request_id(), Ok("value".to_string()));
403 assert_eq!(Poll::Ready(Ok("value".to_string())), poll(&mut future, &waker));
404 let _ = poll(&mut future, &waker);
405 }
406
407 #[test]
408 fn a_rejection_is_an_io_error_that_keeps_the_name_and_the_message() {
409 for (name, kind) in [
410 ("NotFoundError", std::io::ErrorKind::NotFound),
411 ("NotAllowedError", std::io::ErrorKind::PermissionDenied),
412 ("SecurityError", std::io::ErrorKind::PermissionDenied),
413 ("AbortError", std::io::ErrorKind::Interrupted),
414 ("TypeError", std::io::ErrorKind::Other),
415 ("", std::io::ErrorKind::Other),
416 ] {
417 let error: std::io::Error = PromiseError::new(name, "The user aborted a request.").into();
418 assert_eq!(kind, error.kind(), "{name}");
419 let rejection = error.get_ref().and_then(|inner| inner.downcast_ref::<PromiseError>()).expect("the rejection");
420 assert_eq!(name, rejection.name());
421 assert_eq!("The user aborted a request.", rejection.message());
422 }
423 }
424
425 #[test]
426 fn the_error_is_shown_with_its_name() {
427 assert_eq!("NotAllowedError: denied", PromiseError::new("NotAllowedError", "denied").to_string());
428 assert_eq!("reason", PromiseError::new("", "reason").to_string());
429 }
430}