Skip to main content

perspective_viewer/utils/browser/
request_animation_frame.rs

1// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
2// ┃ ██████ ██████ ██████       █      █      █      █      █ █▄  ▀███ █       ┃
3// ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█  ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄  ▀█ █ ▀▀▀▀▀ ┃
4// ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄   █ ▄▄▄▄▄ ┃
5// ┃ █      ██████ █  ▀█▄       █ ██████      █      ███▌▐███ ███████▄ █       ┃
6// ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
7// ┃ Copyright (c) 2017, the Perspective Authors.                              ┃
8// ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
9// ┃ This file is part of the Perspective library, distributed under the terms ┃
10// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
11// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
12
13use ::futures::channel::oneshot::*;
14use perspective_js::utils::{global, *};
15use wasm_bindgen::prelude::*;
16
17/// An `async` version of `queueMicrotask()`, curiously absent from [`web_sys`].
18pub async fn await_queue_microtask() -> ApiResult<()> {
19    #[wasm_bindgen]
20    extern "C" {
21        #[wasm_bindgen(js_name = Window)]
22        type QMWindow;
23
24        #[wasm_bindgen(catch, method, structural, js_class = "Window", js_name = queueMicrotask)]
25        fn queue_microtask(this: &QMWindow, callback: &::js_sys::Function) -> Result<i32, JsValue>;
26    }
27
28    let (sender, receiver) = channel::<()>();
29    let jsfun = Closure::once_into_js(move || sender.send(()).unwrap());
30    js_sys::global()
31        .dyn_into::<QMWindow>()
32        .unwrap()
33        .queue_microtask(jsfun.unchecked_ref())?;
34
35    Ok(receiver.await?)
36}
37
38/// An `async` version of `requestAnimationFrame()`, which resolves on the next
39/// animation frame.
40pub async fn request_animation_frame() {
41    let (sender, receiver) = channel::<()>();
42    let jsfun = Closure::once_into_js(move || sender.send(()).unwrap());
43    global::window()
44        .request_animation_frame(jsfun.unchecked_ref())
45        .unwrap();
46
47    receiver.await.unwrap()
48}
49
50/// An `async` which awaits the browser's `load` event, which is automatically
51/// bypassed if `document.readyState` indicates this has already occurred.
52/// Resolve once the DOM is interactive and every linked stylesheet has
53/// APPLIED (`link.sheet` set) — the point at which computed styles are
54/// readable. Never await the `load` EVENT here: an in-flight `@font-face`
55/// request delays `load` on Firefox, and the font loader that depends on
56/// this function is itself what manages those requests.
57pub async fn await_dom_loaded() -> ApiResult<()> {
58    loop {
59        let document = global::document();
60        let state = document.ready_state();
61        if state == "complete" || state == "loaded" {
62            return Ok(());
63        }
64
65        if state != "loading" {
66            let links = document.query_selector_all("link[rel=stylesheet]")?;
67            let applied = (0..links.length()).all(|i| {
68                links
69                    .item(i)
70                    .and_then(|node| node.dyn_into::<web_sys::HtmlLinkElement>().ok())
71                    .is_none_or(|link| link.sheet().is_some())
72            });
73
74            if applied {
75                return Ok(());
76            }
77        }
78
79        set_timeout(50).await?;
80    }
81}
82
83/// An `async` version of `set_timeout`, which resolves in `timeout`
84/// milliseconds
85pub async fn set_timeout(timeout: i32) -> ApiResult<()> {
86    let (sender, receiver) = channel::<()>();
87    let jsfun = Closure::once_into_js(move || {
88        let _ = sender.send(());
89    });
90
91    global::window()
92        .set_timeout_with_callback_and_timeout_and_arguments_0(jsfun.unchecked_ref(), timeout)?;
93
94    Ok(receiver.await?)
95}