diff --git a/crates/eframe/src/web/web_runner.rs b/crates/eframe/src/web/web_runner.rs index bfb96d9c710a..49cdf346b5d8 100644 --- a/crates/eframe/src/web/web_runner.rs +++ b/crates/eframe/src/web/web_runner.rs @@ -1,7 +1,4 @@ -use std::{ - cell::{Cell, RefCell}, - rc::Rc, -}; +use std::{cell::RefCell, rc::Rc}; use wasm_bindgen::prelude::*; @@ -29,7 +26,7 @@ pub struct WebRunner { events_to_unsubscribe: Rc>>, /// Current animation frame in flight. - request_animation_frame_id: Cell>, + frame: Rc>>, resize_observer: Rc>>, } @@ -49,7 +46,7 @@ impl WebRunner { panic_handler, runner: Rc::new(RefCell::new(None)), events_to_unsubscribe: Rc::new(RefCell::new(Default::default())), - request_animation_frame_id: Cell::new(None), + frame: Default::default(), resize_observer: Default::default(), } } @@ -125,9 +122,10 @@ impl WebRunner { pub fn destroy(&self) { self.unsubscribe_from_all_events(); - if let Some(id) = self.request_animation_frame_id.get() { + if let Some(frame) = self.frame.take() { let window = web_sys::window().unwrap(); - window.cancel_animation_frame(id).ok(); + window.cancel_animation_frame(frame.id).ok(); + drop(frame.closure); } if let Some(runner) = self.runner.replace(None) { @@ -203,14 +201,27 @@ impl WebRunner { } pub(crate) fn request_animation_frame(&self) -> Result<(), wasm_bindgen::JsValue> { + if self.frame.borrow().is_some() { + // there is already an animation frame in flight + return Ok(()); + } + let window = web_sys::window().unwrap(); let closure = Closure::once({ let runner_ref = self.clone(); - move || events::paint_and_schedule(&runner_ref) + move || { + // we can paint now, so clear the animation frame + // this drop the `closure` and allows another + // animation frame to be scheduled + let _ = runner_ref.frame.take(); + events::paint_and_schedule(&runner_ref) + } }); - self.request_animation_frame_id.set(Some(id)); - closure.forget(); // We must forget it, or else the callback is canceled on drop + let id = window.request_animation_frame(closure.as_ref().unchecked_ref())?; + self.frame + .borrow_mut() + .replace(AnimationFrameRequest { id, closure }); Ok(()) } @@ -231,6 +242,19 @@ impl WebRunner { // ---------------------------------------------------------------------------- +// https://rustwasm.github.io/wasm-bindgen/api/wasm_bindgen/closure/struct.Closure.html#using-fnonce-and-closureonce-with-requestanimationframe +struct AnimationFrameRequest { + /// Represents the ID of a frame in flight. + /// + /// This is only set between a call to `request_animation_frame` and the invocation of its callback, + /// which means that repeated calls to `request_animation_frame` will be ignored. + id: i32, + + /// The callback given to `request_animation_frame`, stored here both to prevent it + /// from being canceled, and from having to `.forget()` it. + closure: Closure Result<(), JsValue>>, +} + struct ResizeObserverContext { resize_observer: web_sys::ResizeObserver, closure: Closure,