Source code
Revision control
Copy as Markdown
Other Tools
Test Info:
- This WPT test may be referenced by the following Test IDs:
- /html/semantics/embedded-content/the-img-element/naturalWidth-naturalHeight-width-height.tentative.html - WPT Dashboard Interop Dashboard
<!doctype html>
<head>
<meta charset="utf-8">
<title>HTMLImageElement attributes naturalWidth, naturalHeight, width, height</title>
<!-- Note: this test asserts a different expectation from what the HTML spec
requires, as of mid-2025 when this testcase is being written. The spec
behavior doesn't appear to be web-compatible for some of the cases here,
addresing that. In the meantime, this test is named with ".tentative" to
indicate that it's not authoritative. After the spec change is accepted,
we can remove the neighboring naturalWidth-naturalHeight.html test which
asserts the prior spec text's expectations, since this test covers the
same ground but with its expectations set according to the
soon-to-be-updated spec text. -->
<link rel="help" href="https://html.spec.whatwg.org/multipage/embedded-content.html#dom-img-naturalwidth-dev">
<link rel="help" href="https://html.spec.whatwg.org/multipage/images.html#density-corrected-intrinsic-width-and-height">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<style>
#scroller {
/* We wrap all the test content in a scroller so that it doesn't push
* the textual test-results too far out of view.
*/
border: 1px solid black;
height: 400px;
width: max-content;
overflow: scroll;
}
#containingBlock {
/* There are a few SVG images here that size so that their margin-box fills
* their containing block width. We define a specific size here so that we
* can then check for it (minus the margins) in the "data-width" attribute.
*/
width: 740px;
}
img {
/* This styling is just cosmetic, to help visualize the images. */
border: 5px solid teal;
margin: 5px;
display: block;
}
</style>
<!-- We specify the img elements in a <template> and then clone them for
testing, so that we can dynamically generate and test several variants
of each img. -->
<template id="imgTemplates">
<!-- For each img element:
* The "data-natural-{width,height}" attributes represent the expected
values of the img element's "naturalWidth" and "naturalHeight" IDL
attributes. This test implicitly expects the "width" and "height" IDL
attributes to have those same expected values; but in cases where that's
not correct, we provide the actual expected value in the
"data-{width,height}" attributes (as distinguished from
data-natural-{width,height}).
* The "title" attribute is a description of the scenario being tested, and
it must be unique to satisfy the test harness requirements. -->
<!-- JPG images -->
<img src="resources/cat.jpg"
title="raster image"
data-natural-width="320" data-natural-height="240">
<img src="resources/cat.jpg" width="10" height="10"
title="raster image with width/height attributes"
data-natural-width="320" data-natural-height="240"
data-width="10" data-height="10"
data-not-rendered-width="10" data-not-rendered-height="10">
<!-- Use "size-comes-from-broken-image-icon" attribute to opt out of checking
img.width and img.height, because they come from the UA-dependent
size of the broken image icon: -->
<img src="non-existent.jpg"
title="non existent image, no natural dimensions"
data-natural-width="0" data-natural-height="0"
size-comes-from-broken-image-icon>
<img src="non-existent.jpg" width="10" height="10"
title="non existent image with width/height attributes, no natural dimensions"
data-natural-width="0" data-natural-height="0"
data-width="10" data-height="10"
data-not-rendered-width="10" data-not-rendered-height="10">
<!-- First group of SVG images: no viewBox, with a missing (or edge-casey, i.e.
negative or percent-valued) value for the width and/or height attr on the
root svg element in a SVG image. -->
title="SVG image, no natural dimensions"
data-natural-width="300" data-natural-height="150">
width="40" height="10"
data-width="40" data-height="10"
data-not-rendered-width="40" data-not-rendered-height="10"
title="SVG image with width/height attrs, no natural dimensions"
data-natural-width="300" data-natural-height="150">
width="40"
data-width="40" data-not-rendered-width="40"
title="SVG image with width attr, no natural dimensions"
data-natural-width="300" data-natural-height="150">
height="10"
data-height="10" data-not-rendered-height="10"
title="SVG image with height attr, no natural dimensions"
data-natural-width="300" data-natural-height="150">
<!-- Note: percent values can't be resolved when determining natural
dimensions, so the exact percentage shouldn't matter. -->
<img src="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' width='400%' height='10%'></svg>"
title="SVG image, percengage natural dimensions"
data-natural-width="300" data-natural-height="150">
<img src="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' width='-400%' height='-10%'></svg>"
title="SVG image, negative percengage natural dimensions"
data-natural-width="300" data-natural-height="150">
<!-- If only one attribute is present, it should show up as a natural
dimension, without influencing the other natural dimension. -->
title="SVG image, with natural width"
data-natural-width="60" data-natural-height="150">
title="SVG image, with natural height"
data-natural-width="300" data-natural-height="60">
<!-- If either attribute is 0 or a negative length, it should show up as a
natural dimension: of 0. -->
title="SVG image, with natural width of 0"
data-natural-width="0" data-natural-height="150">
title="SVG image, with natural height of 0"
data-natural-width="300" data-natural-height="0">
title="SVG image, with natural width being negative"
data-natural-width="0" data-natural-height="150">
title="SVG image, with natural height being negative"
data-natural-width="300" data-natural-height="0">
<!-- Second group of SVG images: Same as above, but now with a viewBox that grants a
3:1 aspect-ratio; whenever we know one natural dimension, that should
combine with the aspect ratio to produce the other natural dimension.
NOTE: for a few subtests here, the image ends up expanding to fill the
containing block's width, i.e. rendering at a larger size than its natural
size. In those cases, we include 'data-width' & 'data-height' attributes,
so that this test's JS can validate that img.width and img.height return
these expected larger values. (Otherwise, we expect img.width and
img.height to return the same values as img.naturalWidth and
img.naturalHeight). -->
title="SVG image, no natural dimensions, and aspect ratio from viewBox"
data-natural-width="300" data-natural-height="100"
data-width="720" data-height="240">
<img src="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' width='400%' height='10%' viewBox='0 0 600 200'></svg>"
title="SVG image, percengage natural dimensions, and aspect ratio from viewBox"
data-natural-width="300" data-natural-height="100"
data-width="720" data-height="240">
<img src="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' width='-400%' height='-10%' viewBox='0 0 600 200'></svg>"
title="SVG image, negative percengage natural dimensions, and aspect ratio from viewBox"
data-natural-width="300" data-natural-height="100"
data-width="720" data-height="240">
<img src="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' width='60' viewBox='0 0 600 200'></svg>"
title="SVG image, with natural width, and aspect ratio from viewBox"
data-natural-width="60" data-natural-height="20">
<img src="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' height='60' viewBox='0 0 600 200'></svg>"
title="SVG image, with natural height, and aspect ratio from viewBox"
data-natural-width="180" data-natural-height="60">
<img src="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' width='0' viewBox='0 0 600 200'></svg>"
title="SVG image, with natural width of 0, and aspect ratio from viewBox"
data-natural-width="0" data-natural-height="0">
<img src="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' height='0' viewBox='0 0 600 200'></svg>"
title="SVG image, with natural height of 0, and aspect ratio from viewBox"
data-natural-width="0" data-natural-height="0">
<img src="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' width='-5' viewBox='0 0 600 200'></svg>"
title="SVG image, with natural width being negative, and aspect ratio from viewBox"
data-natural-width="0" data-natural-height="0">
<img src="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' height='-5' viewBox='0 0 600 200'></svg>"
title="SVG image, with natural height being negative, and aspect ratio from viewBox"
data-natural-width="0" data-natural-height="0">
<!-- Third group of SVG images: Check a degenerate 0-sized viewBox for some of the
cases; it should have no impact. -->
title="SVG image, no natural dimensions, viewBox with 0 width/height"
data-natural-width="300" data-natural-height="150">
title="SVG image, no natural dimensions, viewBox with 0 width"
data-natural-width="300" data-natural-height="150">
title="SVG image, no natural dimensions, viewBox with 0 height"
data-natural-width="300" data-natural-height="150">
<img src="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' width='60' viewBox='0 0 0 0'></svg>"
title="SVG image, with natural width, viewBox with 0 width/height"
data-natural-width="60" data-natural-height="150">
<img src="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' width='60' viewBox='0 0 10 0'></svg>"
title="SVG image, with natural width, viewBox with 0 width"
data-natural-width="60" data-natural-height="150">
<img src="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' width='60' viewBox='0 0 0 10'></svg>"
title="SVG image, with natural width, viewBox with 0 height"
data-natural-width="60" data-natural-height="150">
<img src="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' height='60' viewBox='0 0 0 0'></svg>"
title="SVG image, with natural height, viewBox with 0 width/height"
data-natural-width="300" data-natural-height="60">
<img src="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' height='60' viewBox='0 0 10 0'></svg>"
title="SVG image, with natural height, viewBox with 0 width"
data-natural-width="300" data-natural-height="60">
<img src="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' height='60' viewBox='0 0 0 10'></svg>"
title="SVG image, with natural height, viewBox with 0 height"
data-natural-width="300" data-natural-height="60">
<!~- Final group of SVG images: we have pixel-valued width/height on the root
svg element, and possibly viewBox as well. The width and height attrs should
determine the natural dimensions, with no impact from viewBox. -->
title="SVG image, with natural width and height"
data-natural-width="60" data-natural-height="40">
<img src="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' width='60' height='40' viewBox='0 0 600 200'></svg>"
title="SVG image, with natural width and height, and aspect ratio from viewBox"
data-natural-width="60" data-natural-height="40">
<img src="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' width='0' height='0' viewBox='0 0 600 200'></svg>"
title="SVG image, with natural width and height of 0, and aspect ratio from viewBox"
data-natural-width="0" data-natural-height="0">
<img src="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' width='-5' height='-5' viewBox='0 0 600 200'></svg>"
title="SVG image, with natural width and height being negative, and aspect ratio from viewBox"
data-natural-width="0" data-natural-height="0">
</template>
</head>
<body>
<div id="scroller">
<div id="containingBlock">
</div>
</div>
<!-- We generate and append all of the tested <img> elements while we're inside
the <body>, so that all of the <img> elements' "load" events will block
the window onload event: -->
<script>
setup({explicit_done:true});
// Utility function to generate a clone of imgTemplates using "srcset" and a
// given density value, with expectations adjusted per the density:
function cloneTemplateWithDensity(density) {
if (typeof(density) !== "number" || density <= 0) {
// If we get here, there's a bug in the test itself; throw an exception:
throw new Error("test error: param should be a positive number");
}
let clone = imgTemplates.content.cloneNode("true");
for (let img of clone.children) {
// Swap out "src" for "srcset":
// The srcset attribute uses a space-separated list of tokens,
// so we need to encode any spaces that are in the URI itself
// before we put it in srcset:
let srcVal = img.getAttribute("src").replaceAll(" ", "%20");
img.removeAttribute("src");
img.setAttribute("srcSet", `${srcVal} ${density}x`);
// Mention the density in the 'title' to keep the title values unique:
img.setAttribute("title",
`${img.getAttribute("title")} (with srcset/${density}x)`);
const isUsingConcreteObjectWidth = (img.dataset.naturalWidth == "300");
const isUsingConcreteObjectHeight = (img.dataset.naturalHeight == "150");
// Scale our 'data-natural-{width,height}' expectations by the density.
// But also:
// * Preserve the original 'data-natural-{width,height}' as the
// 'data-{width,height}' expectation if it's just the concrete object size
// (which doesn't actually get scaled by the density in practice when
// the image actually renders).
// * Preserve the original 'data-natural-{width,height}' as the
// 'data-not-rendered-{width,height}' expectation (if we don't already have
// one) since browsers don't do density-correction on .width and .height when
// the image is not being rendered, as discussed in
for (let name in img.dataset) {
if (name.startsWith("natural")) {
let origExpectation = img.dataset[name];
// Scale our img.natural{Width,Height} expectation:
img.dataset[name] = origExpectation / density;
let isWidthAxis = (name == "naturalWidth");
let nameWithoutNatural = (isWidthAxis ? "width" : "height");
let isConcreteObjectSize =
(isWidthAxis ? isUsingConcreteObjectWidth : isUsingConcreteObjectHeight);
if (isConcreteObjectSize && !(nameWithoutNatural in img.dataset)) {
img.dataset[nameWithoutNatural] = origExpectation;
}
// Construct a string for "data-not-rendered-{width,height}" for
// whichever axis we're currently handling, and stash the
// origExpectation in there if we don't already have some expectation
// set there:
let notRenderedName = name.replace("natural", "notRendered");
if (!(notRenderedName in img.dataset)) {
img.dataset[notRenderedName] = origExpectation;
}
}
}
}
return clone;
}
// Clone and append a copy of the contents of imgTemplates, for testing:
let clone = imgTemplates.content.cloneNode("true");
containingBlock.appendChild(clone);
// Append additional clones, modified to use srcset with specified density.
// Note:
// * '1' is the trivial case; we're just testing that for completeness,
// to be sure there aren't any unexpected differences between
// <img src="..."> and <img srcset="... 1x">).
// * It's best for whatever density we test here to be something that evenly
// divides all of the image sizes in this test (so 1 and 2 are easy choices).
containingBlock.appendChild(cloneTemplateWithDensity(1));
containingBlock.appendChild(cloneTemplateWithDensity(2));
// After all the img elements have loaded (indicated by the window load event),
// we run the various tests:
onload = function() {
Array.from(document.images).forEach(img => {
const expectedNaturalWidth = parseFloat(img.dataset.naturalWidth);
const expectedNaturalHeight = parseFloat(img.dataset.naturalHeight);
test(function() {
// We expect naturalWidth to match the provided data-natural-width
// (and similar for 'height').
assert_equals(img.naturalWidth, expectedNaturalWidth, 'naturalWidth');
assert_equals(img.naturalHeight, expectedNaturalHeight, 'naturalHeight');
let shouldCheckWidthAndHeight =
!img.hasAttribute("size-comes-from-broken-image-icon");
if (shouldCheckWidthAndHeight) {
// If 'data-width' is provided, then we expect img.width to match it.
// Otherwise we expect img.width to match the 'data-natural-width'.
// (And similar for 'height'.)
const expectedWidth = 'width' in img.dataset ?
parseFloat(img.dataset.width) : expectedNaturalWidth;
const expectedHeight = 'height' in img.dataset ?
parseFloat(img.dataset.height) : expectedNaturalHeight;
assert_equals(img.width, expectedWidth, 'width');
assert_equals(img.height, expectedHeight, 'height');
}
}, `${img.title}`);
test(function() {
// Now test what we get when the img is not rendered.
// * naturalWidth and naturalHeight shouldn't change.
// * width and height should generally match naturalWidth and
// naturalHeight. (Exceptions are indicated via the
// 'data-not-rendered-{width/height} attributes).
this.add_cleanup(function() {
img.style.display = "";
});
img.style.display = "none";
img.getBoundingClientRect(); // Flush layout.
assert_equals(img.naturalWidth, expectedNaturalWidth,
'naturalWidth when not rendered');
assert_equals(img.naturalHeight, expectedNaturalHeight,
'naturalHeight when not rendered');
const expectedNotRenderedWidth = 'notRenderedWidth' in img.dataset ?
parseFloat(img.dataset.notRenderedWidth) : expectedNaturalWidth;
const expectedNotRenderedHeight = 'notRenderedHeight' in img.dataset ?
parseFloat(img.dataset.notRenderedHeight) : expectedNaturalHeight;
assert_equals(img.width, expectedNotRenderedWidth,
'width when not rendered');
assert_equals(img.height, expectedNotRenderedHeight,
'height when not rendered');
}, `${img.title} (when not rendered)`);
});
done();
};
</script>
</body>