{"record":{"id":"4ffadd829a93ae7c","repo":"GraphiteEditor/Graphite","slug":"failed-to-call-settimeout","errorCode":null,"errorMessage":"Failed to call `setTimeout`","messagePattern":"Failed to call `setTimeout`","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"frontend/wrapper/src/helpers.rs","lineNumber":36,"sourceCode":"use 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\");\n\t\t\treturn T::default();\n\t\t};\n\n\t\tcallback(editor)\n\t})\n}\n\n/// Provides access to the `Editor` and its `EditorWrapper` by calling the given closure with them as arguments.\n#[cfg(not(feature = \"native\"))]","sourceCodeStart":18,"sourceCodeEnd":54,"githubUrl":"https://github.com/GraphiteEditor/Graphite/blob/c507b356453361e31638b8bff8f6d46b6da2961e/frontend/wrapper/src/helpers.rs#L18-L54","documentation":"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).","triggerScenarios":"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.","commonSituations":"Node-based unit tests of editor timing logic; embedded webviews with aggressive script injection; overlays/security tools patching global timer functions.","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)."],"exampleFix":"// before\nweb_sys::window()\n  .expect(\"No global `window` exists\")\n  .set_timeout_with_callback_and_timeout_and_arguments_0(f.as_ref().unchecked_ref(), delay)\n  .expect(\"Failed to call `setTimeout`\");\n\n// after\nif let Some(window) = web_sys::window() {\n  if let Err(err) = window.set_timeout_with_callback_and_timeout_and_arguments_0(f.as_ref().unchecked_ref(), delay) {\n    error!(\"setTimeout threw {err:?}; dropping scheduled task\");\n  }\n} else {\n  error!(\"No global `window` exists\");\n}","handlingStrategy":"fallback","validationCode":"// TS: ensure setTimeout exists and is a function before editor init\nif (typeof window !== 'undefined' && typeof window.setTimeout !== 'function') {\n  throw new Error('window.setTimeout unavailable; editor cannot schedule tasks');\n}","typeGuard":"function setTimeoutAvailable(): boolean {\n  return typeof window !== 'undefined' && typeof window.setTimeout === 'function';\n}","tryCatchPattern":"Not catchable from JS; validate the environment up front and check `await editor.hasCrashed()` after failures to decide on remount.","preventionTips":["Avoid scripts/polyfills that clobber window.setTimeout.","Flush or cancel pending editor timers before unmounting the page."],"tags":["settimeout","web-sys","wasm","panic","jsdom"],"backgroundTag":"settimeout-failed","analyzedSha":"c507b356453361e31638b8bff8f6d46b6da2961e","analyzedAt":"2026-08-16T21:57:18.596Z","schemaVersion":2},"datasetVersion":"2026-08-16T23:17:17.608Z"}