{"record":{"id":"bbf22c8586277aec","repo":"zed-industries/zed","slug":"canvas-readback-option-could-not-be-set","errorCode":null,"errorMessage":"Canvas readback option could not be set","messagePattern":"Canvas readback option could not be set","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/gpui_web/src/canvas_text.rs","lineNumber":40,"sourceCode":"\nstruct TextCanvas {\n    canvas: OffscreenCanvas,\n    context: OffscreenCanvasRenderingContext2d,\n}\n\nimpl TextCanvas {\n    fn new() -> Result<Self> {\n        let canvas = OffscreenCanvas::new(1, 1)\n            .map_err(|error| anyhow!(\"creating text OffscreenCanvas: {error:?}\"))?;\n        let options = js_sys::Object::new();\n        // Every rasterized glyph is read back for upload into GPUI's atlas.\n        let assigned = js_sys::Reflect::set(\n            &options,\n            &JsValue::from_str(\"willReadFrequently\"),\n            &JsValue::TRUE,\n        )\n        .map_err(|error| anyhow!(\"setting Canvas readback option: {error:?}\"))?;\n        ensure!(assigned, \"Canvas readback option could not be set\");\n        let context = canvas\n            .get_context_with_context_options(\"2d\", &options)\n            .map_err(|error| anyhow!(\"getting OffscreenCanvas 2D context: {error:?}\"))?\n            .context(\"OffscreenCanvas 2D text rendering is unavailable\")?\n            .dyn_into::<OffscreenCanvasRenderingContext2d>()\n            .map_err(|error| anyhow!(\"unexpected OffscreenCanvas 2D context: {error:?}\"))?;\n        Ok(Self { canvas, context })\n    }\n\n    fn configure(&self, css_font: &str) -> Result<()> {\n        ensure!(!css_font.trim().is_empty(), \"Canvas text font is empty\");\n        // An invalid CSS font assignment is ignored by Canvas. Reset first so\n        // it cannot accidentally inherit the preceding request's font.\n        self.context.set_font(\"10px sans-serif\");\n        self.context.set_font(css_font);\n        self.context.set_text_align(\"left\");\n        self.context.set_text_baseline(\"alphabetic\");\n        self.context.set_fill_style_str(\"white\");","sourceCodeStart":22,"sourceCodeEnd":58,"githubUrl":"https://github.com/zed-industries/zed/blob/916fc2b8cb3a815cbef4a3b40e13081be72036b6/crates/gpui_web/src/canvas_text.rs#L22-L58","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before\nlet assigned = js_sys::Reflect::set(&options, &JsValue::from_str(\"willReadFrequently\"), &JsValue::TRUE)\n    .map_err(|error| anyhow!(\"setting Canvas readback option: {error:?}\"))?;\nensure!(assigned, \"Canvas readback option could not be set\");\n// after\nlet options = js_sys::Object::new(); // fresh, extensible object\njs_sys::Reflect::set(&options, &JsValue::from_str(\"willReadFrequently\"), &JsValue::TRUE)\n    .map_err(|error| anyhow!(\"setting Canvas readback option: {error:?}\"))?\n    .then_some(())\n    .ok_or_else(|| anyhow!(\"Canvas readback option could not be set\"))?;","handlingStrategy":"try-catch","validationCode":"// guard before constructing\nlet options = js_sys::Object::new();\nlet assigned = js_sys::Reflect::set(&options, &JsValue::from_str(\"willReadFrequently\"), &JsValue::TRUE)\n    .unwrap_or(false);\nif !assigned {\n    return Err(anyhow!(\"environment rejects canvas context options\"));\n}","typeGuard":"fn is_plain_object(value: &JsValue) -> bool {\n    js_sys::Object::is_extensible(value) && !js_sys::Object::is_frozen(value) && !js_sys::Object::is_sealed(value)\n}","tryCatchPattern":"match TextCanvas::new() {\n    Ok(canvas) => canvas,\n    Err(e) if e.to_string().contains(\"readback option\") => {\n        // degraded environment: fall back to non-offscreen canvas or log and disable text caching\n        log::warn!(\"canvas options unsupported: {e}\");\n        fallback_renderer()\n    }\n    Err(e) => return Err(e),\n}","preventionTips":["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"],"tags":["wasm","webgpu","canvas","javascript"],"backgroundTag":"internal-invariant-violation","analyzedSha":"916fc2b8cb3a815cbef4a3b40e13081be72036b6","analyzedAt":"2026-09-19T19:09:50.599Z","contentChangedAt":"2026-09-19T19:09:50.599Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}