{"record":{"id":"758d468c39fa52b7","repo":"windmill-labs/windmill","slug":"canvas-2d-context-unavailable","errorCode":null,"errorMessage":"Canvas 2D context unavailable","messagePattern":"Canvas 2D context unavailable","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"frontend/src/lib/components/copilot/chat/imageUtils.ts","lineNumber":161,"sourceCode":" */\nexport async function normalizeImageDataUrl(\n\tdataUrl: string,\n\tname?: string,\n\tmaxEdge: number = MAX_IMAGE_EDGE\n): Promise<AttachedImage> {\n\tconst img = await loadImage(dataUrl)\n\tconst srcW = img.naturalWidth || img.width\n\tconst srcH = img.naturalHeight || img.height\n\tif (!srcW || !srcH) throw new Error('Image has no dimensions')\n\tif (srcW * srcH > MAX_IMAGE_PIXELS) throw new Error('Image resolution is too large')\n\tconst scale = Math.min(1, maxEdge / Math.max(srcW, srcH))\n\tconst w = Math.max(1, Math.round(srcW * scale))\n\tconst h = Math.max(1, Math.round(srcH * scale))\n\tconst canvas = document.createElement('canvas')\n\tcanvas.width = w\n\tcanvas.height = h\n\tconst ctx = canvas.getContext('2d')\n\tif (!ctx) throw new Error('Canvas 2D context unavailable')\n\tctx.drawImage(img, 0, 0, w, h)\n\treturn { ...encodeCanvas(canvas), name }\n}\n\n/** Read a user-provided image file and produce a bounded, model-ready AttachedImage. */\nexport async function fileToAttachedImage(file: File | Blob): Promise<AttachedImage> {\n\tif (file.size > MAX_IMAGE_BYTES) throw new Error('Image file is too large')\n\tconst name = file instanceof File ? file.name : undefined\n\tconst dataUrl = await blobToDataUrl(file)\n\treturn await normalizeImageDataUrl(dataUrl, name)\n}\n\n/** Split a data URL into its media type and base64 payload (for the Anthropic converter). */\nexport function parseImageDataUrl(url: string): { mediaType: string; base64: string } {\n\tconst match = /^data:([^;,]+)?(;base64)?,(.*)$/s.exec(url)\n\tif (!match) return { mediaType: 'image/png', base64: '' }\n\treturn { mediaType: match[1] || 'image/png', base64: match[2] ? match[3] : '' }\n}","sourceCodeStart":143,"sourceCodeEnd":179,"githubUrl":"https://github.com/windmill-labs/windmill/blob/e474e8803ce2ff5c2df09a58dab51d45f5c922ca/frontend/src/lib/components/copilot/chat/imageUtils.ts#L143-L179","documentation":"After sizing an offscreen canvas, normalizeImageDataUrl() calls canvas.getContext('2d'); the Canvas API is allowed to return null (e.g. when the context type is unsupported or canvas creation is blocked by browser policy/hardware limits). Since drawing is impossible without the context, the adapter throws this error rather than crashing later inside drawImage with a confusing null dereference.","triggerScenarios":"canvas.getContext('2d') returns null — browsers with canvas disabled via policy (e.g. some hardened enterprise setups), devices where GPU-backed canvas allocation fails, or non-browser environments (SSR/tests without a DOM canvas implementation).","commonSituations":"Running chat attachment code in an SSR or Node test environment where 'canvas' isn't implemented; corporate browser policies disabling 2D canvas (fingerprinting protection); memory exhaustion preventing canvas backing-store allocation.","solutions":["Run the attachment flow in a real browser context (the chat UI), not SSR/Node tests.","Check browser settings/extensions/policies that disable canvas (fingerprint blockers like CanvasBlocker).","Free memory / reduce other canvas usage if allocation is failing, then retry.","Catch the error and offer a fallback that skips normalization (send the original data URL if within limits) or show a friendly message."],"exampleFix":"// before\nconst img = await fileToAttachedImage(file) // in vitest/jsdom\n// after\nif (typeof document === 'undefined' || !document.createElement('canvas').getContext('2d')) {\n  throw new Error('Image normalization requires a browser canvas')\n}\nconst img = await fileToAttachedImage(file)","handlingStrategy":"fallback","validationCode":"function canvas2dAvailable(): boolean {\n  try { return !!document.createElement('canvas').getContext('2d') } catch { return false }\n}\nif (!canvas2dAvailable()) throw new Error('Canvas unavailable in this environment')","typeGuard":"function has2dContext(c: HTMLCanvasElement): c is HTMLCanvasElement & { getContext(t: '2d'): CanvasRenderingContext2D } {\n  return c.getContext('2d') !== null\n}","tryCatchPattern":"try {\n  const attached = await fileToAttachedImage(file)\n} catch (e) {\n  if (e instanceof Error && e.message === 'Canvas 2D context unavailable') {\n    notify('Your browser/environment cannot process images (canvas disabled)')\n  } else throw e\n}","preventionTips":["Only run image normalization in browser contexts, never SSR/Node tests","Check for corporate policies or extensions blocking canvas fingerprinting","Feature-detect canvas support before enabling image attachments in the UI"],"tags":["browser","canvas","environment"],"backgroundTag":"canvas-context-unavailable","analyzedSha":"e474e8803ce2ff5c2df09a58dab51d45f5c922ca","analyzedAt":"2026-09-03T12:38:19.024Z","contentChangedAt":"2026-09-03T12:38:19.024Z","schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}