diff --git a/CHANGELOG.md b/CHANGELOG.md index 0d198d7d..8af1139d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ # Unreleased - On X11, fix `ResumeTimeReached` being fired too early. +- On Web, replaced zero timeout for `ControlFlow::Poll` with `requestAnimationFrame` - On Web, fix a possible panic during event handling # 0.22.0 (2020-03-09) diff --git a/src/event_loop.rs b/src/event_loop.rs index 8a05e313..224292a5 100644 --- a/src/event_loop.rs +++ b/src/event_loop.rs @@ -72,7 +72,8 @@ impl fmt::Debug for EventLoopWindowTarget { #[derive(Copy, Clone, Debug, PartialEq, Eq)] 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. + /// whether or not new events are available to process. For web, events are sent when + /// `requestAnimationFrame` fires. Poll, /// When the current loop iteration finishes, suspend the thread until another event arrives. Wait, diff --git a/src/platform_impl/web/event_loop/runner.rs b/src/platform_impl/web/event_loop/runner.rs index 7c67eac0..d381b942 100644 --- a/src/platform_impl/web/event_loop/runner.rs +++ b/src/platform_impl/web/event_loop/runner.rs @@ -241,7 +241,7 @@ impl Shared { root::ControlFlow::Poll => { let cloned = self.clone(); State::Poll { - timeout: backend::Timeout::new(move || cloned.poll(), Duration::from_millis(0)), + request: backend::AnimationFrameRequest::new(move || cloned.poll()), } } root::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 23e8045f..16b6e623 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 { - timeout: backend::Timeout, + request: backend::AnimationFrameRequest, }, Exit, } diff --git a/src/platform_impl/web/stdweb/mod.rs b/src/platform_impl/web/stdweb/mod.rs index a95dfc7d..3632307c 100644 --- a/src/platform_impl/web/stdweb/mod.rs +++ b/src/platform_impl/web/stdweb/mod.rs @@ -3,7 +3,7 @@ mod event; mod timeout; pub use self::canvas::Canvas; -pub use self::timeout::Timeout; +pub use self::timeout::{AnimationFrameRequest, Timeout}; use crate::dpi::{LogicalSize, Size}; use crate::platform::web::WindowExtStdweb; diff --git a/src/platform_impl/web/stdweb/timeout.rs b/src/platform_impl/web/stdweb/timeout.rs index 00ac2ab0..72bfb72b 100644 --- a/src/platform_impl/web/stdweb/timeout.rs +++ b/src/platform_impl/web/stdweb/timeout.rs @@ -1,5 +1,7 @@ +use std::cell::Cell; +use std::rc::Rc; use std::time::Duration; -use stdweb::web::{window, IWindowOrWorker, TimeoutHandle}; +use stdweb::web::{window, IWindowOrWorker, RequestAnimationFrameHandle, TimeoutHandle}; #[derive(Debug)] pub struct Timeout { @@ -23,3 +25,39 @@ impl Drop for Timeout { handle.clear(); } } + +#[derive(Debug)] +pub struct AnimationFrameRequest { + handle: Option, + // track callback state, because `cancelAnimationFrame` is slow + fired: Rc>, +} + +impl AnimationFrameRequest { + pub fn new(mut f: F) -> AnimationFrameRequest + where + F: 'static + FnMut(), + { + let fired = Rc::new(Cell::new(false)); + let c_fired = fired.clone(); + let handle = window().request_animation_frame(move |_| { + (*c_fired).set(true); + f(); + }); + + AnimationFrameRequest { + handle: Some(handle), + fired, + } + } +} + +impl Drop for AnimationFrameRequest { + fn drop(&mut self) { + if !(*self.fired).get() { + if let Some(handle) = self.handle.take() { + handle.cancel(); + } + } + } +} diff --git a/src/platform_impl/web/web_sys/mod.rs b/src/platform_impl/web/web_sys/mod.rs index c41cd069..94efe1ec 100644 --- a/src/platform_impl/web/web_sys/mod.rs +++ b/src/platform_impl/web/web_sys/mod.rs @@ -3,7 +3,7 @@ mod event; mod timeout; pub use self::canvas::Canvas; -pub use self::timeout::Timeout; +pub use self::timeout::{AnimationFrameRequest, 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 e7ce69a0..e95c54ed 100644 --- a/src/platform_impl/web/web_sys/timeout.rs +++ b/src/platform_impl/web/web_sys/timeout.rs @@ -1,3 +1,5 @@ +use std::cell::Cell; +use std::rc::Rc; use std::time::Duration; use wasm_bindgen::closure::Closure; use wasm_bindgen::JsCast; @@ -38,3 +40,48 @@ impl Drop for Timeout { window.clear_timeout_with_handle(self.handle); } } + +#[derive(Debug)] +pub struct AnimationFrameRequest { + handle: i32, + // track callback state, because `cancelAnimationFrame` is slow + fired: Rc>, + _closure: Closure, +} + +impl AnimationFrameRequest { + pub fn new(mut f: F) -> AnimationFrameRequest + where + F: 'static + FnMut(), + { + let window = web_sys::window().expect("Failed to obtain window"); + + let fired = Rc::new(Cell::new(false)); + let c_fired = fired.clone(); + let closure = Closure::wrap(Box::new(move || { + (*c_fired).set(true); + f(); + }) as Box); + + let handle = window + .request_animation_frame(&closure.as_ref().unchecked_ref()) + .expect("Failed to request animation frame"); + + AnimationFrameRequest { + handle, + fired, + _closure: closure, + } + } +} + +impl Drop for AnimationFrameRequest { + fn drop(&mut self) { + if !(*self.fired).get() { + let window = web_sys::window().expect("Failed to obtain window"); + window + .cancel_animation_frame(self.handle) + .expect("Failed to cancel animation frame"); + } + } +}