GraphiteEditor/Graphite · error
Failed to call `requestAnimationFrame`
Error message
Failed to call `requestAnimationFrame`
What it means
After obtaining the browser window, the helper calls `window.requestAnimationFrame(callback)`; the web-sys binding returns `Result<(), JsValue>` because the underlying JS call can throw. The `.expect("Failed to call `requestAnimationFrame`")` panics when the browser rejects the call — which real browsers essentially never do for a valid function callback, so in practice this fires in DOM polyfills/mock environments where `requestAnimationFrame` is missing or not a function.
Source
Thrown at frontend/wrapper/src/helpers.rs:27
use editor::application::Editor;
#[cfg(feature = "editor")]
use editor::messages::input_mapper::utility_types::input_keyboard::Key;
#[cfg(not(feature = "native"))]
use editor::messages::prelude::*;
use js_sys::{Object, Reflect};
#[cfg(not(feature = "native"))]
use std::sync::atomic::Ordering;
use std::time::Duration;
use wasm_bindgen::JsCast;
use wasm_bindgen::prelude::*;
use web_sys::{CanvasRenderingContext2d, HtmlCanvasElement, ImageData, window};
/// Helper function for calling JS's `requestAnimationFrame` with the given closure
pub(crate) fn request_animation_frame(f: &Closure<dyn FnMut(f64)>) {
web_sys::window()
.expect("No global `window` exists")
.request_animation_frame(f.as_ref().unchecked_ref())
.expect("Failed to call `requestAnimationFrame`");
}
/// Helper function for calling JS's `setTimeout` with the given closure and delay
pub(crate) fn set_timeout(f: &Closure<dyn FnMut()>, delay: Duration) {
let delay = delay.clamp(Duration::ZERO, Duration::from_millis(i32::MAX as u64)).as_millis() as i32;
web_sys::window()
.expect("No global `window` exists")
.set_timeout_with_callback_and_timeout_and_arguments_0(f.as_ref().unchecked_ref(), delay)
.expect("Failed to call `setTimeout`");
}
/// Provides access to the `Editor` by calling the given closure with it as an argument.
#[cfg(not(feature = "native"))]
fn editor<T: Default>(callback: impl FnOnce(&mut editor::application::Editor) -> T) -> T {
EDITOR.with(|editor| {
let mut guard = editor.try_lock();
let Ok(Some(editor)) = guard.as_deref_mut() else {
log::error!("Failed to borrow editor");View on GitHub (pinned to c507b35645)
Solutions
- Run wasm tests in a real browser via `wasm-bindgen-test --chrome`/`--headless` instead of Node+jsdom.
- If a fake DOM is required, install a proper rAF polyfill before loading the wrapper (`window.requestAnimationFrame ??= (cb) => setTimeout(() => cb(performance.now()), 16)`).
- Change the helper to log and fall back to `set_timeout` when the rAF call returns `Err`.
- Check `typeof window?.requestAnimationFrame === 'function'` before initializing the editor.
Example fix
// before
web_sys::window()
.expect("No global `window` exists")
.request_animation_frame(f.as_ref().unchecked_ref())
.expect("Failed to call `requestAnimationFrame`");
// after
if let Some(window) = web_sys::window() {
match window.request_animation_frame(f.as_ref().unchecked_ref()) {
Ok(()) => return,
Err(err) => error!("requestAnimationFrame threw {err:?}; falling back to setTimeout"),
}
} else {
error!("No global `window` exists");
}
set_timeout(f, Duration::from_millis(16)); // degraded but functional fallback Defensive patterns
Strategy: fallback
Validate before calling
// TS: polyfill before the wrapper schedules anything
if (typeof window !== 'undefined' && !window.requestAnimationFrame) {
window.requestAnimationFrame = (cb: FrameRequestCallback) => setTimeout(() => cb(performance.now()), 16);
} Type guard
function rafAvailable(): boolean {
return typeof window !== 'undefined' && typeof window.requestAnimationFrame === 'function';
} Try / catch
A wasm trap cannot be caught in JS; wrap editor boot in a guard that verifies rafAvailable() first, and after unexpected behavior check `await editor.hasCrashed()` to decide whether to remount.
Prevention
- Run tests in a real browser or install a complete rAF polyfill.
- Verify `typeof window.requestAnimationFrame === 'function'` before initializing the editor.
When it happens
Trigger: Calling the helper in a jsdom/happy-dom test environment (older jsdom ships without `requestAnimationFrame`), under an SSR mock where `window` exists but only partially implements the DOM, or where a polyfill/monkey-patch replaces rAF with something that throws.
Common situations: Running component or wasm tests in Node with a fake `window`; bundler SSR shims that provide `window` but not the animation-frame APIs; ad-blockers or scripts that clobber `window.requestAnimationFrame`.
Related errors
- No global `window` exists
- Failed to call `setTimeout`
- window should have a document
- Failed to get canvas context
- Failed to get canvas context
AI-assisted analysis of GraphiteEditor/Graphite@c507b35645 (2026-08-16).
Data as JSON: /api/errors/3f8d837871a36bcd.
Report an issue: GitHub.