GraphiteEditor/Graphite · error
Failed to get canvas context
Error message
Failed to get canvas context
What it means
The overlay renderer caches the [data-overlays-canvas] DOM element and lazily creates its 2D context with canvas.get_context("2d"). The browser returns null when that canvas element has already handed out a different context type (a canvas produces only one context type for its lifetime) or when 2D context creation fails; .ok().flatten().expect("Failed to get canvas context") turns the null into a panic that aborts the overlay frame. The chained dyn_into().expect fires if the returned object is not a CanvasRenderingContext2d.
Source
Thrown at editor/src/messages/portfolio/document/overlays/overlays_message_handler.rs:47
use crate::messages::viewport::{Position, ToPhysical};
use wasm_bindgen::JsCast;
// Discard detached canvas after a panel reorganization remounts the DOM
if self.canvas.as_ref().is_some_and(|canvas| !canvas.is_connected()) {
self.canvas = None;
self.context = None;
}
let canvas = match &self.canvas {
Some(canvas) => canvas,
None => {
let Some(new_canvas) = overlay_canvas_element() else { return };
self.canvas.get_or_insert(new_canvas)
}
};
let canvas_context = self.context.get_or_insert_with(|| {
let canvas_context = canvas.get_context("2d").ok().flatten().expect("Failed to get canvas context");
canvas_context.dyn_into().expect("Context should be a canvas 2d context")
});
let size_logical = viewport.size();
let size_physical = size_logical.to_physical();
let width = size_logical.x().max(size_physical.x());
let height = size_logical.y().max(size_physical.y());
canvas_context.clear_rect(0., 0., width, height);
if visibility_settings.all() {
responses.add(DocumentMessage::GridOverlays {
context: OverlayContext {
render_context: canvas_context.clone(),
visibility_settings: visibility_settings.clone(),
viewport: *viewport,
},
});View on GitHub (pinned to c507b35645)
Solutions
- Ensure only '2d' is ever requested from the element matching [data-overlays-canvas]; search the codebase for other get_context calls hitting that selector.
- Invalidate self.canvas and self.context caches when the canvas element is recreated or context creation fails, so a fresh element is re-queried next frame.
- Replace both expects with graceful degradation: log a warning and skip the overlay frame.
- Verify exactly one element with [data-overlays-canvas] exists; duplicates can return a canvas another system already claimed.
Example fix
// before
let canvas_context = self.context.get_or_insert_with(|| {
let canvas_context = canvas.get_context("2d").ok().flatten().expect("Failed to get canvas context");
canvas_context.dyn_into().expect("Context should be a canvas 2d context")
});
// after
let context = self.context.get_or_insert_with(|| {
canvas
.get_context("2d")
.ok()
.flatten()
.and_then(|ctx| ctx.dyn_into::<web_sys::CanvasRenderingContext2d>().ok())
});
let Some(canvas_context) = context else {
log::warn!("Overlay canvas 2D context unavailable; skipping overlay frame");
return;
}; Defensive patterns
Strategy: fallback
Validate before calling
// DOM/TS side, before the Rust overlay path runs each frame
const canvas = document.querySelector<HTMLCanvasElement>('[data-overlays-canvas]');
if (!canvas) return;
const ctx = canvas.getContext('2d');
if (!ctx) { console.warn('overlays: 2D context unavailable'); return; } Type guard
fn overlay_canvas_has_2d_context() -> bool {
overlay_canvas_element()
.and_then(|c| c.get_context("2d").ok().flatten())
.is_some()
} Try / catch
let context = canvas
.get_context("2d")
.ok()
.flatten()
.and_then(|ctx| ctx.dyn_into::<web_sys::CanvasRenderingContext2d>().ok());
let Some(canvas_context) = context else {
log::warn!("overlay 2D context unavailable; skipping overlay frame");
return;
}; Prevention
- Never request a second context type on the overlays canvas — a canvas returns only one context type for its lifetime
- Invalidate cached canvas/context handles when the DOM canvas is recreated
- Audit embedders and integrations so nothing claims [data-overlays-canvas] for WebGL/WebGPU
When it happens
Trigger: Any code path that earlier called getContext('webgl') or getContext('webgpu') on the same overlays canvas element; a browser refusing to allocate another 2D context (too many live canvases, GPU reset, memory pressure); or the cached element being stale because the DOM canvas was replaced.
Common situations: WASM builds where another subsystem (WebGL preview, custom integration, embedding host) claimed the overlay canvas; hosts that recreate canvases during layout; low-end devices hitting context limits.
Related errors
- Failed to get canvas context
- Failed to draw ellipse
- Failed to transform circle
- Failed to draw the circle
- transform should be able to be set to be able to account for
AI-assisted analysis of GraphiteEditor/Graphite@c507b35645 (2026-08-16).
Data as JSON: /api/errors/9fd6bded21b0df17.
Report an issue: GitHub.