{"record":{"id":"6d145252cc92b223","repo":"linebender/druid","slug":"failed-to-produce-a-text-context","errorCode":null,"errorMessage":"Failed to produce a text context","messagePattern":"Failed to produce a text context","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"druid-shell/src/backend/web/window.rs","lineNumber":589,"sourceCode":"            s.invalid.borrow_mut().add_rect(rect);\n        }\n        self.render_soon();\n    }\n\n    pub fn invalidate(&self) {\n        if let Some(s) = self.0.upgrade() {\n            s.invalid\n                .borrow_mut()\n                .add_rect(s.area.get().size_dp().to_rect());\n        }\n        self.render_soon();\n    }\n\n    pub fn text(&self) -> PietText {\n        let s = self\n            .0\n            .upgrade()\n            .unwrap_or_else(|| panic!(\"Failed to produce a text context\"));\n\n        PietText::new(s.context.clone())\n    }\n\n    pub fn add_text_field(&self) -> TextFieldToken {\n        TextFieldToken::next()\n    }\n\n    pub fn remove_text_field(&self, token: TextFieldToken) {\n        if let Some(state) = self.0.upgrade() {\n            if state.active_text_input.get() == Some(token) {\n                state.active_text_input.set(None);\n            }\n        }\n    }\n\n    pub fn set_focused_text_field(&self, active_field: Option<TextFieldToken>) {\n        if let Some(state) = self.0.upgrade() {","sourceCodeStart":571,"sourceCodeEnd":607,"githubUrl":"https://github.com/linebender/druid/blob/0f8b1195e4e073f9597f2865299c3d18f8e4005f/druid-shell/src/backend/web/window.rs#L571-L607","documentation":"The web backend's WindowHandle::text() returns a PietText handle built from the window's rendering context, which is held via a Weak reference. If the underlying window (WebWindow) has already been dropped (its canvas/context gone), upgrade() returns None and the code panics instead of returning an error. It means you called text() on a window handle that no longer points to a live window.","triggerScenarios":"Calling window.text() (druid-shell web backend, window.rs:589) after the window was closed/dropped — e.g. holding a WindowHandle past window close, or calling text() from an async callback/timer after the canvas was removed from the DOM.","commonSituations":"WebAssembly apps using druid-shell directly that stash a WindowHandle in a global or JS-side closure and use it later; background tasks or requestAnimationFrame callbacks outliving a dropped window; forgetting that the web backend's window is garbage-collected with its canvas.","solutions":["Only call text() while the window is guaranteed alive; obtain the PietText handle during setup (in the window's init/connect handlers) and store it.","Don't hold WindowHandle clones in globals or JS callbacks that can outlive the window; tie their lifetime to the window's lifetime.","Restructure async/deferred work to go through the shell's scheduled callbacks (idle/timers owned by the window) so they are cancelled when the window dies.","If you need delayed text access, keep the parent Druid app state alive and fetch text through the app context rather than a raw handle."],"exampleFix":"// before: using a stashed handle later\nlet handle = window_handle.clone();\non_timeout(move || {\n    let piet_text = handle.text(); // panics if window dropped\n});\n// after: capture the text handle while the window is alive\nlet piet_text = window.text();\non_timeout(move || {\n    let piet_text = piet_text.clone(); // safe, independent of window lifetime\n});","handlingStrategy":"try-catch","validationCode":"// web backend: check window liveness before use (no public API, so track it yourself)\nlet window_alive = !dropped_windows.contains(&window_id);","typeGuard":"function isWindowAlive(handle: WindowHandleRef): boolean {\n  return handle.weak.upgrade().is_some(); // mirror of Weak::upgrade check\n}","tryCatchPattern":"let text = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| window.text()))\n    .map_err(|_| anyhow!(\"window already dropped; text() unavailable\"))?;","preventionTips":["Acquire PietText during window setup and reuse it instead of calling text() later.","Never store WindowHandle in globals, JS closures, or timers that can outlive the window.","Route deferred work through window-owned idle/timer callbacks so it is cancelled on close.","On wasm, remember the canvas removal drops the window — clear stored handles on close."],"tags":["wasm","web","text","lifetime","panic"],"backgroundTag":"null-reference","analyzedSha":"0f8b1195e4e073f9597f2865299c3d18f8e4005f","analyzedAt":"2026-09-10T14:27:34.582Z","contentChangedAt":"2026-09-10T14:27:34.582Z","schemaVersion":2},"datasetVersion":"2026-09-16T09:17:16.951Z"}