SVG foreignObject Can Taint Canvas and Break WebGL Textures
If you render HTML inside SVG with <foreignObject>, draw that SVG to a canvas, and then use the canvas as a WebGL texture, Chromium may reject the texture as tainted.
The confusing part is that this can happen even when there are no remote images, no external fonts, and no obvious cross-origin resource. The <foreignObject> rasterization path itself can be enough to make the canvas unusable for WebGL texture upload.
The symptom
In a three.js or WebGL app, the console may show a security error near texture upload:
SecurityError: Failed to execute 'texSubImage2D' on 'WebGL2RenderingContext':
Tainted canvases may not be loaded.
The visible result is usually a missing texture, a blank plane, or a fallback material.
Minimal failure shape
This is the risky pipeline:
HTML layout
-> SVG <foreignObject>
-> draw SVG image into <canvas>
-> THREE.CanvasTexture or WebGL texture upload
The <foreignObject> step is the problem. It asks the browser to embed HTML layout inside SVG rasterization. That can trigger canvas tainting rules even when the content looks local.
Detect taint before WebGL upload
Do not rely on a try/catch around three.js texture initialization. Libraries may catch or defer internal errors.
Probe the canvas directly:
function assertCanvasReadable(canvas: HTMLCanvasElement) {
const context = canvas.getContext("2d");
if (!context) {
throw new Error("Canvas 2D context is not available.");
}
try {
context.getImageData(0, 0, 1, 1);
} catch (error) {
throw new Error(`Canvas is tainted and cannot be used as a WebGL texture: ${error}`);
}
}
Call this before creating a texture:
assertCanvasReadable(canvas);
const texture = new THREE.CanvasTexture(canvas);
texture.needsUpdate = true;
getImageData() is a reliable readback check because tainted canvases are not readable.
Use Canvas 2D directly for generated text textures
If your goal is to render generated text, labels, pages, or diagrams into a WebGL texture, bypass SVG <foreignObject> and draw directly:
function renderLabel(text: string) {
const canvas = document.createElement("canvas");
canvas.width = 1024;
canvas.height = 512;
const context = canvas.getContext("2d");
if (!context) throw new Error("2D context unavailable.");
context.fillStyle = "#ffffff";
context.fillRect(0, 0, canvas.width, canvas.height);
context.fillStyle = "#111827";
context.font = "32px system-ui, sans-serif";
context.fillText(text, 48, 96);
assertCanvasReadable(canvas);
return canvas;
}
This avoids the browser's HTML-in-SVG rasterization path. For many app-generated labels and document previews, direct Canvas 2D drawing is less elegant than DOM layout but more predictable for WebGL.
Vertical text and complex layout
Japanese vertical text is a common reason developers reach for <foreignObject>: CSS writing-mode can lay out text nicely in DOM.
For WebGL textures, the safer architecture is to implement the layout in canvas coordinates:
function drawVerticalText(
context: CanvasRenderingContext2D,
text: string,
startX: number,
startY: number,
lineHeight: number
) {
let y = startY;
for (const char of text) {
context.fillText(char, startX, y);
y += lineHeight;
}
}
That can grow into pagination, columns, punctuation handling, and font fallback. The benefit is that the generated canvas remains readable and can be uploaded as a texture.
When SVG is still fine
This article is not a warning against SVG in general.
Pure SVG shapes, paths, gradients, and text can often be rasterized safely. The high-risk part is embedding HTML through <foreignObject> and then expecting the result to behave like a normal same-origin bitmap for WebGL.
Architecture choices
Choose based on the target:
Need DOM layout and no WebGL texture
-> SVG foreignObject may be acceptable
Need WebGL texture from generated content
-> Canvas 2D direct rendering is safer
Need pixel-perfect complex pages
-> server-side rasterization can be safer
Need graceful fallback only
-> CSS 3D transform may be enough
References
- MDN: Use cross-origin images in a canvas
- Chromium issue: canvas tainted after drawing SVG including foreignObject
- three.js CanvasTexture documentation
Summary
If WebGL texture upload fails after drawing an SVG to canvas, do not only look for external images. Check whether the SVG uses <foreignObject>. Probe the canvas with getImageData(), then switch to direct Canvas 2D rendering or server-side rasterization when the texture must be reliable.