{"record":{"id":"2edd7818d5d50deb","repo":"heygen-com/hyperframes","slug":"drawelement-todataurl-returned-no-base64-payload","errorCode":null,"errorMessage":"drawElement: toDataURL returned no base64 payload","messagePattern":"drawElement: toDataURL returned no base64 payload","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/engine/src/services/drawElementService.ts","lineNumber":542,"sourceCode":"        // seek produced no paint-level change (static scene, or transform-only\n        // GSAP updates that are compositor-side and never repaint). Sentinel\n        // dirty + requestPaint(), installed by injectDrawElementCanvas — see\n        // __hfDeInvalidate there for the full mechanism/rationale.\n        usedRequestPaint = aw.__hfDeInvalidate?.() === true;\n        // Safety net: if the paint event doesn't arrive (feature drift /\n        // throttled page), fall back to an unsynchronized draw after 250 ms —\n        // worst case one-frame-stale content (the root's alpha may lag its\n        // transform by that frame) rather than a hung render.\n        setTimeout(() => {\n          canvas.removeEventListener(\"paint\", onPaint);\n          drawAndEncode();\n        }, 250);\n      });\n    },\n    { w: width, h: height, fmt: format, q: quality, sync: syncToPaintEvent },\n  );\n  const base64 = dataUrl.split(\",\")[1];\n  if (!base64) throw new Error(\"drawElement: toDataURL returned no base64 payload\");\n  return Buffer.from(base64, \"base64\");\n}\n\n// ── Worker-encode pipeline ────────────────────────────────────────────────────\n//\n// Architecture: an in-page OffscreenCanvas Worker encodes JPEG frames off the\n// main thread. The main thread does seek+paint+drawElement+createImageBitmap\n// (the \"produce\" phase) and immediately transfers the bitmap to the worker.\n// The worker encodes it concurrently while the main thread processes the next\n// frame — hiding ~7.4ms of encode cost behind ~8.4ms of produce work.\n//\n// The worker posts the encoded bytes back by calling window.__hfFrameReady\n// (a Puppeteer exposeFunction binding that calls a node-side callback).\n// Node resolves the per-frame Promise from that callback.\n\ninterface WorkerEncodeEntry {\n  resolve: (buf: Buffer) => void;\n  reject: (err: Error) => void;","sourceCodeStart":524,"sourceCodeEnd":560,"githubUrl":"https://github.com/heygen-com/hyperframes/blob/c2996c8626135db5253519359d8a063d3bafad8d/packages/engine/src/services/drawElementService.ts#L524-L560","documentation":"Thrown when canvas.toDataURL() returns a string with no comma-separated base64 payload. A valid data URL looks like 'data:image/png;base64,<data>'; splitting on ',' yields [mime, base64]. If the string is empty, 'data:', or has no comma, index 1 is undefined and the error fires. This typically indicates a tainted canvas (cross-origin content) or an empty/corrupted canvas.","triggerScenarios":"The drawElement capture evaluate calls canvas.toDataURL('image/png') (or jpeg), returns the dataUrl string to Node, and the code does dataUrl.split(',')[1]. If toDataURL returned '' or 'data:' (which happens for a tainted or zero-size canvas), base64 is undefined.","commonSituations":"The canvas drew a cross-origin image without CORS headers, tainting it — toDataURL returns 'data:' with no payload. The canvas dimensions are zero. The canvas was cleared or never drawn to. A browser security policy blocks data URL extraction.","solutions":["Ensure all images drawn to the canvas have crossorigin='anonymous' and proper CORS headers.","Verify canvas.width and canvas.height are positive before toDataURL.","Check the dataUrl string in the error path: log it to distinguish '' (tainted) from 'data:' (empty).","If using file:// origin (as the FX host does), ensure assets are local to avoid cross-origin taint."],"exampleFix":null,"handlingStrategy":"validation","validationCode":"// verify canvas is not tainted before capture\nconst notTainted = await page.evaluate(() => {\n  const canvas = document.getElementById('__hf_de_canvas') as HTMLCanvasElement;\n  if (!canvas) return false;\n  try {\n    const url = canvas.toDataURL('image/png');\n    return url.includes(',') && url.split(',')[1].length > 0;\n  } catch {\n    return false; // SecurityError = tainted\n  }\n});\nif (!notTainted) {\n  throw new Error('drawElement canvas is tainted or empty');\n}","typeGuard":null,"tryCatchPattern":"try {\n  const buf = await captureDrawElement(page, opts);\n} catch (err) {\n  if (err instanceof Error && err.message.includes('toDataURL returned no base64')) {\n    // check for cross-origin images, CORS headers, tainted canvas\n  }\n  throw err;\n}","preventionTips":["Set crossorigin='anonymous' on all images loaded into the composition.","Ensure image servers return Access-Control-Allow-Origin headers.","Use file:// or same-origin assets to avoid canvas taint.","Verify canvas dimensions are positive before capture."],"tags":["drawelement","canvas","todataurl","cors","security","browser"],"backgroundTag":null,"analyzedSha":"c2996c8626135db5253519359d8a063d3bafad8d","analyzedAt":"2026-08-12T22:18:56.877Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}