linebender/druid · error

request_animation_frame failed

Error message

request_animation_frame failed

What it means

Panic from `.expect()` when `IdleHandle::wake` (or the equivalent idle-notification path) fails to schedule `process_idle_queue` via `request_animation_frame`. Idle tokens are flushed on the next animation frame; if the browser rejects the rAF registration, queued idle work would be silently lost, so the library treats it as fatal.

Solutions

  1. Drop/cancel IdleHandles and background tasks when the window closes instead of waking a dead window
  2. Make background completions check page/window liveness (e.g. `web_sys::window().is_some()`) before scheduling idle work
  3. Add a requestAnimationFrame polyfill for non-browser test environments
  4. Debounce wake-ups so many late callbacks don't pile up on a torn-down page

Example fix

// before
handle.wake(); // panics if page unloaded
// after
if web_sys::window().is_some() {
    handle.wake();
}
Defensive patterns

Strategy: fallback

Validate before calling

// Confirm browser context before waking idle handles
fn safe_to_wake() -> bool {
    web_sys::window().is_some()
}

Type guard

fn handle_live(handle: &IdleHandle) -> bool {
    !handle_window_closed && web_sys::window().is_some()
}

Try / catch

// Fallback: retry once on the next tick, else log and drop
if safe_to_wake() {
    handle.wake();
} else {
    console_error("idle wake dropped: no window context");
}

Prevention

When it happens

Trigger: `IdleHandle::wake` / `schedule_idle` on the web backend after the window has been dropped (the `upgrade()` still succeeds but the page is torn down), during page unload, or in an environment lacking `requestAnimationFrame`.

Common situations: A background task (fetch promise, WebSocket message) resolves and calls `schedule_idle` after the user navigated away or closed the tab; running wasm under Node/jsdom tests; detached iframes receiving late idle wake-ups.

Related errors


AI-assisted analysis of linebender/druid@0f8b1195e4 (2026-09-10). Data as JSON: /api/errors/9c9bb0c0929b3e00. Report an issue: GitHub.

Appendix: source

Thrown at druid-shell/src/backend/web/window.rs:741

unsafe impl Sync for IdleHandle {}

impl IdleHandle {
    /// Add an idle handler, which is called (once) when the main thread is idle.
    pub fn add_idle_callback<F>(&self, callback: F)
    where
        F: FnOnce(&mut dyn WinHandler) + Send + 'static,
    {
        let mut queue = self.queue.lock().expect("IdleHandle::add_idle queue");
        queue.push(IdleKind::Callback(Box::new(callback)));

        if queue.len() == 1 {
            if let Some(window_state) = self.state.upgrade() {
                let state = window_state.clone();
                window_state
                    .request_animation_frame(move || {
                        state.process_idle_queue();
                    })
                    .expect("request_animation_frame failed");
            }
        }
    }

    pub fn add_idle_token(&self, token: IdleToken) {
        let mut queue = self.queue.lock().expect("IdleHandle::add_idle queue");
        queue.push(IdleKind::Token(token));

        if queue.len() == 1 {
            if let Some(window_state) = self.state.upgrade() {
                let state = window_state.clone();
                window_state
                    .request_animation_frame(move || {
                        state.process_idle_queue();
                    })
                    .expect("request_animation_frame failed");
            }
        }

View on GitHub (pinned to 0f8b1195e4)