v156 · graphics · canvas
SVG foreignObject does not taint the canvas for blob URLs
Drawing an SVG that contains a <foreignObject> onto a canvas taints it when the SVG came from an HTTP URL, and does not when it came from a data: URI — every engine agrees on both. The blob: case was the disagreement: Chromium and WebKit tainted, Gecko did not. Chrome 156 matches Gecko, which matters because it removes the reason to reach for a data URI at all.
concepts
-
Taint matrix
The same SVG drawn onto three canvases from three URL schemes, then
getImageData()called on each. The verdicts are read from realSecurityErrors, not from a table someone typed — so on a pre-156 browser the blob column reports tainted, and that is the change in one screen. -
HTML into a canvas
The use case this unlocks: compose styled HTML, wrap it in a
foreignObject, draw it to a canvas from a blob URL, and export a PNG. Edit the content and the styling live, then try the export and see whether the canvas lets you. -
What the blob URL saves
Percent-encoding inflates the markup by about 60%, and the inflated string is held in memory alongside the source. Whether the data URI is also slower turns out to be a question worth measuring rather than assuming — this one measures it, on your machine, at sizes from a label to a full page.
why it shipped
foreignObject is the only way to get HTML content into a canvas — laid out by the real engine, with real fonts and real CSS — which is why every "screenshot this element" library is built on it. The tainting rules exist because an SVG can reference cross-origin content and canvas readback would leak it; a data: URI cannot reference anything the page did not already have, so it is safe, and every engine agrees.
A blob: URL is the same situation: its contents came from the page itself. Gecko treated it that way; Chromium and WebKit did not, so authors had to percent-encode their markup into a data URI to get a readable canvas — roughly 60% more bytes, held in memory as a string, for content that can run to hundreds of kilobytes. The shipping intent cites processing speed as the motivation; the cost demo here measures both paths so you can see what actually moves on your machine. Either way, Chrome 156 removes the need to choose.
the API
const svg = `<svg xmlns="http://www.w3.org/2000/svg" width="400" height="200">
<foreignObject width="400" height="200">
<div xmlns="http://www.w3.org/1999/xhtml">${html}</div>
</foreignObject>
</svg>`;
// Chrome 156: this canvas stays readable.
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);
canvas.toBlob(save); // before 156: SecurityError
};
image.src = url;