{"record":{"id":"de16bb3411d518c2","repo":"danny-avila/LibreChat","slug":"canvas-is-unavailable-for-mermaid-png-export","errorCode":null,"errorMessage":"Canvas is unavailable for Mermaid PNG export","messagePattern":"Canvas is unavailable for Mermaid PNG export","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"client/src/utils/diagram/export.ts","lineNumber":229,"sourceCode":"  triggerDownload(url, exportFilename(filename, 'svg'));\n}\n\nexport async function downloadMermaidPng(\n  svg: string,\n  filename: string,\n  dimensions?: MermaidDimensions | null,\n  background?: string,\n): Promise<void> {\n  const sourceUrl = await blobDataUrl(svgBlob(svg));\n  const image = await loadSvgImage(sourceUrl);\n  const sourceWidth = dimensions?.width || image.naturalWidth || image.width;\n  const sourceHeight = dimensions?.height || image.naturalHeight || image.height;\n  const canvasDimensions = resolveCanvasDimensions(sourceWidth, sourceHeight);\n  const canvas = document.createElement('canvas');\n  const context = canvas.getContext('2d');\n\n  if (!context) {\n    throw new Error('Canvas is unavailable for Mermaid PNG export');\n  }\n\n  canvas.width = canvasDimensions.width;\n  canvas.height = canvasDimensions.height;\n  context.imageSmoothingEnabled = true;\n  context.imageSmoothingQuality = 'high';\n  if (background) {\n    context.fillStyle = background;\n    context.fillRect(0, 0, canvas.width, canvas.height);\n  }\n  context.drawImage(image, 0, 0, canvas.width, canvas.height);\n\n  const png = await encodePng(canvas);\n  const outputUrl = URL.createObjectURL(png);\n  triggerDownload(outputUrl, exportFilename(filename, 'png'));\n}\n","sourceCodeStart":211,"sourceCodeEnd":246,"githubUrl":"https://github.com/danny-avila/LibreChat/blob/5ff282f9006c436e561de1afd39a481bea1ef0d8/client/src/utils/diagram/export.ts#L211-L246","documentation":"Thrown by the PNG export path when `document.createElement('canvas').getContext('2d')` returns null. A null 2D context means the browser either does not expose the Canvas 2D API or has exhausted its per-page context limit (browsers cap simultaneous WebGL/2D contexts; exceeding it returns null for new ones). This is rare in modern browsers but real in constrained/headless environments.","triggerScenarios":"Running the export in a headless test runner (jsdom) that does not implement `canvas.getContext('2d')` without the `canvas` npm package; a page that leaked many canvas contexts and hit the browser limit; an embedded WebView/older browser without 2D canvas; a hardening extension that blocks canvas (fingerprinting protection returning null).","commonSituations":"Unit/integration tests under jsdom without canvas polyfill; privacy browsers/extensions (Brave, Tor, fingerprinting shields) that neuter canvas; long-lived SPA that created hundreds of canvases; embedded WebView in a native app with limited canvas support.","solutions":["In tests, install/configure the `canvas` package for jsdom, or mock `downloadMermaidPng`.","In production, free unused canvas contexts (set width/height to 0 and let them be GC'd) to avoid hitting the per-page context cap.","Detect the null context and fall back to SVG export (or a server-side rasterization) instead of failing.","If a fingerprinting shield is the cause, document that PNG export requires canvas and offer SVG download as an alternative."],"exampleFix":"// before\nconst canvas = document.createElement('canvas');\nconst context = canvas.getContext('2d');\nif (!context) {\n  throw new Error('Canvas is unavailable for Mermaid PNG export');\n}\n// after — fall back to SVG download when a 2D context cannot be obtained\nconst context = canvas.getContext('2d');\nif (!context) {\n  logger.warn('Canvas 2D unavailable; falling back to SVG export');\n  return downloadMermaidSvg(svg, filename);\n}","handlingStrategy":"fallback","validationCode":"// Probe canvas 2D support before offering PNG export\nfunction canvas2dAvailable(): boolean {\n  try {\n    return !!document.createElement('canvas').getContext('2d');\n  } catch { return false; }\n}","typeGuard":"function hasCanvas2D(): boolean {\n  if (typeof document === 'undefined') return false;\n  const c = document.createElement('canvas');\n  return c.getContext('2d') != null;\n}","tryCatchPattern":"const context = canvas.getContext('2d');\nif (!context) {\n  // Fall back to SVG download instead of failing the export entirely\n  return downloadMermaidSvg(svg, filename);\n}","preventionTips":["Free unused canvas contexts (resize to 0) to avoid hitting the per-page context cap.","Offer SVG download as a fallback when canvas is unavailable (privacy browsers, jsdom).","In jsdom tests, install/configure the `canvas` package or mock `downloadMermaidPng`."],"tags":["mermaid","canvas","export","browser-compat","test-environment"],"backgroundTag":null,"analyzedSha":"5ff282f9006c436e561de1afd39a481bea1ef0d8","analyzedAt":"2026-08-12T21:38:08.145Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}