zed-industries/zed · error
Canvas readback option could not be set
Error message
Canvas readback option could not be set
What it means
Raised in `TextCanvas::new` when the `willReadFrequently` option could not be assigned on the canvas context options object via `js_sys::Reflect::set` — `Reflect::set` succeeded (no exception) but returned `false`, meaning the property could not be created/written. Without this option, subsequent `getImageData` readbacks would be slower, so the library treats a silent assignment failure as fatal. On wasm targets this usually indicates the options object is frozen, non-extensible, or has a non-writable 'willReadFrequently' property.
Solutions
- Run in a standard browser environment where OffscreenCanvas context options are supported
- Ensure the options object is a plain mutable `js_sys::Object` — don't pass frozen or sealed objects
- Check for polyfills/proxies overriding Object or Reflect behavior in the page
- Update the browser/wasm runtime to a version with full OffscreenCanvas 2D support
Example fix
// before
let assigned = js_sys::Reflect::set(&options, &JsValue::from_str("willReadFrequently"), &JsValue::TRUE)
.map_err(|error| anyhow!("setting Canvas readback option: {error:?}"))?;
ensure!(assigned, "Canvas readback option could not be set");
// after
let options = js_sys::Object::new(); // fresh, extensible object
js_sys::Reflect::set(&options, &JsValue::from_str("willReadFrequently"), &JsValue::TRUE)
.map_err(|error| anyhow!("setting Canvas readback option: {error:?}"))?
.then_some(())
.ok_or_else(|| anyhow!("Canvas readback option could not be set"))?; Defensive patterns
Strategy: try-catch
Validate before calling
// guard before constructing
let options = js_sys::Object::new();
let assigned = js_sys::Reflect::set(&options, &JsValue::from_str("willReadFrequently"), &JsValue::TRUE)
.unwrap_or(false);
if !assigned {
return Err(anyhow!("environment rejects canvas context options"));
} Type guard
fn is_plain_object(value: &JsValue) -> bool {
js_sys::Object::is_extensible(value) && !js_sys::Object::is_frozen(value) && !js_sys::Object::is_sealed(value)
} Try / catch
match TextCanvas::new() {
Ok(canvas) => canvas,
Err(e) if e.to_string().contains("readback option") => {
// degraded environment: fall back to non-offscreen canvas or log and disable text caching
log::warn!("canvas options unsupported: {e}");
fallback_renderer()
}
Err(e) => return Err(e),
} Prevention
- Always build fresh js_sys::Object instances for context options; never reuse frozen objects
- Test the wasm bundle in the actual target browsers early
- Avoid proxies/polyfills around Reflect/Object in pages embedding the app
- Detect OffscreenCanvas feature support before attempting to construct the canvas
When it happens
Trigger: Calling the canvas-text constructor in gpui_web with an options object created with `Object::freeze`, or an environment/proxy (e.g. a strict security policy or monkey-patched Object) where the 'willReadFrequently' property is defined as non-writable, making `Reflect::set` return false.
Common situations: Custom wasm shim/proxy objects injected by test harnesses or browser extensions, hardened JS environments with frozen globals, or exotic embedding runtimes (non-browser JS engines) that don't fully support OffscreenCanvas option objects.
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
- Canvas text direction could not be set
- setting Canvas readback option
- Canvas returned non-finite text metrics
- Canvas text font is empty
- Canvas text raster exceeds maximum dimension
AI-assisted analysis of zed-industries/zed@916fc2b8cb (2026-09-19).
Data as JSON: /api/errors/bbf22c8586277aec.
Report an issue: GitHub.
Appendix: source
Thrown at crates/gpui_web/src/canvas_text.rs:40
struct TextCanvas {
canvas: OffscreenCanvas,
context: OffscreenCanvasRenderingContext2d,
}
impl TextCanvas {
fn new() -> Result<Self> {
let canvas = OffscreenCanvas::new(1, 1)
.map_err(|error| anyhow!("creating text OffscreenCanvas: {error:?}"))?;
let options = js_sys::Object::new();
// Every rasterized glyph is read back for upload into GPUI's atlas.
let assigned = js_sys::Reflect::set(
&options,
&JsValue::from_str("willReadFrequently"),
&JsValue::TRUE,
)
.map_err(|error| anyhow!("setting Canvas readback option: {error:?}"))?;
ensure!(assigned, "Canvas readback option could not be set");
let context = canvas
.get_context_with_context_options("2d", &options)
.map_err(|error| anyhow!("getting OffscreenCanvas 2D context: {error:?}"))?
.context("OffscreenCanvas 2D text rendering is unavailable")?
.dyn_into::<OffscreenCanvasRenderingContext2d>()
.map_err(|error| anyhow!("unexpected OffscreenCanvas 2D context: {error:?}"))?;
Ok(Self { canvas, context })
}
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");View on GitHub (pinned to 916fc2b8cb)