{"record":{"id":"7d72028798633e79","repo":"GraphiteEditor/Graphite","slug":"failed-to-get-canvas-context-7d7202","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/utility_types_web.rs","lineNumber":1026,"sourceCode":"\tpub fn fill_path(&mut self, subpaths: impl Iterator<Item = impl Borrow<Subpath<PointId>>>, transform: DAffine2, color: &str) {\n\t\tself.push_path(subpaths, transform);\n\n\t\tself.render_context.set_fill_style_str(color);\n\t\tself.render_context.fill();\n\t}\n\n\t/// Fills the area inside the path with a pattern. Assumes `color` is an sRGB hex string.\n\t/// Used by the fill tool to show the area to be filled.\n\tpub fn fill_path_pattern(&mut self, subpaths: impl Iterator<Item = impl Borrow<Subpath<PointId>>>, transform: DAffine2, color: &str) {\n\t\tconst PATTERN_WIDTH: usize = 4;\n\t\tconst PATTERN_HEIGHT: usize = 4;\n\n\t\tlet pattern_canvas = OffscreenCanvas::new(PATTERN_WIDTH as u32, PATTERN_HEIGHT as u32).unwrap();\n\t\tlet pattern_context: OffscreenCanvasRenderingContext2d = pattern_canvas\n\t\t\t.get_context(\"2d\")\n\t\t\t.ok()\n\t\t\t.flatten()\n\t\t\t.expect(\"Failed to get canvas context\")\n\t\t\t.dyn_into()\n\t\t\t.expect(\"Context should be a canvas 2d context\");\n\n\t\t// 4x4 pixels, 4 components (RGBA) per pixel\n\t\tlet mut data = [0_u8; 4 * PATTERN_WIDTH * PATTERN_HEIGHT];\n\n\t\tlet rgba = hex_to_rgba_u8(color);\n\n\t\t// ┌▄▄┬──┬──┬──┐\n\t\t// ├▀▀┼──┼──┼──┤\n\t\t// ├──┼──┼▄▄┼──┤\n\t\t// ├──┼──┼▀▀┼──┤\n\t\t// └──┴──┴──┴──┘\n\t\tlet pixels = [(0, 0), (2, 2)];\n\t\tfor &(x, y) in &pixels {\n\t\t\tlet index = (x + y * PATTERN_WIDTH) * 4;\n\t\t\tdata[index..index + 4].copy_from_slice(&rgba);\n\t\t}","sourceCodeStart":1008,"sourceCodeEnd":1044,"githubUrl":"https://github.com/GraphiteEditor/Graphite/blob/c507b356453361e31638b8bff8f6d46b6da2961e/editor/src/messages/portfolio/document/overlays/utility_types_web.rs#L1008-L1044","documentation":"fill_path_pattern creates a fresh 4x4 OffscreenCanvas per call and does get_context(\"2d\").ok().flatten().expect(\"Failed to get canvas context\"). get_context returns Err if the call throws and None if a 2D context cannot be created (context limits, memory pressure, or the runtime not fully implementing OffscreenCanvas 2D). This expect panics in WASM whenever context creation fails, which is most likely on browsers with partial OffscreenCanvas support or under resource exhaustion because a new canvas is allocated on every fill-tool hover.","triggerScenarios":"Invoking the fill tool overlay (fill_path_pattern) on a browser where OffscreenCanvas exists but get_context(\"2d\") returns null (Safari < 16.4-era WebKit, some WebViews), or after allocating so many short-lived OffscreenCanvases that the browser refuses another context.","commonSituations":"Older Safari/iOS WebViews with incomplete OffscreenCanvas; headless test environments; memory-constrained devices where per-call canvas creation exhausts the context budget.","solutions":["Cache one shared 4x4 pattern OffscreenCanvas (and only rewrite its pixels when the color changes) instead of allocating per call","Feature-detect OffscreenCanvas 2D once at startup and fall back to a document.createElement('canvas') path when unavailable","Handle the None case with a log and a solid-color fill instead of expect, so the fill preview degrades instead of aborting"],"exampleFix":"// before\nlet pattern_context: OffscreenCanvasRenderingContext2d = pattern_canvas\n\t.get_context(\"2d\")\n\t.ok()\n\t.flatten()\n\t.expect(\"Failed to get canvas context\")\n\t.dyn_into()\n\t.expect(\"Context should be a canvas 2d context\");\n\n// after\nlet Some(ctx) = pattern_canvas.get_context(\"2d\").ok().flatten() else {\n\tlog::error!(\"Failed to get 2D context for fill pattern\");\n\treturn;\n};\nlet pattern_context: OffscreenCanvasRenderingContext2d = ctx.dyn_into().expect(\"Context should be a canvas 2d context\");","handlingStrategy":"validation","validationCode":"// feature-detect once, and cache the pattern canvas instead of allocating per call\nuse wasm_bindgen::JsValue;\nif !js_sys::Reflect::get(&wasm_bindgen::global(), &\"OffscreenCanvas\".into()).map(|v| !v.is_undefined()).unwrap_or(false) {\n\t// fall back to solid fill; do not call fill_path_pattern\n}","typeGuard":null,"tryCatchPattern":"match pattern_canvas.get_context(\"2d\") {\n\tOk(Some(ctx)) => { /* use ctx */ }\n\tOk(None) => log::error!(\"2D context unavailable for fill pattern\"),\n\tErr(e) => log::error!(\"get_context threw: {e:?}\"),\n}","preventionTips":["Cache a single small OffscreenCanvas for patterns rather than creating one per fill-tool hover","Feature-detect OffscreenCanvas 2D support at startup and disable/fall back the patterned preview","Free pattern contexts (set to None) when the fill tool is deactivated to avoid context exhaustion"],"tags":["rust","wasm-bindgen","offscreencanvas","canvas-2d","resource-exhaustion"],"backgroundTag":"canvas-context-creation-failed","analyzedSha":"c507b356453361e31638b8bff8f6d46b6da2961e","analyzedAt":"2026-08-16T21:57:18.596Z","schemaVersion":2},"datasetVersion":"2026-08-16T23:17:17.608Z"}