{"record":{"id":"3f8d837871a36bcd","repo":"GraphiteEditor/Graphite","slug":"failed-to-call-requestanimationframe","errorCode":null,"errorMessage":"Failed to call `requestAnimationFrame`","messagePattern":"Failed to call `requestAnimationFrame`","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"frontend/wrapper/src/helpers.rs","lineNumber":27,"sourceCode":"use editor::application::Editor;\n#[cfg(feature = \"editor\")]\nuse editor::messages::input_mapper::utility_types::input_keyboard::Key;\n#[cfg(not(feature = \"native\"))]\nuse editor::messages::prelude::*;\nuse js_sys::{Object, Reflect};\n#[cfg(not(feature = \"native\"))]\nuse std::sync::atomic::Ordering;\nuse std::time::Duration;\nuse wasm_bindgen::JsCast;\nuse wasm_bindgen::prelude::*;\nuse web_sys::{CanvasRenderingContext2d, HtmlCanvasElement, ImageData, window};\n\n/// Helper function for calling JS's `requestAnimationFrame` with the given closure\npub(crate) fn request_animation_frame(f: &Closure<dyn FnMut(f64)>) {\n\tweb_sys::window()\n\t\t.expect(\"No global `window` exists\")\n\t\t.request_animation_frame(f.as_ref().unchecked_ref())\n\t\t.expect(\"Failed to call `requestAnimationFrame`\");\n}\n\n/// Helper function for calling JS's `setTimeout` with the given closure and delay\npub(crate) fn set_timeout(f: &Closure<dyn FnMut()>, delay: Duration) {\n\tlet delay = delay.clamp(Duration::ZERO, Duration::from_millis(i32::MAX as u64)).as_millis() as i32;\n\tweb_sys::window()\n\t\t.expect(\"No global `window` exists\")\n\t\t.set_timeout_with_callback_and_timeout_and_arguments_0(f.as_ref().unchecked_ref(), delay)\n\t\t.expect(\"Failed to call `setTimeout`\");\n}\n\n/// Provides access to the `Editor` by calling the given closure with it as an argument.\n#[cfg(not(feature = \"native\"))]\nfn editor<T: Default>(callback: impl FnOnce(&mut editor::application::Editor) -> T) -> T {\n\tEDITOR.with(|editor| {\n\t\tlet mut guard = editor.try_lock();\n\t\tlet Ok(Some(editor)) = guard.as_deref_mut() else {\n\t\t\tlog::error!(\"Failed to borrow editor\");","sourceCodeStart":9,"sourceCodeEnd":45,"githubUrl":"https://github.com/GraphiteEditor/Graphite/blob/c507b356453361e31638b8bff8f6d46b6da2961e/frontend/wrapper/src/helpers.rs#L9-L45","documentation":"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.","triggerScenarios":"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.","commonSituations":"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`.","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."],"exampleFix":"// before\nweb_sys::window()\n  .expect(\"No global `window` exists\")\n  .request_animation_frame(f.as_ref().unchecked_ref())\n  .expect(\"Failed to call `requestAnimationFrame`\");\n\n// after\nif let Some(window) = web_sys::window() {\n  match window.request_animation_frame(f.as_ref().unchecked_ref()) {\n    Ok(()) => return,\n    Err(err) => error!(\"requestAnimationFrame threw {err:?}; falling back to setTimeout\"),\n  }\n} else {\n  error!(\"No global `window` exists\");\n}\nset_timeout(f, Duration::from_millis(16)); // degraded but functional fallback","handlingStrategy":"fallback","validationCode":"// TS: polyfill before the wrapper schedules anything\nif (typeof window !== 'undefined' && !window.requestAnimationFrame) {\n  window.requestAnimationFrame = (cb: FrameRequestCallback) => setTimeout(() => cb(performance.now()), 16);\n}","typeGuard":"function rafAvailable(): boolean {\n  return typeof window !== 'undefined' && typeof window.requestAnimationFrame === 'function';\n}","tryCatchPattern":"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.","preventionTips":["Run tests in a real browser or install a complete rAF polyfill.","Verify `typeof window.requestAnimationFrame === 'function'` before initializing the editor."],"tags":["requestanimationframe","web-sys","wasm","panic","jsdom"],"backgroundTag":"requestanimationframe-failed","analyzedSha":"c507b356453361e31638b8bff8f6d46b6da2961e","analyzedAt":"2026-08-16T21:57:18.596Z","schemaVersion":2},"datasetVersion":"2026-08-16T23:17:17.608Z"}