Closed Bug 1935269 Opened 9 months ago Closed 3 months ago

img.naturalWidth and naturalHeight return 0 for dimensionless SVG images (and img.width and height return 0 for similar reasons, if such an image isn't being rendered)

Categories

(Core :: Layout: Images, Video, and HTML Frames, defect)

defect

Tracking

()

RESOLVED FIXED
140 Branch
Tracking Status
firefox140 --- fixed

People

(Reporter: dholbert, Assigned: dholbert)

References

Details

(Keywords: spec-needed, webcompat:platform-bug)

User Story

platform-scheduled:2025-07-15

Attachments

(7 files)

Attached file testcase 1

STR:

  1. Load attached testcase (which has an <img> pointing at a trivial dimensionless SVG data-URI document)

EXPECTED RESULTS:
The testcase should report img naturalWidth x naturalHeight: 300 x 150

ACTUAL RESULTS:
The testcase reports img naturalWidth x naturalHeight: 0 x 0

Chromium and WebKit give EXPECTED RESULTS.
Firefox gives ACTUAL RESULTS.

Note that we do report 300x150 for the img.width and height attributes, but we're returning 0 from naturalWidth and naturalHeight.

It looks like these attributes are all defined here in the HTML spec, in a non-normative section:
https://html.spec.whatwg.org/multipage/embedded-content.html#dom-img-width-dev
https://html.spec.whatwg.org/multipage/embedded-content.html#dom-img-naturalwidth-dev

In this case it seems Chrome/Safari are reporting the actual rendered width/height as the naturalWidth (because there is no actual "natural width" or "natural height" for this dimensionless image). I'm not yet finding spec justification for that, but there's at least one site that seems to rely on the Chrome/Safari behavior when computing an aspect ratio to kick off their sizing logic (which results in 0/0 = NaN in Firefox); see bug 1933708.

Using this more legacy-friendly testcase, I confirmed that this behavior goes back quite a long ways in Firefox; we show the same behavior[1] going back at least to NIghtly 2011-12-01 (Firefox 11 timeframe). So this has probably been the behavior ever since we implemented SVG-as-an-image, I'd guess.

[1] that same behavior being:

img width x height: 300 x 150
img naturalWidth x naturalHeight: 0 x 0

And Chromium's behavior goes back a long ways too; Chrome 37 (the oldest that BrowserStack lets me test on Win11) matches current Chrome in returning 300x150 for the naturalWidth x naturalHeight here.

See bug 1607081. I think this is just a duplicate of that.

Having said that the HTML spec says this...

https://html.spec.whatwg.org/multipage/embedded-content.html#embedded-content

image.naturalWidth
image.naturalHeight

These attributes return the natural dimensions of the image, or 0 if the dimensions are not known.

We don't know the dimensions do we? So we must return 0, no?

I've always thought (like Cameron did) that bug 1607081 is invalid per the HTML specification (as is this bug).

These attributes are not just defined by the (informative) note in the spec; below that, within the main text, we see:

The IDL attributes naturalWidth and naturalHeight must return the density-corrected natural width and height of the image, in CSS pixels, if the image has density-corrected natural width and height and is available, or else 0.

This seems pretty unambiguous. A dimensionless SVG has no natural width/height, and therefore these attributes must return 0.

So according to the HTML spec, I'd agree this is clearly invalid (and the other browsers have a bug).

However, maybe this is a case where we should accept that the webkit/blink behavior is more-or-less a de facto standard for the web, and the HTML spec (and our behavior) should be modified to reflect this?

Thanks for those references.

(In reply to Jonathan Kew [:jfkthame] from comment #4)

However, maybe this is a case where we should accept that the webkit/blink behavior is more-or-less a de facto standard for the web, and the HTML spec (and our behavior) should be modified to reflect this?

Hard to know, but I suspect this may be the case, given the longstanding Chromium/WebKit behavior here (and their collective dominance, paired with the fact that there is at least some web content that accidentally depends on their behavior).

I'll post some links to the WebKit code that seems to implement their current beahvior, for reference if/when we do end up updating the spec and/or our implementation to mitigate webcompat issues here.

Here is WebKit's HTMLImageElement::naturalWidth() implementation.

With a bit of guesswork about the exact codepath, I think that function ends up calling this stack of functions to reach the relevant SVG-image sizing code:
CachedImage::unclampedImageSizeForRenderer
CachedImage::imageSizeForRenderer
SVGImageCache::imageSizeForRenderer
SVGImage::size

That size function just returns SVGImage::m_intrinsicSize which would have gotten its value in SVGImage::containerSize

And specifically this section seems to fall back to 300x150 when setting that value:https://searchfox.org/wubkat/rev/48c752dce43162935898b93cefa254a21a5e84c5/Source/WebCore/svg/graphics/SVGImage.cpp#148-149,172-180

IntSize SVGImage::containerSize() const
{
...
    FloatSize currentSize;
    if (rootElement->hasIntrinsicWidth() && rootElement->hasIntrinsicHeight())
        currentSize = rootElement->currentViewportSizeExcludingZoom();
    else
        currentSize = rootElement->currentViewBoxRect().size();

    // Use the default CSS intrinsic size if the above failed.
    if (currentSize.isEmpty())
        return IntSize(300, 150);

Note that the isEmpty function at the end of my previous comment is defined here:
https://searchfox.org/wubkat/rev/48c752dce43162935898b93cefa254a21a5e84c5/Source/WebCore/platform/graphics/FloatSize.h#77

constexpr bool isEmpty() const { return m_width <= 0 || m_height <= 0; }

So it seems like WebKit is explicitly declining to have an SVG image whose intrinsic height or width is zero, even if it's declared as such.

See Also: → 1906145

Chrome's behavior is a bit more subtle than WebKit's -- they do return a naturalWidth (or naturalHeight) of 0 if the image's svg element is explicitly declared as having an 0px width (or height).

It's only when the svg element is declared as having a percent-valued width (or height) that Chrome treats it as having a naturalWidth (or naturalHeight) that matches the fallback replaced-element size.

Here's a testcase to demonstrate that distinction, where Firefox/Chromium/WebKit all disagree on img naturalWidth x naturalHeight. I've made the width an explicit 0px size here, vs. height an explicit 0% size, and the length vs percentage are handled differently in Chromium:

  • Firefox: 0 x 0
  • Chromium: 0 x 150
  • WebKit: 300 x 150

So I guess testcase 3 is showing that:

  • WebKit refuses to accept that the natural width and height might actually be legitimately 0
  • whereas Chrome accepts those as possible legitimate values, but still handles the "not-available/not-known" case by returning the appropriate component of the fallback 300x150 replaced-element dimensions.
  • whereas Firefox accepts 0 as possible legitimate values and also handles the "not-available/not-known" case by returning 0 per spec.

Chrome's SVG-as-an-image implementation is in this file:
https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/renderer/core/svg/graphics/svg_image.cc
...and intrinsic_size_ is the relevant member-variable for the purposes of this bug. They do indeed initialize it to 300x150 in some cases, here (kDefaultWidth by kDefaultHeight):

    intrinsic_size_ = PhysicalSize::FromSizeFFloor(blink::ConcreteObjectSize(
        sizing_info, gfx::SizeF(LayoutReplaced::kDefaultWidth,
                                LayoutReplaced::kDefaultHeight)));

They consider this to be a bug, per a comment here:
https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/renderer/core/svg/graphics/svg_image.cc;l=706-707;drc=616d60fca655937c2b730db94fd32d37ddff3bb5
which points to this bug:
https://issues.chromium.org/issues/41357911

I dropped a comment on the Chromium bug report to see if their version of this can get retriaged.

Given that their version of the bug is much narrower than WebKit's version, and is known-to-be-a-bug, and we've got only one known compat issue associated, it seems conceivable that we can get alignment on the specced behavior that Firefox already implements here...

Here's a testcase checking a variety of width/height combinations, most of which should produce a naturalWidth or naturalHeight value of 0.

Firefox gives PASS: All subtests passed!
Chrome gives FAIL: found 24 failures. Check DevTools console for details
WebKit gives FAIL: found 60 failures. Check DevTools console for details

So this does seem invalid (and a dupe of bug 1607081), but I'll leave it open for now; let's see if/how the other browsers respond to their bugs.

Severity: -- → S4

(In reply to Jonathan Kew [:jfkthame] from comment #15)

So this does seem invalid (and a dupe of bug 1607081), but I'll leave it open for now; let's see if/how the other browsers respond to their bugs.

Good news; there now seems to be a fair amount of ongoing progress on the Chromium bug which is a great sign.

I'll still leave this open since for now at least this is still a potential point of interop pain, but if Chromium's bug manages to get closed, I think we're good to close this out too.

Blocks: 1951871

As I just noticed in bug 1951871, this behavior-difference is also exposed via the .width and .height attributes for images that are not being rendered (i.e. not in the DOM), because https://html.spec.whatwg.org/multipage/embedded-content.html#dom-img-width defines those in terms of the natural {width,height} if the image has a natural width or height (or else 0 if the image does not, as is the case here).

STR:

  1. Load the testcase included here, which reports the width and height IDL attributes of an img that's not in the DOM, whose src is pointing at a dimensionless SVG image (with a 40x30, aspect-ratio just to make things non-default).

RESULTS, by browser:
0 x 0 (Firefox)
200 x 150 (Chromium)
40 x 30 (WebKit)

Attachment #9470135 - Attachment description: testcase 5 (using img `width` and `height` attributes, on an image not-in-the-document) → testcase 5 (reading the img `width` and `height` attributes, on an image that's not being rendered)
Summary: img.naturalWidth and naturalHeight return 0 for dimensionless SVG images → img.naturalWidth and naturalHeight return 0 for dimensionless SVG images (and img.width and height return 0 for similar reasons, if such an image isn't being rendered)

[removing webcompat:blocked-resources -- no one's actively working on this at the moment, but that's for good reasons as discussed in comment 16. If it becomes clear that we do need to make a change here (instead of engaging other browsers to align on our spec-compliant behavior, as I've been doing), we can likely get this resourced. In that case, it's likely not too complex to land a fix here; it's mostly a question of what the right change would be, since there's no consensus behavior right now between other browsers on what size to use for unsized SVG images.]

[--> Setting a tentative platform-scheduled date for early H2, since I think it's worth being sure we've got this sorted out by that point, and I think it's feasible to do so.]

Whiteboard: [platform-scheduled: 2025-07-15]

(In reply to Daniel Holbert [:dholbert] from comment #8)

Chrome's behavior is a bit more subtle than WebKit's -- they do return a naturalWidth (or naturalHeight) of 0 if the image's svg element is explicitly declared as having an 0px width (or height).

Side note - IanK mentioned in the CSSWG meeting today that Chromium has run into compat constraints where sites require this^ special handling for 0 -- i.e. there are cases where sites use an SVG image that has explicit height="0" and/or width="0" on the root <svg> element, where there's a strong requirement that the image be treated as having a natural size of 0 in that axis.

So if we do change behavior here, we need to keep that constraint in mind (i.e. we don't want to match the WebKit behavior that I described in comment 10). In other words: if we add special zero-avoiding behavior here for compat purposes, it needs to be scoped to only change behavior for images that lack a length-valued height and/or width on the root svg node.

(also: added spec-needed keyword since any behavior-changes we hypothetically make here will want to be based on (or fodder-for) spec updates to reflect the new reality.)

Keywords: spec-needed

One other observation about possible fixes here -- for the sites that are broken by this, the most important thing to get something painting is to return a nonzero value. However, also-important is for the number to be reasonably large, because it may be used as the resolution-to-which-the-image-will-be-rasterized.

Here's an extreme example to demonstrate that -- I built with a local patch to make VectorImage::GetWidth and GetHeight return 5 instead of 0 in their final return clauses (referenced in bug 1951871 comment 6); and that makes both icloud (bug 1923304) and findmybus.im (bug 1951871) start drawing the otherwise-missing-graphics, but they draw them extremely blurry (presumably because the SVG images get rasterized at their reported "natural size" of 5x5, and then that rasterization gets scaled to fill a larger fixed-size region on a <canvas>.

(However, this strawman approach is fine for the other associated webcompat bug, bug 1933708, since in that case the SVG image doesn't actually ever need to be drawn; it's just a stub data URI that ultimately gets replaced with a JPG or similar.)

Attachment #9471566 - Attachment description: screenshot of strawman "return 5" patch → screenshot of a strawman "return 5" patch resulting in blurry icons on associated webcompat site-reports
Assignee: nobody → dholbert
Status: NEW → ASSIGNED
Blocks: 1954054
User Story: (updated)
Whiteboard: [platform-scheduled: 2025-07-15]
User Story: (updated)
User Story: (updated)

I filed a spec bug https://github.com/whatwg/html/issues/11287 to change the spec on this to reflect the reality of WebCompat requirements/constraints.

Depends on: 1964813
Depends on: 1964818
Attachment #9485874 - Attachment description: WIP: Bug 1935269: Make HTMLImageElement::NaturalSize() fall back to the default 300x150 concrete object size instead of 0x0. → Bug 1935269: Make HTMLImageElement::NaturalSize() fall back to the default 300x150 concrete object size instead of 0x0. r?#layout
Pushed by dholbert@mozilla.com: https://hg.mozilla.org/integration/autoland/rev/6531e4de2d1d Make HTMLImageElement::NaturalSize() fall back to the default 300x150 concrete object size instead of 0x0. r=layout-reviewers,emilio
Status: ASSIGNED → RESOLVED
Closed: 3 months ago
Resolution: --- → FIXED
Target Milestone: --- → 140 Branch
Blocks: 1965560

We still show 0x0 on testcase 5 (which is bad from the perspective of bug 1923304 and bug 1951871) - I spun off followup bug 1965560 to fix that.

Blocks: 1967844
QA Whiteboard: [qa-triage-done-c141/b140]
Duplicate of this bug: 1607081
Depends on: 1965114
You need to log in before you can comment on or make changes to this bug.

Attachment

General

Created:
Updated:
Size: