GraphiteEditor/Graphite · error
Context should be a canvas 2d context
Error message
Context should be a canvas 2d context
What it means
After get_context("2d") succeeds, dyn_into::<OffscreenCanvasRenderingContext2d>() performs an unchecked-at-compile-time JS type downcast and this .expect("Context should be a canvas 2d context") panics if the returned JsValue is not actually an OffscreenCanvasRenderingContext2d. That happens when the runtime returns a different object for "2d" (a plain CanvasRenderingContext2D from a polyfill, or an incomplete OffscreenCanvas implementation). In fully compliant browsers this is effectively unreachable.
Source
Thrown at editor/src/messages/portfolio/document/overlays/utility_types_web.rs:1028
self.render_context.set_fill_style_str(color);
self.render_context.fill();
}
/// Fills the area inside the path with a pattern. Assumes `color` is an sRGB hex string.
/// Used by the fill tool to show the area to be filled.
pub fn fill_path_pattern(&mut self, subpaths: impl Iterator<Item = impl Borrow<Subpath<PointId>>>, transform: DAffine2, color: &str) {
const PATTERN_WIDTH: usize = 4;
const PATTERN_HEIGHT: usize = 4;
let pattern_canvas = OffscreenCanvas::new(PATTERN_WIDTH as u32, PATTERN_HEIGHT as u32).unwrap();
let pattern_context: OffscreenCanvasRenderingContext2d = pattern_canvas
.get_context("2d")
.ok()
.flatten()
.expect("Failed to get canvas context")
.dyn_into()
.expect("Context should be a canvas 2d context");
// 4x4 pixels, 4 components (RGBA) per pixel
let mut data = [0_u8; 4 * PATTERN_WIDTH * PATTERN_HEIGHT];
let rgba = hex_to_rgba_u8(color);
// ┌▄▄┬──┬──┬──┐
// ├▀▀┼──┼──┼──┤
// ├──┼──┼▄▄┼──┤
// ├──┼──┼▀▀┼──┤
// └──┴──┴──┴──┘
let pixels = [(0, 0), (2, 2)];
for &(x, y) in &pixels {
let index = (x + y * PATTERN_WIDTH) * 4;
data[index..index + 4].copy_from_slice(&rgba);
}
let image_data = web_sys::ImageData::new_with_u8_clamped_array_and_sh(wasm_bindgen::Clamped(&data), PATTERN_WIDTH as u32, PATTERN_HEIGHT as u32).unwrap();View on GitHub (pinned to c507b35645)
Solutions
- Use dyn_ref::<OffscreenCanvasRenderingContext2d>() to try the downcast without panicking, logging and bailing on mismatch
- Pin consistent web-sys/wasm-bindgen versions so the Rust-side type matches the shipped JS classes
- Test on the oldest supported browser matrix to catch partial OffscreenCanvas implementations
Example fix
// before
.dyn_into()
.expect("Context should be a canvas 2d context");
// after
let Ok(pattern_context) = ctx.dyn_into::<OffscreenCanvasRenderingContext2d>() else {
log::error!("2D context was not an OffscreenCanvasRenderingContext2d");
return;
}; Defensive patterns
Strategy: type-guard
Type guard
fn as_offscreen_2d(value: &wasm_bindgen::JsValue) -> bool {
value.is_instance_of::<web_sys::OffscreenCanvasRenderingContext2d>()
} Try / catch
match ctx.dyn_into::<OffscreenCanvasRenderingContext2d>() {
Ok(typed) => { /* use typed context */ }
Err(value) => log::error!("unexpected 2D context type: {value:?}"),
} Prevention
- Use dyn_ref/dyn_into as a checked downcast and handle the Err arm instead of expecting
- Keep web-sys and wasm-bindgen versions aligned with the browser APIs you target
- Smoke-test canvas paths in the oldest browser of your support matrix
When it happens
Trigger: A browser or polyfill returns a non-OffscreenCanvas 2D context object from OffscreenCanvas.getContext('2d'), causing JsCast::dyn_into to fail and the expect to panic.
Common situations: Running the web editor under legacy polyfills (core-js/ OffscreenCanvas shims), WebViews with half-implemented OffscreenCanvas, or wasm-bindgen/web-sys version mismatches that change the expected JS class.
Related errors
- Failed to get canvas context
- Failed to draw arc
- Failed to measure the text dimensions
- Failed to rotate the render context to the specified angle
- Failed to draw the text at the calculated position
AI-assisted analysis of GraphiteEditor/Graphite@c507b35645 (2026-08-16).
Data as JSON: /api/errors/beea4b510d8e74ee.
Report an issue: GitHub.