From 7ce86c3d2a4125948817cc9201674a1a59a75130 Mon Sep 17 00:00:00 2001 From: dAxpeDDa Date: Tue, 13 Jun 2023 16:39:06 +0200 Subject: [PATCH] Use `Window.requestIdleCallback()` --- CHANGELOG.md | 1 + src/event_loop.rs | 7 --- src/platform_impl/web/event_loop/runner.rs | 7 +-- src/platform_impl/web/event_loop/state.rs | 2 +- src/platform_impl/web/web_sys/mod.rs | 2 +- src/platform_impl/web/web_sys/timeout.rs | 66 +++++++++++++++++----- 6 files changed, 59 insertions(+), 26 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d8bcefc6..578187a0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -78,6 +78,7 @@ And please only add new entries to the top of this list, right below the `# Unre - On Web, fix the bfcache by not using the `beforeunload` event. - On Web, fix scale factor resize suggestion always overwriting the canvas size. - On macOS, fix crash when dropping `Window`. +- On Web, use `Window.requestIdleCallback()` for `ControlFlow::Poll` when available. # 0.28.6 diff --git a/src/event_loop.rs b/src/event_loop.rs index d3fe3248..7c81bbe1 100644 --- a/src/event_loop.rs +++ b/src/event_loop.rs @@ -158,13 +158,6 @@ impl fmt::Debug for EventLoopWindowTarget { pub enum ControlFlow { /// When the current loop iteration finishes, immediately begin a new iteration regardless of /// whether or not new events are available to process. - /// - /// ## Platform-specific - /// - /// - **Web:** Events are queued and usually sent when `requestAnimationFrame` fires but sometimes - /// the events in the queue may be sent before the next `requestAnimationFrame` callback, for - /// example when the scaling of the page has changed. This should be treated as an implementation - /// detail which should not be relied on. Poll, /// When the current loop iteration finishes, suspend the thread until another event arrives. diff --git a/src/platform_impl/web/event_loop/runner.rs b/src/platform_impl/web/event_loop/runner.rs index f6340830..42494cfd 100644 --- a/src/platform_impl/web/event_loop/runner.rs +++ b/src/platform_impl/web/event_loop/runner.rs @@ -442,10 +442,9 @@ impl Shared { ControlFlow::Poll => { let cloned = self.clone(); State::Poll { - request: backend::AnimationFrameRequest::new( - self.window().clone(), - move || cloned.poll(), - ), + request: backend::IdleCallback::new(self.window().clone(), move || { + cloned.poll() + }), } } ControlFlow::Wait => State::Wait { diff --git a/src/platform_impl/web/event_loop/state.rs b/src/platform_impl/web/event_loop/state.rs index dabf0cb5..b9dacee6 100644 --- a/src/platform_impl/web/event_loop/state.rs +++ b/src/platform_impl/web/event_loop/state.rs @@ -15,7 +15,7 @@ pub enum State { start: Instant, }, Poll { - request: backend::AnimationFrameRequest, + request: backend::IdleCallback, }, Exit, } diff --git a/src/platform_impl/web/web_sys/mod.rs b/src/platform_impl/web/web_sys/mod.rs index 611fcd78..4a47ebbc 100644 --- a/src/platform_impl/web/web_sys/mod.rs +++ b/src/platform_impl/web/web_sys/mod.rs @@ -9,7 +9,7 @@ mod timeout; pub use self::canvas::Canvas; pub use self::event::ButtonsState; pub use self::scaling::ScaleChangeDetector; -pub use self::timeout::{AnimationFrameRequest, Timeout}; +pub use self::timeout::{IdleCallback, Timeout}; use crate::dpi::{LogicalSize, Size}; use crate::platform::web::WindowExtWebSys; diff --git a/src/platform_impl/web/web_sys/timeout.rs b/src/platform_impl/web/web_sys/timeout.rs index 8250cf63..1273a84d 100644 --- a/src/platform_impl/web/web_sys/timeout.rs +++ b/src/platform_impl/web/web_sys/timeout.rs @@ -1,8 +1,11 @@ +use once_cell::unsync::OnceCell; use std::cell::Cell; use std::rc::Rc; use std::time::Duration; use wasm_bindgen::closure::Closure; +use wasm_bindgen::prelude::wasm_bindgen; use wasm_bindgen::JsCast; +use wasm_bindgen::JsValue; #[derive(Debug)] pub struct Timeout { @@ -40,16 +43,21 @@ impl Drop for Timeout { } #[derive(Debug)] -pub struct AnimationFrameRequest { +pub struct IdleCallback { window: web_sys::Window, - handle: i32, - // track callback state, because `cancelAnimationFrame` is slow + handle: Handle, fired: Rc>, _closure: Closure, } -impl AnimationFrameRequest { - pub fn new(window: web_sys::Window, mut f: F) -> AnimationFrameRequest +#[derive(Clone, Copy, Debug)] +enum Handle { + IdleCallback(u32), + Timeout(i32), +} + +impl IdleCallback { + pub fn new(window: web_sys::Window, mut f: F) -> IdleCallback where F: 'static + FnMut(), { @@ -60,11 +68,21 @@ impl AnimationFrameRequest { f(); }) as Box); - let handle = window - .request_animation_frame(closure.as_ref().unchecked_ref()) - .expect("Failed to request animation frame"); + let handle = if has_idle_callback_support(&window) { + Handle::IdleCallback( + window + .request_idle_callback(closure.as_ref().unchecked_ref()) + .expect("Failed to request idle callback"), + ) + } else { + Handle::Timeout( + window + .set_timeout_with_callback(closure.as_ref().unchecked_ref()) + .expect("Failed to set timeout"), + ) + }; - AnimationFrameRequest { + IdleCallback { window, handle, fired, @@ -73,12 +91,34 @@ impl AnimationFrameRequest { } } -impl Drop for AnimationFrameRequest { +impl Drop for IdleCallback { fn drop(&mut self) { if !(*self.fired).get() { - self.window - .cancel_animation_frame(self.handle) - .expect("Failed to cancel animation frame"); + match self.handle { + Handle::IdleCallback(handle) => self.window.cancel_idle_callback(handle), + Handle::Timeout(handle) => self.window.clear_timeout_with_handle(handle), + } } } } + +fn has_idle_callback_support(window: &web_sys::Window) -> bool { + thread_local! { + static IDLE_CALLBACK_SUPPORT: OnceCell = OnceCell::new(); + } + + IDLE_CALLBACK_SUPPORT.with(|support| { + *support.get_or_init(|| { + #[wasm_bindgen] + extern "C" { + type IdleCallbackSupport; + + #[wasm_bindgen(method, getter, js_name = requestIdleCallback)] + fn has_request_idle_callback(this: &IdleCallbackSupport) -> JsValue; + } + + let support: &IdleCallbackSupport = window.unchecked_ref(); + !support.has_request_idle_callback().is_undefined() + }) + }) +}