Skip to content

Instantly share code, notes, and snippets.

@damons
Created March 10, 2026 17:32
Show Gist options
  • Select an option

  • Save damons/0284ed5241cb3c9a73f7d6d54374fc17 to your computer and use it in GitHub Desktop.

Select an option

Save damons/0284ed5241cb3c9a73f7d6d54374fc17 to your computer and use it in GitHub Desktop.
Firefox fit-content SVG zero-width reproduction

Proposed target bug: Bug 1929151 Related bugs: 1993701, 1204850, 1409529

Suggested summary if a new bug is preferred: Firefox resolves dimensionless SVG <img> to 0x0 inside width:fit-content parent when the img has width:100% and max-width

Suggested Bugzilla comment for Bug 1929151:

This looks like another reduced testcase for the same underlying issue, but affecting `width: fit-content` on the parent instead of `min-width` / `max-content` on the image itself.

Reduced testcase attached:
firefox-fit-content-shadow.html

What it does:
- Uses a dimensionless external-style SVG image (natural size ends up 300x104 in both browsers)
- Places it inside a parent link with `display:block; width:fit-content`
- Gives the `<img>` `width:100%; max-width:92px`
- Repeats the same case in regular DOM and in shadow DOM
- Includes control cases where the parent width is explicit `92px`

Observed results:
- Chrome 145:
  - DOM + fit-content: link 92x32, img 92x32
  - Shadow + fit-content: link 92x32, img 92x32
- Firefox 147:
  - DOM + fit-content: link 0x0, img 0x0
  - Shadow + fit-content: link 0x0, img 0x0
- In both browsers, the explicit-width control cases render as 92x32.
- In both browsers, the image reports `naturalWidth=300` and `naturalHeight=104`.

So this does not seem to be a shadow-DOM issue. Firefox specifically collapses the fit-content cases to zero during intrinsic sizing.

Why this looks spec-wrong:
- CSS Sizing 3 §5.2.1 says that for replaced elements, a cyclic percentage preferred size is treated as the property's initial value for max-content contribution calculations.
- In this testcase, the image is replaced, `width:100%` is cyclic, and `max-width:92px` is non-cyclic.
- So the image's max-content contribution should not become 0 here. At minimum, it should remain constrained by its non-cyclic `max-width:92px`, not collapse to zero.

Relevant spec text:
- https://drafts.csswg.org/css-sizing-3/#intrinsic-contribution
- In particular the "Intrinsic Contributions of Percentage-Sized Boxes" rules:
  - cyclic preferred/max size on replaced elements for max-content contribution: treat as initial value
  - summary table in §5.2.1

Cross-browser measurements from automation:

{ "chrome": { "domFit": { "linkWidth": 92, "imgWidth": 92, "naturalWidth": 300, "naturalHeight": 104 }, "shadowFit": { "linkWidth": 92, "imgWidth": 92, "naturalWidth": 300, "naturalHeight": 104 } }, "firefox": { "domFit": { "linkWidth": 0, "imgWidth": 0, "naturalWidth": 300, "naturalHeight": 104 }, "shadowFit": { "linkWidth": 0, "imgWidth": 0, "naturalWidth": 300, "naturalHeight": 104 } } }


If helpful I can turn this into a WPT-style testcase as well.

Notes:

  • The reduced testcase is at /Users/damon/simulchrist/testcases/firefox-fit-content-shadow.html
  • Measured results are at /Users/damon/simulchrist/testcases/firefox-fit-content-shadow-results.json
{
"chrome": {
"domFit": {
"imgHeight": 32,
"imgWidth": 92,
"linkHeight": 32,
"linkWidth": 92,
"naturalHeight": 104,
"naturalWidth": 300
},
"domFixed": {
"imgHeight": 32,
"imgWidth": 92,
"linkHeight": 32,
"linkWidth": 92,
"naturalHeight": 104,
"naturalWidth": 300
},
"shadowFit": {
"imgHeight": 32,
"imgWidth": 92,
"linkHeight": 32,
"linkWidth": 92,
"naturalHeight": 104,
"naturalWidth": 300
},
"shadowFixed": {
"imgHeight": 32,
"imgWidth": 92,
"linkHeight": 32,
"linkWidth": 92,
"naturalHeight": 104,
"naturalWidth": 300
}
},
"firefox": {
"domFit": {
"imgHeight": 0,
"imgWidth": 0,
"linkHeight": 0,
"linkWidth": 0,
"naturalHeight": 104,
"naturalWidth": 300
},
"domFixed": {
"imgHeight": 32,
"imgWidth": 92,
"linkHeight": 32,
"linkWidth": 92,
"naturalHeight": 104,
"naturalWidth": 300
},
"shadowFit": {
"imgHeight": 0,
"imgWidth": 0,
"linkHeight": 0,
"linkWidth": 0,
"naturalHeight": 104,
"naturalWidth": 300
},
"shadowFixed": {
"imgHeight": 32,
"imgWidth": 92,
"linkHeight": 32,
"linkWidth": 92,
"naturalHeight": 104,
"naturalWidth": 300
}
}
}
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Firefox fit-content SVG repro</title>
<style>
:root {
color-scheme: dark;
font-family: ui-sans-serif, system-ui, sans-serif;
}
body {
margin: 0;
padding: 24px;
background: #111;
color: #fff;
}
h1 {
font-size: 20px;
margin: 0 0 12px;
}
p {
margin: 0 0 20px;
max-width: 72ch;
}
.grid {
display: grid;
gap: 20px;
grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
}
.case {
background: #1a1a1a;
border: 1px solid #333;
border-radius: 12px;
padding: 16px;
}
.case h2 {
font-size: 16px;
margin: 0 0 10px;
}
.stage {
align-items: start;
background: #090909;
border-radius: 10px;
display: grid;
justify-content: start;
min-height: 120px;
padding: 12px;
}
.logo-link-fit {
display: block;
width: fit-content;
}
.logo-link-fixed {
display: block;
width: 92px;
}
.logo {
display: block;
width: 100%;
max-width: 92px;
background: rgba(255, 255, 255, 0.08);
}
.metrics {
font: 14px/1.45 ui-monospace, SFMono-Regular, Menlo, monospace;
margin-top: 12px;
white-space: pre-wrap;
}
</style>
</head>
<body>
<h1>Firefox fit-content SVG repro</h1>
<p>
This isolates the Wax player logo case: a block link sized with
<code>width: fit-content</code> containing an SVG <code>&lt;img&gt;</code>
with <code>width: 100%</code> and <code>max-width: 92px</code>.
</p>
<div class="grid">
<section class="case">
<h2>DOM + fit-content</h2>
<div class="stage" id="dom-fit-stage">
<a class="logo-link-fit" id="dom-fit-link" href="#">
<img class="logo" id="dom-fit-img" alt="logo">
</a>
</div>
<div class="metrics" id="dom-fit-metrics"></div>
</section>
<section class="case">
<h2>DOM + fixed width</h2>
<div class="stage" id="dom-fixed-stage">
<a class="logo-link-fixed" id="dom-fixed-link" href="#">
<img class="logo" id="dom-fixed-img" alt="logo">
</a>
</div>
<div class="metrics" id="dom-fixed-metrics"></div>
</section>
<section class="case">
<h2>Shadow + fit-content</h2>
<div class="stage" id="shadow-fit-host"></div>
<div class="metrics" id="shadow-fit-metrics"></div>
</section>
<section class="case">
<h2>Shadow + fixed width</h2>
<div class="stage" id="shadow-fixed-host"></div>
<div class="metrics" id="shadow-fixed-metrics"></div>
</section>
</div>
<script>
const svg = encodeURIComponent(`
<svg xmlns="http://www.w3.org/2000/svg" width="100%" height="100%" viewBox="0 0 92 32">
<rect width="92" height="32" rx="4" fill="#d00"></rect>
<text x="46" y="22" text-anchor="middle" font-size="16" font-family="Arial" fill="#fff">WAX</text>
</svg>
`);
const dataUrl = `data:image/svg+xml,${svg}`;
function setSource(id) {
document.getElementById(id).src = dataUrl;
}
setSource("dom-fit-img");
setSource("dom-fixed-img");
function mountShadow(hostId, fit) {
const host = document.getElementById(hostId);
const shadow = host.attachShadow({ mode: "open" });
shadow.innerHTML = `
<style>
.logo-link-fit { display: block; width: fit-content; }
.logo-link-fixed { display: block; width: 92px; }
.logo {
display: block;
width: 100%;
max-width: 92px;
background: rgba(255, 255, 255, 0.08);
}
</style>
<a class="${fit ? "logo-link-fit" : "logo-link-fixed"}" id="link" href="#">
<img class="logo" id="img" alt="logo" src="${dataUrl}">
</a>
`;
return shadow;
}
const shadowFit = mountShadow("shadow-fit-host", true);
const shadowFixed = mountShadow("shadow-fixed-host", false);
function describeBox(label, link, img, output) {
const linkRect = link.getBoundingClientRect();
const imgRect = img.getBoundingClientRect();
output.textContent = [
`${label}`,
`link: ${Math.round(linkRect.width)} x ${Math.round(linkRect.height)}`,
`img: ${Math.round(imgRect.width)} x ${Math.round(imgRect.height)}`,
`natural: ${img.naturalWidth} x ${img.naturalHeight}`,
].join("\n");
}
function measure() {
describeBox(
"DOM + fit-content",
document.getElementById("dom-fit-link"),
document.getElementById("dom-fit-img"),
document.getElementById("dom-fit-metrics"),
);
describeBox(
"DOM + fixed width",
document.getElementById("dom-fixed-link"),
document.getElementById("dom-fixed-img"),
document.getElementById("dom-fixed-metrics"),
);
describeBox(
"Shadow + fit-content",
shadowFit.getElementById("link"),
shadowFit.getElementById("img"),
document.getElementById("shadow-fit-metrics"),
);
describeBox(
"Shadow + fixed width",
shadowFixed.getElementById("link"),
shadowFixed.getElementById("img"),
document.getElementById("shadow-fixed-metrics"),
);
}
window.addEventListener("load", () => {
requestAnimationFrame(() => {
requestAnimationFrame(measure);
});
});
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment