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}