{"record":{"id":"485b07da1230ff3e","repo":"garrytan/gstack","slug":"2d-canvas-context-unavailable","errorCode":null,"errorMessage":"2d canvas context unavailable","messagePattern":"2d canvas context unavailable","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"lib/diagram-render/src/entry.ts","lineNumber":129,"sourceCode":"window.__rasterize = async (svgText: string, targetWidthPx: number): Promise<string> => {\n  assertTargetWidth(targetWidthPx);\n  const blob = new Blob([svgText], { type: \"image/svg+xml;charset=utf-8\" });\n  const url = URL.createObjectURL(blob);\n  try {\n    const img = new Image();\n    await new Promise<void>((resolve, reject) => {\n      img.onload = () => resolve();\n      img.onerror = () => reject(new Error(\"SVG image decode failed (malformed SVG or foreignObject content)\"));\n      img.src = url;\n    });\n    const naturalW = img.naturalWidth || 800;\n    const naturalH = img.naturalHeight || 600;\n    const scale = targetWidthPx / naturalW;\n    const canvas = document.createElement(\"canvas\");\n    canvas.width = Math.round(naturalW * scale);\n    canvas.height = Math.round(naturalH * scale);\n    const ctx = canvas.getContext(\"2d\");\n    if (!ctx) throw new Error(\"2d canvas context unavailable\");\n    ctx.fillStyle = \"#ffffff\";\n    ctx.fillRect(0, 0, canvas.width, canvas.height);\n    ctx.drawImage(img, 0, 0, canvas.width, canvas.height);\n    // Throws on tainted canvas — callers fall back to __mountForScreenshot +\n    // `browse screenshot --selector \"#raster-stage\"`.\n    return canvas.toDataURL(\"image/png\");\n  } finally {\n    URL.revokeObjectURL(url);\n  }\n};\n\n/**\n * Fallback rasterization stage: mount the SVG in the DOM so the caller can\n * take an element screenshot (no canvas, no taint rules). Returns a marker\n * string; the artifact is the screenshot, not the return value.\n */\nwindow.__mountForScreenshot = (svgText: string, targetWidthPx: number): string => {\n  document.getElementById(\"raster-stage\")?.remove();","sourceCodeStart":111,"sourceCodeEnd":147,"githubUrl":"https://github.com/garrytan/gstack/blob/94993f74012782fd94416dd44b8314f6363a13a4/lib/diagram-render/src/entry.ts#L111-L147","documentation":"Thrown by __rasterize() when canvas.getContext('2d') returns null after a successful SVG image decode. The browser refuses to hand out a 2D context when hardware-accelerated rendering is unavailable, the canvas has already been allocated a different context type, or the headless environment lacks GPU support. With no context the function cannot draw or call toDataURL, so it fails rather than returning a blank image.","triggerScenarios":"document.createElement('canvas').getContext('2d') returns null inside the headless browser tab. Typical in headless Chromium with GPU disabled (--disable-gpu) and no software rasterizer, in a sandboxed CI environment, or after a prior call assigned a webgl context to the same canvas element (not the case here since each call creates a fresh canvas).","commonSituations":"CI runner with no GPU and --disable-gpu without SwiftShader; a constrained container that blocks GPU process access; an extremely memory-constrained tab that cannot allocate the backing store; rare browser build without 2d canvas support.","solutions":["Launch the browse daemon with software rendering enabled (e.g. add --use-gl=angle --use-angle=swiftshader or --enable-unsafe-swiftshader).","Remove an unconditional --disable-gpu flag, or pair it with a software rasterizer backend.","Fall back to __mountForScreenshot + `browse screenshot --selector \"#raster-stage\"` which needs no canvas.","Verify the headless browser build bundles 2D canvas support."],"exampleFix":null,"handlingStrategy":"fallback","validationCode":null,"typeGuard":null,"tryCatchPattern":"try {\n  return await __rasterize(svgText, targetWidthPx);\n} catch (e) {\n  if (e instanceof Error && e.message === '2d canvas context unavailable') {\n    // Fallback: mount the SVG and screenshot the element instead\n    __mountForScreenshot(svgText, targetWidthPx);\n    return browseScreenshot('#raster-stage');\n  }\n  throw e;\n}","preventionTips":["Launch the browse daemon with software rasterization flags (swiftshader).","In CI, ensure the headless browser bundles 2D canvas support.","Implement a __mountForScreenshot fallback wherever __rasterize is used.","Smoke-test canvas.getContext('2d') at page load and warn if null."],"tags":["diagram-render","rasterization","canvas","browser-env","headless"],"backgroundTag":null,"analyzedSha":"94993f74012782fd94416dd44b8314f6363a13a4","analyzedAt":"2026-08-12T04:06:23.140Z","schemaVersion":2},"datasetVersion":"2026-08-12T13:17:24.610Z"}