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

  1. Ensure only '2d' is ever requested from the element matching [data-overlays-canvas]; search the codebase for other get_context calls hitting that selector.
  2. 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.
  3. Replace both expects with graceful degradation: log a warning and skip the overlay frame.
  4. 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

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


AI-assisted analysis of GraphiteEditor/Graphite@c507b35645 (2026-08-16). Data as JSON: /api/errors/9fd6bded21b0df17. Report an issue: GitHub.