GraphiteEditor/Graphite · error
Failed to call `setTimeout`
Error message
Failed to call `setTimeout`
What it means
Once the window is obtained, `set_timeout` calls `window.setTimeout(callback, delay)` through web-sys, whose binding returns `Result<(), JsValue>` because the JS call may throw. The `.expect("Failed to call `setTimeout`")` panics on that `Err`. In a functioning browser `setTimeout` essentially never throws for a valid function argument, so this is effectively a poisoned/mocked-environment error (setTimeout deleted, clobbered by a script, or a polyfill that throws).
Source
Thrown at frontend/wrapper/src/helpers.rs:36
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");
return T::default();
};
callback(editor)
})
}
/// Provides access to the `Editor` and its `EditorWrapper` by calling the given closure with them as arguments.
#[cfg(not(feature = "native"))]View on GitHub (pinned to c507b35645)
Solutions
- Run timing-related wasm tests in a real browser (`wasm-bindgen-test --chrome`) rather than a DOM emulation.
- Sanity-check the host page: `typeof window.setTimeout === 'function'` before creating the editor.
- Harden the helper: log the `JsValue` error and drop or re-dispatch the task instead of `.expect`.
- Avoid tearing down the wrapper while delayed tasks are still scheduled (flush/cancel timers on dispose).
Example fix
// before
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`");
// after
if let Some(window) = web_sys::window() {
if let Err(err) = window.set_timeout_with_callback_and_timeout_and_arguments_0(f.as_ref().unchecked_ref(), delay) {
error!("setTimeout threw {err:?}; dropping scheduled task");
}
} else {
error!("No global `window` exists");
} Defensive patterns
Strategy: fallback
Validate before calling
// TS: ensure setTimeout exists and is a function before editor init
if (typeof window !== 'undefined' && typeof window.setTimeout !== 'function') {
throw new Error('window.setTimeout unavailable; editor cannot schedule tasks');
} Type guard
function setTimeoutAvailable(): boolean {
return typeof window !== 'undefined' && typeof window.setTimeout === 'function';
} Try / catch
Not catchable from JS; validate the environment up front and check `await editor.hasCrashed()` after failures to decide on remount.
Prevention
- Avoid scripts/polyfills that clobber window.setTimeout.
- Flush or cancel pending editor timers before unmounting the page.
When it happens
Trigger: The wrapper scheduling a delayed task in a DOM emulation (jsdom/happy-dom) where `setTimeout` is partially implemented or shimmed to throw; `window.setTimeout` overwritten by a vendor script or CSP/privacy extension; passing a closure after the JS heap has been torn down during page unload.
Common situations: Node-based unit tests of editor timing logic; embedded webviews with aggressive script injection; overlays/security tools patching global timer functions.
Understand the failure class
- Timeouts: ETIMEDOUT, deadlines, and hung requests — what actually expires when a request times out.
Related errors
- Failed to call `requestAnimationFrame`
- No global `window` exists
- 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/4ffadd829a93ae7c.
Report an issue: GitHub.