diff --git a/src/platform_impl/web/event_loop/window_target.rs b/src/platform_impl/web/event_loop/window_target.rs index 8cfbb42756..a4c62a8313 100644 --- a/src/platform_impl/web/event_loop/window_target.rs +++ b/src/platform_impl/web/event_loop/window_target.rs @@ -719,6 +719,14 @@ impl EventLoopWindowTarget { } }, ); + + let runner = self.runner.clone(); + canvas.on_frame_throttle(move || { + runner.send_event(Event::WindowEvent { + window_id: RootWindowId(id), + event: WindowEvent::FrameThrottled, + }) + }) } pub fn available_monitors(&self) -> VecDequeIter { diff --git a/src/platform_impl/web/web_sys/canvas.rs b/src/platform_impl/web/web_sys/canvas.rs index 67a383e8a5..07f2db27ea 100644 --- a/src/platform_impl/web/web_sys/canvas.rs +++ b/src/platform_impl/web/web_sys/canvas.rs @@ -2,7 +2,7 @@ use super::super::WindowId; use super::event_handle::EventListenerHandle; use super::media_query_handle::MediaQueryListHandle; use super::pointer::PointerHandler; -use super::{event, ButtonsState, ResizeScaleHandle}; +use super::{event, ButtonsState, FrameThrottlingHandler, ResizeScaleHandle}; use crate::dpi::{LogicalPosition, PhysicalPosition, PhysicalSize}; use crate::error::OsError as RootOE; use crate::event::{Force, MouseButton, MouseScrollDelta}; @@ -37,6 +37,7 @@ pub struct Canvas { on_dark_mode: Option, pointer_handler: PointerHandler, on_resize_scale: Option, + frame_throttling_handler: FrameThrottlingHandler, } pub struct Common { @@ -87,7 +88,7 @@ impl Canvas { Ok(Canvas { common: Common { - window, + window: window.clone(), raw: canvas, old_size: Rc::default(), current_size: Rc::default(), @@ -105,6 +106,7 @@ impl Canvas { on_dark_mode: None, pointer_handler: PointerHandler::new(), on_resize_scale: None, + frame_throttling_handler: FrameThrottlingHandler::new(window), }) } @@ -365,6 +367,13 @@ impl Canvas { )); } + pub(crate) fn on_frame_throttle(&mut self, f: F) + where + F: 'static + FnMut(), + { + self.frame_throttling_handler.on_frame_throttle(f) + } + pub fn request_fullscreen(&self) { self.common.request_fullscreen() } @@ -373,6 +382,10 @@ impl Canvas { self.common.is_fullscreen() } + pub fn request_frame_throttling_hint(&self) { + self.frame_throttling_handler.request(); + } + pub(crate) fn handle_scale_change( &self, runner: &super::super::event_loop::runner::Shared, diff --git a/src/platform_impl/web/web_sys/frame_throttling.rs b/src/platform_impl/web/web_sys/frame_throttling.rs new file mode 100644 index 0000000000..c289fa146e --- /dev/null +++ b/src/platform_impl/web/web_sys/frame_throttling.rs @@ -0,0 +1,62 @@ +use std::cell::Cell; +use std::rc::Rc; +use wasm_bindgen::closure::Closure; +use wasm_bindgen::JsCast; + +pub struct FrameThrottlingHandler { + window: web_sys::Window, + closure: Closure, + handle: Rc>>, +} + +impl FrameThrottlingHandler { + pub fn new(window: web_sys::Window) -> Self { + let handle = Rc::new(Cell::new(None)); + let closure = Closure::new({ + let handle = handle.clone(); + move || handle.set(None) + }); + + Self { + window, + closure, + handle, + } + } + + pub fn on_frame_throttle(&mut self, mut f: F) + where + F: 'static + FnMut(), + { + let handle = self.handle.clone(); + self.closure = Closure::new(move || { + handle.set(None); + f(); + }) + } + + pub fn request(&self) { + if let Some(handle) = self.handle.take() { + self.window + .cancel_animation_frame(handle) + .expect("Failed to cancel animation frame"); + } + + let handle = self + .window + .request_animation_frame(self.closure.as_ref().unchecked_ref()) + .expect("Failed to request animation frame"); + + self.handle.set(Some(handle)); + } +} + +impl Drop for FrameThrottlingHandler { + fn drop(&mut self) { + if let Some(handle) = self.handle.take() { + self.window + .cancel_animation_frame(handle) + .expect("Failed to cancel animation frame"); + } + } +} diff --git a/src/platform_impl/web/web_sys/mod.rs b/src/platform_impl/web/web_sys/mod.rs index 367680d201..c7f78c586f 100644 --- a/src/platform_impl/web/web_sys/mod.rs +++ b/src/platform_impl/web/web_sys/mod.rs @@ -1,6 +1,7 @@ mod canvas; pub mod event; mod event_handle; +mod frame_throttling; mod media_query_handle; mod pointer; mod resize_scaling; @@ -9,6 +10,7 @@ mod timeout; pub use self::canvas::Canvas; pub use self::event::ButtonsState; pub use self::event_handle::EventListenerHandle; +pub use self::frame_throttling::FrameThrottlingHandler; pub use self::resize_scaling::ResizeScaleHandle; pub use self::timeout::{IdleCallback, Timeout}; diff --git a/src/platform_impl/web/window.rs b/src/platform_impl/web/window.rs index df361ca008..ccc4bb3568 100644 --- a/src/platform_impl/web/window.rs +++ b/src/platform_impl/web/window.rs @@ -353,7 +353,10 @@ impl Window { #[inline] pub fn request_frame_throttling_hint(&self) -> Result<(), NotSupportedError> { - Err(NotSupportedError::new()) + self.inner.dispatch(move |inner| { + inner.canvas.borrow().request_frame_throttling_hint(); + }); + Ok(()) } #[inline] diff --git a/src/window.rs b/src/window.rs index 84c4c6afe0..d3058f01cb 100644 --- a/src/window.rs +++ b/src/window.rs @@ -1139,7 +1139,7 @@ impl Window { /// /// - **Wayland:** Uses frame callbacks. The user must perform drawing operation resulting in /// `wl_surface.commit`(eglSwapBuffers, etc) after issueing a request. - /// - ** macOS / Windows / iOS / Android / Web / X11 / Orbital:** Not supported. + /// - ** macOS / Windows / iOS / Android / X11 / Orbital:** Not supported. /// /// [`FrameThrottled`]: crate::event::WindowEvent::FrameThrottled. #[inline]