linebender/druid · error

Failed to request animation frame

Error message

Failed to request animation frame

What it means

Panic from `.expect()` when the JS `window.requestAnimationFrame()` call, made while building a `Window` in the web backend, throws or the browser rejects the callback registration. The library uses an animation frame to deliver the initial scale/size events to the handler; without it the new window would never learn its geometry, so failure is treated as fatal to `build`.

Solutions

  1. Ensure `build()` runs from a real browser window context after the DOM/canvas is ready (e.g. inside `requestAnimationFrame`/`onload`), not in a worker or during unload
  2. Provide a `requestAnimationFrame` polyfill when testing under Node/jsdom (e.g. raf polyfill package)
  3. Check the browser console for the underlying JS exception; fix the page lifecycle issue it reports
  4. Patch the app to call `build()` after `document.visibilitychange` indicates the page is visible

Example fix

// before
let window = WindowBuilder::new(app).build().expect("build failed"); // panics here in jsdom
// after (ensure browser-like env first)
if window web_sys::window().and_then(|w| w.request_animation_frame(cb)) .is_none() {
    panic!("requestAnimationFrame unavailable: run in a real browser environment");
}
let window = WindowBuilder::new(app).build().expect("build failed");
Defensive patterns

Strategy: try-catch

Validate before calling

// Ensure a real browser context exists before building windows
fn browser_ready() -> bool {
    web_sys::window()
        .map(|w| w.document().is_some())
        .unwrap_or(false)
}

Type guard

fn has_window() -> bool {
    web_sys::window().is_some()
}

Try / catch

// Panics cannot be caught across wasm normally; validate before build:
if !browser_ready() {
    console_error("window.build called outside a browser window context");
    return; // abort startup instead of panicking inside build()
}
let handle = WindowBuilder::new(app).build().expect("build failed");

Prevention

When it happens

Trigger: `WindowBuilder::build()` (web/wasm32 target) when `requestAnimationFrame` throws — e.g. the page is being torn down, the code runs outside a browser window context (worker/jsdom/testing harness), or the browser refuses callback registration during page unload/navigation.

Common situations: Testing a druid/wasm app under Node or jsdom where `requestAnimationFrame` is unimplemented, embedding the canvas in an iframe that is removed during startup, or triggering window creation during `beforeunload`/`visibility: hidden` shutdown.

Related errors


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

Appendix: source

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

            canvas,
            canvas_size,
            context,
            invalid: RefCell::new(Region::EMPTY),
            click_counter: ClickCounter::default(),
            active_text_input: Cell::new(None),
            rendering_soon: Cell::new(false),
        });

        setup_web_callbacks(&window);

        // Register the scale & size with the window handler.
        let wh = window.clone();
        window
            .request_animation_frame(move || {
                wh.handler.borrow_mut().scale(scale);
                wh.handler.borrow_mut().size(size_dp);
            })
            .expect("Failed to request animation frame");

        let handle = WindowHandle(Rc::downgrade(&window));

        window.handler.borrow_mut().connect(&handle.clone().into());

        Ok(handle)
    }
}

impl WindowHandle {
    pub fn show(&self) {
        self.render_soon();
    }

    pub fn resizable(&self, _resizable: bool) {
        warn!("resizable unimplemented for web");
    }

View on GitHub (pinned to 0f8b1195e4)