zed-industries/zed · error

Canvas text direction could not be set

Error message

Canvas text direction could not be set

What it means

Raised in `TextCanvas::configure` when assigning `direction = "ltr"` on the OffscreenCanvasRenderingContext2d via `js_sys::Reflect::set` completes without an exception but returns `false`, meaning the property write was rejected. The library forces left-to-right direction for deterministic text measurement, and treats a rejected assignment as fatal since subsequent text metrics would be unreliable. This mirrors the readback-option check in `new` and indicates an unusual JS environment or a context object that rejects property writes.

Solutions

  1. Run in an up-to-date browser with full CanvasRenderingContext2D.direction support
  2. If using a canvas mock/shim in tests, define properties as writable or implement a setter that returns true
  3. Check for proxies intercepting the context object and allow-list the 'direction' property
  4. Fall back to not setting direction (accepting default) if the environment cannot support it

Example fix

// before
ensure!(assigned, "Canvas text direction could not be set");
// after
if !assigned {
    log::warn!("Canvas text direction could not be set; relying on default direction");
}
Ok(())
Defensive patterns

Strategy: try-catch

Validate before calling

// probe whether the context accepts property writes before configuring
let probe = js_sys::Reflect::set(
    context.as_ref(),
    &JsValue::from_str("direction"),
    &JsValue::from_str("ltr"),
).unwrap_or(false);
if !probe { log::warn!("context rejects direction writes; metrics may vary"); }

Type guard

fn context_accepts_writes(context: &js_sys::Object) -> bool {
    !js_sys::Object::is_frozen(context) && !js_sys::Object::is_sealed(context)
}

Try / catch

match canvas.configure(&css_font) {
    Err(e) if e.to_string().contains("direction could not be set") => {
        // proceed with default direction; log for diagnostics
        log::warn!("direction ltr unsupported: {e}");
    }
    Err(e) => return Err(e),
    Ok(()) => (),
}

Prevention

When it happens

Trigger: The rendering context object is frozen/sealed or wrapped in a proxy that rejects writes, the JS engine does not implement the writable `direction` property on 2D contexts, or the context was replaced by a mock in tests with non-writable properties.

Common situations: Non-standard embedding runtimes (Node-based wasm testing with canvas mocks), browser versions lacking full CanvasRenderingContext2D.direction support, or test double objects whose property descriptors are non-writable.

Understand the failure class

Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.

Related errors


AI-assisted analysis of zed-industries/zed@916fc2b8cb (2026-09-19). Data as JSON: /api/errors/4a50f21009d6786a. Report an issue: GitHub.

Appendix: source

Thrown at crates/gpui_web/src/canvas_text.rs:66

    }

    fn configure(&self, css_font: &str) -> Result<()> {
        ensure!(!css_font.trim().is_empty(), "Canvas text font is empty");
        // An invalid CSS font assignment is ignored by Canvas. Reset first so
        // it cannot accidentally inherit the preceding request's font.
        self.context.set_font("10px sans-serif");
        self.context.set_font(css_font);
        self.context.set_text_align("left");
        self.context.set_text_baseline("alphabetic");
        self.context.set_fill_style_str("white");
        // web-sys does not expose direction on the offscreen 2D context.
        let assigned = js_sys::Reflect::set(
            self.context.as_ref(),
            &JsValue::from_str("direction"),
            &JsValue::from_str("ltr"),
        )
        .map_err(|error| anyhow!("setting Canvas text direction: {error:?}"))?;
        ensure!(assigned, "Canvas text direction could not be set");
        Ok(())
    }
}

fn with_canvas<T>(operation: impl FnOnce(&mut TextCanvas) -> Result<T>) -> Result<T> {
    CANVAS.with(|canvas| {
        let mut canvas = canvas
            .try_borrow_mut()
            .context("Canvas text renderer was called reentrantly")?;
        if canvas.is_none() {
            *canvas = Some(TextCanvas::new()?);
        }
        operation(
            canvas
                .as_mut()
                .context("Canvas text renderer is unavailable")?,
        )
    })

View on GitHub (pinned to 916fc2b8cb)