{"record":{"id":"2523dfa106c48b8f","repo":"pbakaus/impeccable","slug":"ancestor-crop-failed-to-produce-a-png-blob","errorCode":null,"errorMessage":"Ancestor crop failed to produce a PNG blob","messagePattern":"Ancestor crop failed to produce a PNG blob","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"skill/scripts/live-browser.js","lineNumber":8156,"sourceCode":"    const captureRoot = findShaderProxyCaptureRoot(el);\n    if (!captureRoot) throw new Error('No painted ancestor for Svelte shader proxy');\n    const rootCanvas = await ms.domToCanvas(captureRoot, opts);\n    const S = opts.scale;\n    const er = el.getBoundingClientRect();\n    const rr = captureRoot.getBoundingClientRect();\n    const sx = (er.left - rr.left) * S;\n    const sy = (er.top - rr.top) * S;\n    const sw = er.width * S;\n    const sh = er.height * S;\n    if (sw <= 0 || sh <= 0) throw new Error('Selected element has no visible capture rect');\n    const crop = doc.createElement('canvas');\n    crop.width = Math.max(1, Math.round(sw));\n    crop.height = Math.max(1, Math.round(sh));\n    const cctx = crop.getContext('2d', { willReadFrequently: true });\n    cctx.drawImage(rootCanvas, sx, sy, sw, sh, 0, 0, crop.width, crop.height);\n    const paper = dominantRgb01(cctx, crop.width, crop.height) || averageRgb01(cctx, crop.width, crop.height);\n    const blob = await new Promise((res) => crop.toBlob(res, 'image/png'));\n    if (!blob) throw new Error('Ancestor crop failed to produce a PNG blob');\n    return { blob, paper };\n  }\n\n  async function captureElementToBlob(el, snapshot, rect) {\n    try { if (document.fonts?.ready) await document.fonts.ready; } catch {}\n    const hasAnnotations = snapshot && (snapshot.comments.length > 0 || snapshot.strokes.length > 0);\n    let annotNode = null;\n    let savedPosition = null;\n    if (hasAnnotations) {\n      const pos = getComputedStyle(el).position;\n      if (pos === 'static') {\n        savedPosition = el.style.position;\n        el.style.position = 'relative';\n      }\n      annotNode = buildAnnotationsForCapture(rect, snapshot);\n      el.appendChild(annotNode);\n    }\n    try {","sourceCodeStart":8138,"sourceCodeEnd":8174,"githubUrl":"https://github.com/pbakaus/impeccable/blob/2bc2879276c1f321a53c4ca99d3371e411329b52/skill/scripts/live-browser.js#L8138-L8174","documentation":"After drawing the ancestor crop onto an offscreen canvas, the script converts it to a PNG via crop.toBlob and throws if the resulting blob is null. HTMLCanvasElement.toBlob yields null when the canvas is tainted (cross-origin content drawn without CORS), the canvas has zero size, or the browser fails the encode.","triggerScenarios":"crop.toBlob('image/png') calls back with null — commonly because rootCanvas was produced from a capture root containing cross-origin images/fonts that tainted the canvas, so drawImage propagated a taint and toBlob cannot export.","commonSituations":"Page embeds third-party images without crossorigin attributes; fonts/images from another origin without CORS headers; screenshotting a region containing an iframe with foreign content.","solutions":["Serve cross-origin images with CORS headers and load them with crossorigin=\"anonymous\" so the canvas stays untainted","Proxy remote assets through the local dev server (same origin) before capture","Confirm the crop canvas has non-zero dimensions (zero-area canvases can yield null blobs)","Retry the capture; if it persists, fall back to capturing a smaller same-origin region"],"exampleFix":"// before\nconst blob = await new Promise((res) => crop.toBlob(res, 'image/png'));\nif (!blob) throw new Error('Ancestor crop failed to produce a PNG blob');\n// after\nconst blob = await new Promise((res, rej) => crop.toBlob(b => b ? res(b) : rej(new Error('toBlob returned null (tainted or zero-size canvas?)')), 'image/png'));","handlingStrategy":"fallback","validationCode":"// detect taint before exporting\ntry { cctx.getImageData(0, 0, 1, 1); } catch { console.warn('canvas tainted; PNG export will fail'); }","typeGuard":null,"tryCatchPattern":"const blob = await new Promise((res) => crop.toBlob(res, 'image/png'));\nif (!blob) {\n  console.warn('PNG export failed — canvas likely tainted; retrying with same-origin assets');\n  return null; // fall back to no-background mode\n}","preventionTips":["Load all images/fonts with crossorigin=\"anonymous\" and serve them with CORS headers","Proxy third-party assets through the same origin before rendering pages you'll capture","Verify crop canvas dimensions are non-zero before toBlob","Wrap toBlob in a promise that rejects on null so failures are diagnosable"],"tags":["canvas","tainted-canvas","blob","cors"],"backgroundTag":"tainted-canvas-export","analyzedSha":"2bc2879276c1f321a53c4ca99d3371e411329b52","analyzedAt":"2026-09-08T04:51:14.109Z","contentChangedAt":"2026-09-08T04:51:14.109Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}