{"record":{"id":"9fd6bded21b0df17","repo":"GraphiteEditor/Graphite","slug":"failed-to-get-canvas-context","errorCode":null,"errorMessage":"Failed to get canvas context","messagePattern":"Failed to get canvas context","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"editor/src/messages/portfolio/document/overlays/overlays_message_handler.rs","lineNumber":47,"sourceCode":"\t\t\t\tuse crate::messages::viewport::{Position, ToPhysical};\n\t\t\t\tuse wasm_bindgen::JsCast;\n\n\t\t\t\t// Discard detached canvas after a panel reorganization remounts the DOM\n\t\t\t\tif self.canvas.as_ref().is_some_and(|canvas| !canvas.is_connected()) {\n\t\t\t\t\tself.canvas = None;\n\t\t\t\t\tself.context = None;\n\t\t\t\t}\n\n\t\t\t\tlet canvas = match &self.canvas {\n\t\t\t\t\tSome(canvas) => canvas,\n\t\t\t\t\tNone => {\n\t\t\t\t\t\tlet Some(new_canvas) = overlay_canvas_element() else { return };\n\t\t\t\t\t\tself.canvas.get_or_insert(new_canvas)\n\t\t\t\t\t}\n\t\t\t\t};\n\n\t\t\t\tlet canvas_context = self.context.get_or_insert_with(|| {\n\t\t\t\t\tlet canvas_context = canvas.get_context(\"2d\").ok().flatten().expect(\"Failed to get canvas context\");\n\t\t\t\t\tcanvas_context.dyn_into().expect(\"Context should be a canvas 2d context\")\n\t\t\t\t});\n\n\t\t\t\tlet size_logical = viewport.size();\n\t\t\t\tlet size_physical = size_logical.to_physical();\n\t\t\t\tlet width = size_logical.x().max(size_physical.x());\n\t\t\t\tlet height = size_logical.y().max(size_physical.y());\n\n\t\t\t\tcanvas_context.clear_rect(0., 0., width, height);\n\n\t\t\t\tif visibility_settings.all() {\n\t\t\t\t\tresponses.add(DocumentMessage::GridOverlays {\n\t\t\t\t\t\tcontext: OverlayContext {\n\t\t\t\t\t\t\trender_context: canvas_context.clone(),\n\t\t\t\t\t\t\tvisibility_settings: visibility_settings.clone(),\n\t\t\t\t\t\t\tviewport: *viewport,\n\t\t\t\t\t\t},\n\t\t\t\t\t});","sourceCodeStart":29,"sourceCodeEnd":65,"githubUrl":"https://github.com/GraphiteEditor/Graphite/blob/c507b356453361e31638b8bff8f6d46b6da2961e/editor/src/messages/portfolio/document/overlays/overlays_message_handler.rs#L29-L65","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\nlet canvas_context = self.context.get_or_insert_with(|| {\n\tlet canvas_context = canvas.get_context(\"2d\").ok().flatten().expect(\"Failed to get canvas context\");\n\tcanvas_context.dyn_into().expect(\"Context should be a canvas 2d context\")\n});\n\n// after\nlet context = self.context.get_or_insert_with(|| {\n\tcanvas\n\t\t.get_context(\"2d\")\n\t\t.ok()\n\t\t.flatten()\n\t\t.and_then(|ctx| ctx.dyn_into::<web_sys::CanvasRenderingContext2d>().ok())\n});\nlet Some(canvas_context) = context else {\n\tlog::warn!(\"Overlay canvas 2D context unavailable; skipping overlay frame\");\n\treturn;\n};","handlingStrategy":"fallback","validationCode":"// DOM/TS side, before the Rust overlay path runs each frame\nconst canvas = document.querySelector<HTMLCanvasElement>('[data-overlays-canvas]');\nif (!canvas) return;\nconst ctx = canvas.getContext('2d');\nif (!ctx) { console.warn('overlays: 2D context unavailable'); return; }","typeGuard":"fn overlay_canvas_has_2d_context() -> bool {\n\toverlay_canvas_element()\n\t\t.and_then(|c| c.get_context(\"2d\").ok().flatten())\n\t\t.is_some()\n}","tryCatchPattern":"let context = canvas\n\t.get_context(\"2d\")\n\t.ok()\n\t.flatten()\n\t.and_then(|ctx| ctx.dyn_into::<web_sys::CanvasRenderingContext2d>().ok());\nlet Some(canvas_context) = context else {\n\tlog::warn!(\"overlay 2D context unavailable; skipping overlay frame\");\n\treturn;\n};","preventionTips":["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"],"tags":["wasm","canvas-2d","dom","overlay","panic"],"backgroundTag":"canvas-2d-context-null","analyzedSha":"c507b356453361e31638b8bff8f6d46b6da2961e","analyzedAt":"2026-08-16T21:57:18.596Z","schemaVersion":2},"datasetVersion":"2026-08-16T23:17:17.608Z"}