linebender/druid · error

Failed to produce a text context

Error message

Failed to produce a text context

What it means

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.

Solutions

  1. 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.
  2. Don't hold WindowHandle clones in globals or JS callbacks that can outlive the window; tie their lifetime to the window's lifetime.
  3. 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.
  4. 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.

Example fix

// before: using a stashed handle later
let handle = window_handle.clone();
on_timeout(move || {
    let piet_text = handle.text(); // panics if window dropped
});
// after: capture the text handle while the window is alive
let piet_text = window.text();
on_timeout(move || {
    let piet_text = piet_text.clone(); // safe, independent of window lifetime
});
Defensive patterns

Strategy: try-catch

Validate before calling

// web backend: check window liveness before use (no public API, so track it yourself)
let window_alive = !dropped_windows.contains(&window_id);

Type guard

function isWindowAlive(handle: WindowHandleRef): boolean {
  return handle.weak.upgrade().is_some(); // mirror of Weak::upgrade check
}

Try / catch

let text = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| window.text()))
    .map_err(|_| anyhow!("window already dropped; text() unavailable"))?;

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of linebender/druid@0f8b1195e4 (2026-09-10). Data as JSON: /api/errors/6d145252cc92b223. Report an issue: GitHub.

Appendix: source

Thrown at druid-shell/src/backend/web/window.rs:589

            s.invalid.borrow_mut().add_rect(rect);
        }
        self.render_soon();
    }

    pub fn invalidate(&self) {
        if let Some(s) = self.0.upgrade() {
            s.invalid
                .borrow_mut()
                .add_rect(s.area.get().size_dp().to_rect());
        }
        self.render_soon();
    }

    pub fn text(&self) -> PietText {
        let s = self
            .0
            .upgrade()
            .unwrap_or_else(|| panic!("Failed to produce a text context"));

        PietText::new(s.context.clone())
    }

    pub fn add_text_field(&self) -> TextFieldToken {
        TextFieldToken::next()
    }

    pub fn remove_text_field(&self, token: TextFieldToken) {
        if let Some(state) = self.0.upgrade() {
            if state.active_text_input.get() == Some(token) {
                state.active_text_input.set(None);
            }
        }
    }

    pub fn set_focused_text_field(&self, active_field: Option<TextFieldToken>) {
        if let Some(state) = self.0.upgrade() {

View on GitHub (pinned to 0f8b1195e4)