GraphiteEditor/Graphite · error
No global `window` exists
Error message
No global `window` exists
What it means
`request_animation_frame` is a helper that schedules a Rust closure on the browser's animation loop by calling `web_sys::window()` first. `web_sys::window()` returns `None` whenever the global `window` object does not exist — i.e. the code is not running in a browser window context — and the `.expect` immediately panics. It is a precondition failure of "this wrapper must run on a DOM main thread", not a browser bug.
Source
Thrown at frontend/wrapper/src/helpers.rs:25
use crate::native_communication::RasterizedImage;
#[cfg(not(feature = "native"))]
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();View on GitHub (pinned to c507b35645)
Solutions
- Ensure the wrapper and anything that schedules frames runs only on the main thread of a real browser page (dynamic `import()` after mount, not during SSR).
- In tests, run wasm-bindgen tests in browser mode (e.g. `wasm-bindgen-test --chrome`) instead of Node.
- Replace the `.expect` with a `let Some(window) = ... else { error!(...); return; }` guard as the sibling `render_image_data_to_canvases` already does.
- If scheduling from a worker is required, post a message to the main thread and let it call rAF.
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
let Some(window) = web_sys::window() else {
error!("Cannot schedule animation frame: no global `window` (worker or non-browser environment?)");
return;
};
window.request_animation_frame(f.as_ref().unchecked_ref()).expect("Failed to call `requestAnimationFrame`"); Defensive patterns
Strategy: validation
Validate before calling
// TS: only boot the wrapper where a real DOM window exists
if (typeof window === 'undefined' || typeof window.requestAnimationFrame !== 'function') {
throw new Error('Graphite wrapper requires a browser main thread (window + DOM)');
}
const editor = await createEditor(callback); Type guard
function hasBrowserWindow(): boolean {
return typeof window !== 'undefined' && typeof window.requestAnimationFrame === 'function';
} Prevention
- Dynamic-import the editor wrapper after DOM mount; never during SSR/prerender.
- Run wasm tests in browser mode, not Node.
- Keep UI scheduling on the main thread; message workers instead of calling rAF there.
When it happens
Trigger: Loading the graphite wasm wrapper in a Web Worker (`DedicatedWorkerGlobalScope`/`SharedWorkerGlobalScope` have no `window`), under Node.js/wasm-bindgen-test's default (non-browser) runner, or in any headless/SSR environment that evaluates the module without a real DOM before hydrating.
Common situations: Unit-testing wrapper code outside a real browser; SSR/prerender pipelines (Vite/Next-style) that import the module during build; moving heavy work (node graph evaluation) into a worker while accidentally calling UI-scheduling helpers there.
Related errors
- Failed to call `requestAnimationFrame`
- Failed to call `setTimeout`
- window should have a document
- should have a window in this context
- Failed to get canvas context
AI-assisted analysis of GraphiteEditor/Graphite@c507b35645 (2026-08-16).
Data as JSON: /api/errors/0a3b4233d0f01354.
Report an issue: GitHub.