-
-
Notifications
You must be signed in to change notification settings - Fork 36k
Closed as not planned
Closed as not planned
Copy link
Description
Description
When using a strict content-security-policy, svg files cannot be loaded properly if they contain inline styles. The errors are raised here
three.js/examples/jsm/loaders/SVGLoader.js
Line 1973 in 5988c7a
const xml = new DOMParser().parseFromString( text, 'image/svg+xml' ); // application/xml |
I think that the DOMParser respects the csp and does not parse inline styles when they are not allowed by the csp. This breaks svgs e.g. when exported from inkscape because inkscape uses style attributes.
I think that inline styles/scripts/whatever are fine irrespective of the csp when loading an svg since threejs does not create dom nodes but maps the elements to threejs primitives, right?
If so, a possible solution might be to replace the DOMParser with another xml parser that is not influenced by the csp.
Reproduction steps
Use the following html file (e.g. with python -m http.server) and toggle the csp at the top:
<!doctype html>
<html>
<head>
<!-- this works -->
<!-- <meta http-equiv="Content-Security-Policy" -->
<!-- content="default-src 'self'; script-src-elem 'nonce-randomnonce' https://cdn.jsdelivr.net; connect-src blob:; style-src 'unsafe-inline'" /> -->
<!-- this does not work -->
<meta http-equiv="Content-Security-Policy"
content="default-src 'self'; script-src-elem 'nonce-randomnonce' https://cdn.jsdelivr.net; connect-src blob:" />
<script type="importmap" nonce="randomnonce">
{
"imports": {
"three": "https://cdn.jsdelivr.net/npm/[email protected]/build/three.module.js",
"SVGLoader": "https://cdn.jsdelivr.net/npm/[email protected]/examples/jsm/loaders/SVGLoader.js"
}
}
</script>
<style nonce="randomnonce">
body {
margin: 0;
}
</style>
</head>
<body>
<script type="module" nonce="randomnonce">
import * as THREE from "three";
import {SVGLoader} from "SVGLoader";
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(
70,
window.innerWidth / window.innerHeight,
0.1,
1000,
);
camera.position.z = 2;
const renderer = new THREE.WebGLRenderer({antialias: true});
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
const loader = new SVGLoader();
const svgData = `
<svg
viewBox="0 0 1 1"
version="1.1"
id="svg1"
inkscape:version="1.4.1 (93de688d07, 2025-03-30)"
sodipodi:docname="asset.svg"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<sodipodi:namedview
id="namedview1"
pagecolor="#ffffff"
bordercolor="#000000"
borderopacity="0.25"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
inkscape:document-units="mm"
inkscape:zoom="256"
inkscape:cx="2.0449219"
inkscape:cy="1.0761719"
inkscape:window-width="1916"
inkscape:window-height="2020"
inkscape:window-x="0"
inkscape:window-y="138"
inkscape:window-maximized="1"
inkscape:current-layer="layer1" />
<defs
id="defs1" />
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(-0.12072184,-0.08673415)">
<rect
style="fill:#888888;fill-opacity:0.933333;stroke:none;stroke-width:0.0173009;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
id="rect1-2"
width="0.84258819"
height="0.88810742"
x="0.19942777"
y="0.14268047"
rx="0.094999999"
ry="0.094999999"
inkscape:label="outline" />
<rect
style="fill:#f2f2f0;fill-opacity:0.933333;stroke:none;stroke-width:0.0168928;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
id="rect1"
width="0.82271105"
height="0.86715645"
x="0.20936632"
y="0.15315592"
rx="0.082271107"
ry="0.086715646"
inkscape:label="rect" />
<path
sodipodi:type="star"
style="fill:#b7c4aa;fill-opacity:1;stroke:none;stroke-width:0.02;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
id="north"
inkscape:flatsided="true"
sodipodi:sides="3"
sodipodi:cx="0.58374268"
sodipodi:cy="0.62210196"
sodipodi:r1="0.13287176"
sodipodi:r2="0.066435881"
sodipodi:arg1="-1.5707963"
sodipodi:arg2="-0.52359878"
inkscape:rounded="0"
inkscape:randomized="0"
d="m 0.58374268,0.4892302 0.11507032,0.19930765 -0.23014064,-1e-8 z"
inkscape:transform-center-y="-0.043083946"
transform="matrix(0.47906062,0,0,1.2970083,0.34253532,-0.45760855)"
inkscape:label="north" />
<path
sodipodi:type="star"
style="fill:#b7c4aa;fill-opacity:1;stroke:none;stroke-width:0.02;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
id="east"
inkscape:flatsided="true"
sodipodi:sides="3"
sodipodi:cx="0.58374268"
sodipodi:cy="0.62210196"
sodipodi:r1="0.13287176"
sodipodi:r2="0.066435881"
sodipodi:arg1="-1.5707963"
sodipodi:arg2="-0.52359878"
inkscape:rounded="0"
inkscape:randomized="0"
d="m 0.58374268,0.4892302 0.11507032,0.19930765 -0.23014064,-1e-8 z"
inkscape:transform-center-y="0.0010335321"
transform="matrix(0,0.47906062,-1.2970083,0,1.6626323,0.30525927)"
inkscape:transform-center-x="-0.27769491"
inkscape:label="east" />
<path
sodipodi:type="star"
style="fill:#b7c4aa;fill-opacity:1;stroke:none;stroke-width:0.02;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
id="south"
inkscape:flatsided="true"
sodipodi:sides="3"
sodipodi:cx="0.58374268"
sodipodi:cy="0.62210196"
sodipodi:r1="0.13287176"
sodipodi:r2="0.066435881"
sodipodi:arg1="-1.5707963"
sodipodi:arg2="-0.52359878"
inkscape:rounded="0"
inkscape:randomized="0"
d="m 0.58374268,0.4892302 0.11507032,0.19930765 -0.23014064,-1e-8 z"
inkscape:transform-center-y="0.27769487"
transform="matrix(-0.47906062,0,0,-1.2970083,0.89976452,1.6253562)"
inkscape:transform-center-x="0.0010335321"
inkscape:label="south" />
<path
sodipodi:type="star"
style="fill:#b7c4aa;fill-opacity:1;stroke:none;stroke-width:0.02;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
id="west"
inkscape:flatsided="true"
sodipodi:sides="3"
sodipodi:cx="0.58374268"
sodipodi:cy="0.62210196"
sodipodi:r1="0.13287176"
sodipodi:r2="0.066435881"
sodipodi:arg1="-1.5707963"
sodipodi:arg2="-0.52359878"
inkscape:rounded="0"
inkscape:randomized="0"
d="m 0.58374268,0.4892302 0.11507032,0.19930765 -0.23014064,-1e-8 z"
inkscape:transform-center-y="-0.0010335321"
transform="matrix(0,-0.47906062,1.2970083,0,-0.42033241,0.86248846)"
inkscape:transform-center-x="0.27769487"
inkscape:label="west" />
</g>
</svg>
`;
const svgBlob = new Blob([svgData], {type: "image/svg+xml"});
const url = URL.createObjectURL(svgBlob);
loader.load(url, (data) => {
const group = new THREE.Group();
data.paths.forEach((path) => {
const material = new THREE.MeshBasicMaterial({
color: path.color,
side: THREE.DoubleSide,
depthWrite: false,
});
const shapes = SVGLoader.createShapes(path);
shapes.forEach((shape) => {
const geometry = new THREE.ShapeGeometry(shape);
const mesh = new THREE.Mesh(geometry, material);
group.add(mesh);
});
});
group.scale.set(1, -1, 1); // Flip Y axis
scene.add(group);
animate();
});
function animate() {
requestAnimationFrame(animate);
renderer.render(scene, camera);
}
window.addEventListener("resize", () => {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
});
</script>
</body>
</html>
Code
// code goes here
Live example
Please use the reproduction html above.
Screenshots
No response
Version
r176.0
Device
Desktop
Browser
Chrome
OS
Linux