sauron_core/dom/
ric.rs

1use crate::dom::{now, request_timeout_callback, window, TimeoutCallbackHandle};
2use std::rc::Rc;
3use wasm_bindgen::closure::Closure;
4use wasm_bindgen::{JsCast, JsValue};
5
6/// request idle callback handle
7#[derive(Clone)]
8pub struct IdleCallbackHandleReal {
9    handle: u32,
10    _closure: Rc<Closure<dyn FnMut(JsValue)>>,
11}
12
13/// when dropped, cancel the idle callback
14impl Drop for IdleCallbackHandleReal {
15    fn drop(&mut self) {
16        window().cancel_idle_callback(self.handle);
17    }
18}
19
20/// request and idle callback
21fn request_idle_callback_real<F>(mut f: F) -> Result<IdleCallbackHandleReal, JsValue>
22where
23    F: FnMut(web_sys::IdleDeadline) + 'static,
24{
25    let closure = Closure::once(move |v: JsValue| {
26        let deadline = v
27            .dyn_into::<web_sys::IdleDeadline>()
28            .expect("must have an idle deadline");
29        f(deadline);
30    });
31
32    let handle = window().request_idle_callback(closure.as_ref().unchecked_ref())?;
33    Ok(IdleCallbackHandleReal {
34        handle,
35        _closure: Rc::new(closure),
36    })
37}
38
39/// Idle deadline interface which could be the real idle deadline if supported, otherwise the
40/// polyfill
41#[derive(Clone)]
42pub enum IdleDeadline {
43    /// the web native IdleDeadline object wrap together with polyfill version
44    Real(web_sys::IdleDeadline),
45    /// A polyfill for simulating IdleDeadline
46    Polyfill {
47        /// timestamp the closure is executed
48        start: f64,
49    },
50}
51
52///
53#[derive(Clone)]
54pub enum IdleCallbackHandle {
55    /// wrapper to the real web native IdleCallbackHandle
56    Real(IdleCallbackHandleReal),
57    ///
58    Polyfill(TimeoutCallbackHandle),
59}
60
61impl IdleDeadline {
62    fn polyfill() -> Self {
63        Self::Polyfill { start: now() }
64    }
65
66    /// calculate the remaining time for the IdleDeadline
67    pub fn time_remaining(&self) -> f64 {
68        match self {
69            Self::Real(deadline) => deadline.time_remaining(),
70            Self::Polyfill { start } => 0.0_f64.max(50. - now() - start),
71        }
72    }
73
74    /// returns true if there is no more time for executing more work
75    pub fn did_timeout(&self) -> bool {
76        match self {
77            Self::Real(deadline) => deadline.did_timeout(),
78            Self::Polyfill { .. } => self.time_remaining() > 0.,
79        }
80    }
81}
82
83/// request idle callback
84pub fn request_idle_callback<F>(mut f: F) -> Result<IdleCallbackHandle, JsValue>
85where
86    F: FnMut(IdleDeadline) + 'static,
87{
88    let is_ric_available = window().get("requestIdleCallback").is_some();
89    if is_ric_available {
90        let handle = request_idle_callback_real(move |dl| {
91            let deadline = IdleDeadline::Real(dl);
92            f(deadline)
93        })?;
94        Ok(IdleCallbackHandle::Real(handle))
95    } else {
96        let handle = request_idle_callback_shim(move || f(IdleDeadline::polyfill()))?;
97        Ok(IdleCallbackHandle::Polyfill(handle))
98    }
99}
100
101fn request_idle_callback_shim<F>(f: F) -> Result<TimeoutCallbackHandle, JsValue>
102where
103    F: FnMut() + 'static,
104{
105    request_timeout_callback(f, 1)
106}