1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
use crate::dom::{now, request_timeout_callback, window, TimeoutCallbackHandle};
use wasm_bindgen::closure::Closure;
use wasm_bindgen::{JsCast, JsValue};

/// request idle callback handle
pub struct IdleCallbackHandleReal {
    handle: u32,
    _closure: Closure<dyn FnMut(JsValue)>,
}

/// when dropped, cancel the idle callback
impl Drop for IdleCallbackHandleReal {
    fn drop(&mut self) {
        window().cancel_idle_callback(self.handle);
    }
}

/// request and idle callback
fn request_idle_callback_real<F>(mut f: F) -> Result<IdleCallbackHandleReal, JsValue>
where
    F: FnMut(web_sys::IdleDeadline) + 'static,
{
    let closure = Closure::once(move |v: JsValue| {
        let deadline = v
            .dyn_into::<web_sys::IdleDeadline>()
            .expect("must have an idle deadline");
        f(deadline);
    });

    let handle = window().request_idle_callback(closure.as_ref().unchecked_ref())?;
    Ok(IdleCallbackHandleReal {
        handle,
        _closure: closure,
    })
}

/// Idle deadline interface which could be the real idle deadline if supported, otherwise the
/// polyfill
pub enum IdleDeadline {
    /// the web native IdleDeadline object wrap together with polyfill version
    Real(web_sys::IdleDeadline),
    /// A polyfill for simulating IdleDeadline
    Polyfill {
        /// timestamp the closure is executed
        start: f64,
    },
}

///
pub enum IdleCallbackHandle {
    /// wrapper to the real web native IdleCallbackHandle
    Real(IdleCallbackHandleReal),
    ///
    Polyfill(TimeoutCallbackHandle),
}

impl IdleDeadline {
    fn polyfill() -> Self {
        Self::Polyfill { start: now() }
    }

    /// calculate the remaining time for the IdleDeadline
    pub fn time_remaining(&self) -> f64 {
        match self {
            Self::Real(deadline) => deadline.time_remaining(),
            Self::Polyfill { start } => 0.0_f64.max(50. - now() - start),
        }
    }

    /// returns true if there is no more time for executing more work
    pub fn did_timeout(&self) -> bool {
        match self {
            Self::Real(deadline) => deadline.did_timeout(),
            Self::Polyfill { .. } => self.time_remaining() > 0.,
        }
    }
}

/// request idle callback
pub fn request_idle_callback<F>(mut f: F) -> Result<IdleCallbackHandle, JsValue>
where
    F: FnMut(IdleDeadline) + 'static,
{
    let is_ric_available = window().get("requestIdleCallback").is_some();
    if is_ric_available {
        let handle = request_idle_callback_real(move |dl| {
            let deadline = IdleDeadline::Real(dl);
            f(deadline)
        })?;
        Ok(IdleCallbackHandle::Real(handle))
    } else {
        let handle = request_idle_callback_shim(move || f(IdleDeadline::polyfill()))?;
        Ok(IdleCallbackHandle::Polyfill(handle))
    }
}

fn request_idle_callback_shim<F>(f: F) -> Result<TimeoutCallbackHandle, JsValue>
where
    F: FnMut() + 'static,
{
    request_timeout_callback(f, 1)
}