{"record":{"id":"0a3b4233d0f01354","repo":"GraphiteEditor/Graphite","slug":"no-global-window-exists","errorCode":null,"errorMessage":"No global `window` exists","messagePattern":"No global `window` exists","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"frontend/wrapper/src/helpers.rs","lineNumber":25,"sourceCode":"use crate::native_communication::RasterizedImage;\n#[cfg(not(feature = \"native\"))]\nuse 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();","sourceCodeStart":7,"sourceCodeEnd":43,"githubUrl":"https://github.com/GraphiteEditor/Graphite/blob/c507b356453361e31638b8bff8f6d46b6da2961e/frontend/wrapper/src/helpers.rs#L7-L43","documentation":"`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.","triggerScenarios":"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.","commonSituations":"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.","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."],"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\nlet Some(window) = web_sys::window() else {\n  error!(\"Cannot schedule animation frame: no global `window` (worker or non-browser environment?)\");\n  return;\n};\nwindow.request_animation_frame(f.as_ref().unchecked_ref()).expect(\"Failed to call `requestAnimationFrame`\");","handlingStrategy":"validation","validationCode":"// TS: only boot the wrapper where a real DOM window exists\nif (typeof window === 'undefined' || typeof window.requestAnimationFrame !== 'function') {\n  throw new Error('Graphite wrapper requires a browser main thread (window + DOM)');\n}\nconst editor = await createEditor(callback);","typeGuard":"function hasBrowserWindow(): boolean {\n  return typeof window !== 'undefined' && typeof window.requestAnimationFrame === 'function';\n}","tryCatchPattern":null,"preventionTips":["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."],"tags":["web-sys","wasm","no-dom","worker","panic","requestanimationframe"],"backgroundTag":"wasm-no-global-window","analyzedSha":"c507b356453361e31638b8bff8f6d46b6da2961e","analyzedAt":"2026-08-16T21:57:18.596Z","schemaVersion":2},"datasetVersion":"2026-08-16T23:17:17.608Z"}