v156 · canvas tainting
HTML into a canvas
This is the thing the change is for. Write HTML and CSS, wrap them in a <foreignObject>, draw the result to a canvas, and export a PNG — the engine does the layout, with real fonts and real CSS. Switch the URL scheme to see which path lets you export.
Compose
Both schemes render identically — tainting affects readback, not drawing. The difference only shows up at export, which is exactly why the bug was easy to ship and painful to hit.
Canvas
Not rendered yet.
Exported image
code path
const svg = `<svg xmlns="http://www.w3.org/2000/svg" width="${w}" height="${h}">
<foreignObject width="${w}" height="${h}">
<div xmlns="http://www.w3.org/1999/xhtml">
<style>${css}</style>${html}
</div>
</foreignObject>
</svg>`;
const url = URL.createObjectURL(new Blob([svg], { type: "image/svg+xml" }));
const image = new Image();
image.onload = () => {
context.drawImage(image, 0, 0);
URL.revokeObjectURL(url);
};
image.src = url;
// The export is where tainting bites. Before Chrome 156 the blob path
// threw SecurityError here and authors fell back to a data: URI.
canvas.toBlob((blob) => download(blob));
Two things bite everyone who builds this: the markup inside foreignObject must be well-formed XHTML with the XHTML namespace, and external stylesheets do not apply — the SVG is a separate document, so the CSS has to travel with it in a <style> element.