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.

Press Render, then try the export.

Exported image

The exported PNG appears here once an export succeeds.

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.

see also