naturalWidth-naturalHeight-width-height.tentative.html (20557B)
1 <!doctype html> 2 <head> 3 <meta charset="utf-8"> 4 <title>HTMLImageElement attributes naturalWidth, naturalHeight, width, height</title> 5 <!-- Note: this test asserts a different expectation from what the HTML spec 6 requires, as of mid-2025 when this testcase is being written. The spec 7 behavior doesn't appear to be web-compatible for some of the cases here, 8 and issue https://github.com/whatwg/html/issues/11287 is filed on 9 addresing that. In the meantime, this test is named with ".tentative" to 10 indicate that it's not authoritative. After the spec change is accepted, 11 we can remove the neighboring naturalWidth-naturalHeight.html test which 12 asserts the prior spec text's expectations, since this test covers the 13 same ground but with its expectations set according to the 14 soon-to-be-updated spec text. --> 15 <link rel="help" href="https://github.com/whatwg/html/issues/11287"> 16 <link rel="help" href="https://html.spec.whatwg.org/multipage/embedded-content.html#dom-img-naturalwidth-dev"> 17 <link rel="help" href="https://html.spec.whatwg.org/multipage/embedded-content.html#dom-img-width"> 18 <link rel="help" href="https://html.spec.whatwg.org/multipage/images.html#density-corrected-intrinsic-width-and-height"> 19 <link rel="help" href="https://drafts.csswg.org/css-images/#natural-dimensions"> 20 <script src="/resources/testharness.js"></script> 21 <script src="/resources/testharnessreport.js"></script> 22 <style> 23 #scroller { 24 /* We wrap all the test content in a scroller so that it doesn't push 25 * the textual test-results too far out of view. 26 */ 27 border: 1px solid black; 28 height: 400px; 29 width: max-content; 30 overflow: scroll; 31 } 32 #containingBlock { 33 /* There are a few SVG images here that size so that their margin-box fills 34 * their containing block width. We define a specific size here so that we 35 * can then check for it (minus the margins) in the "data-width" attribute. 36 */ 37 width: 740px; 38 } 39 img { 40 /* This styling is just cosmetic, to help visualize the images. */ 41 border: 5px solid teal; 42 margin: 5px; 43 display: block; 44 } 45 </style> 46 <!-- We specify the img elements in a <template> and then clone them for 47 testing, so that we can dynamically generate and test several variants 48 of each img. --> 49 <template id="imgTemplates"> 50 <!-- For each img element: 51 * The "data-natural-{width,height}" attributes represent the expected 52 values of the img element's "naturalWidth" and "naturalHeight" IDL 53 attributes. This test implicitly expects the "width" and "height" IDL 54 attributes to have those same expected values; but in cases where that's 55 not correct, we provide the actual expected value in the 56 "data-{width,height}" attributes (as distinguished from 57 data-natural-{width,height}). 58 * The "title" attribute is a description of the scenario being tested, and 59 it must be unique to satisfy the test harness requirements. --> 60 61 <!-- JPG images --> 62 <img src="resources/cat.jpg" 63 title="raster image" 64 data-natural-width="320" data-natural-height="240"> 65 <img src="resources/cat.jpg" width="10" height="10" 66 title="raster image with width/height attributes" 67 data-natural-width="320" data-natural-height="240" 68 data-width="10" data-height="10" 69 data-not-rendered-width="10" data-not-rendered-height="10"> 70 <!-- Use "size-comes-from-broken-image-icon" attribute to opt out of checking 71 img.width and img.height, because they come from the UA-dependent 72 size of the broken image icon: --> 73 <img src="non-existent.jpg" 74 title="non existent image, no natural dimensions" 75 data-natural-width="0" data-natural-height="0" 76 size-comes-from-broken-image-icon> 77 <img src="non-existent.jpg" width="10" height="10" 78 title="non existent image with width/height attributes, no natural dimensions" 79 data-natural-width="0" data-natural-height="0" 80 data-width="10" data-height="10" 81 data-not-rendered-width="10" data-not-rendered-height="10"> 82 83 <!-- First group of SVG images: no viewBox, with a missing (or edge-casey, i.e. 84 negative or percent-valued) value for the width and/or height attr on the 85 root svg element in a SVG image. --> 86 <img src="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg'></svg>" 87 title="SVG image, no natural dimensions" 88 data-natural-width="300" data-natural-height="150"> 89 <img src="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg'></svg>" 90 width="40" height="10" 91 data-width="40" data-height="10" 92 data-not-rendered-width="40" data-not-rendered-height="10" 93 title="SVG image with width/height attrs, no natural dimensions" 94 data-natural-width="300" data-natural-height="150"> 95 <img src="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg'></svg>" 96 width="40" 97 data-width="40" data-not-rendered-width="40" 98 title="SVG image with width attr, no natural dimensions" 99 data-natural-width="300" data-natural-height="150"> 100 <img src="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg'></svg>" 101 height="10" 102 data-height="10" data-not-rendered-height="10" 103 title="SVG image with height attr, no natural dimensions" 104 data-natural-width="300" data-natural-height="150"> 105 <!-- Note: percent values can't be resolved when determining natural 106 dimensions, so the exact percentage shouldn't matter. --> 107 <img src="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' width='400%' height='10%'></svg>" 108 title="SVG image, percengage natural dimensions" 109 data-natural-width="300" data-natural-height="150"> 110 <img src="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' width='-400%' height='-10%'></svg>" 111 title="SVG image, negative percengage natural dimensions" 112 data-natural-width="300" data-natural-height="150"> 113 <!-- If only one attribute is present, it should show up as a natural 114 dimension, without influencing the other natural dimension. --> 115 <img src="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' width='60'></svg>" 116 title="SVG image, with natural width" 117 data-natural-width="60" data-natural-height="150"> 118 <img src="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' height='60'></svg>" 119 title="SVG image, with natural height" 120 data-natural-width="300" data-natural-height="60"> 121 <!-- If either attribute is 0 or a negative length, it should show up as a 122 natural dimension: of 0. --> 123 <img src="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' width='0'></svg>" 124 title="SVG image, with natural width of 0" 125 data-natural-width="0" data-natural-height="150"> 126 <img src="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' height='0'></svg>" 127 title="SVG image, with natural height of 0" 128 data-natural-width="300" data-natural-height="0"> 129 <img src="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' width='-5'></svg>" 130 title="SVG image, with natural width being negative" 131 data-natural-width="300" data-natural-height="150"> 132 <img src="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' height='-5'></svg>" 133 title="SVG image, with natural height being negative" 134 data-natural-width="300" data-natural-height="150"> 135 136 <!-- Second group of SVG images: Same as above, but now with a viewBox that grants a 137 3:1 aspect-ratio; whenever we know one natural dimension, that should 138 combine with the aspect ratio to produce the other natural dimension. 139 140 NOTE: for a few subtests here, the image ends up expanding to fill the 141 containing block's width, i.e. rendering at a larger size than its natural 142 size. In those cases, we include 'data-width' & 'data-height' attributes, 143 so that this test's JS can validate that img.width and img.height return 144 these expected larger values. (Otherwise, we expect img.width and 145 img.height to return the same values as img.naturalWidth and 146 img.naturalHeight). --> 147 <img src="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 600 200'></svg>" 148 title="SVG image, no natural dimensions, and aspect ratio from viewBox" 149 data-natural-width="300" data-natural-height="100" 150 data-width="720" data-height="240"> 151 <img src="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' width='400%' height='10%' viewBox='0 0 600 200'></svg>" 152 title="SVG image, percengage natural dimensions, and aspect ratio from viewBox" 153 data-natural-width="300" data-natural-height="100" 154 data-width="720" data-height="240"> 155 <img src="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' width='-400%' height='-10%' viewBox='0 0 600 200'></svg>" 156 title="SVG image, negative percengage natural dimensions, and aspect ratio from viewBox" 157 data-natural-width="300" data-natural-height="100" 158 data-width="720" data-height="240"> 159 <img src="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' width='60' viewBox='0 0 600 200'></svg>" 160 title="SVG image, with natural width, and aspect ratio from viewBox" 161 data-natural-width="60" data-natural-height="20"> 162 <img src="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' height='60' viewBox='0 0 600 200'></svg>" 163 title="SVG image, with natural height, and aspect ratio from viewBox" 164 data-natural-width="180" data-natural-height="60"> 165 <img src="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' width='0' viewBox='0 0 600 200'></svg>" 166 title="SVG image, with natural width of 0, and aspect ratio from viewBox" 167 data-natural-width="0" data-natural-height="0"> 168 <img src="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' height='0' viewBox='0 0 600 200'></svg>" 169 title="SVG image, with natural height of 0, and aspect ratio from viewBox" 170 data-natural-width="0" data-natural-height="0"> 171 <img src="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' width='-5' viewBox='0 0 600 200'></svg>" 172 title="SVG image, with natural width being negative, and aspect ratio from viewBox" 173 data-natural-width="300" data-natural-height="100" 174 data-width="720" data-height="240"> 175 <img src="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' height='-5' viewBox='0 0 600 200'></svg>" 176 title="SVG image, with natural height being negative, and aspect ratio from viewBox" 177 data-natural-width="300" data-natural-height="100" 178 data-width="720" data-height="240"> 179 180 <!-- Third group of SVG images: Check a degenerate 0-sized viewBox for some of the 181 cases; it should have no impact. --> 182 <img src="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 0 0'></svg>" 183 title="SVG image, no natural dimensions, viewBox with 0 width/height" 184 data-natural-width="300" data-natural-height="150"> 185 <img src="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 10 0'></svg>" 186 title="SVG image, no natural dimensions, viewBox with 0 width" 187 data-natural-width="300" data-natural-height="150"> 188 <img src="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 0 10'></svg>" 189 title="SVG image, no natural dimensions, viewBox with 0 height" 190 data-natural-width="300" data-natural-height="150"> 191 <img src="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' width='60' viewBox='0 0 0 0'></svg>" 192 title="SVG image, with natural width, viewBox with 0 width/height" 193 data-natural-width="60" data-natural-height="150"> 194 <img src="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' width='60' viewBox='0 0 10 0'></svg>" 195 title="SVG image, with natural width, viewBox with 0 width" 196 data-natural-width="60" data-natural-height="150"> 197 <img src="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' width='60' viewBox='0 0 0 10'></svg>" 198 title="SVG image, with natural width, viewBox with 0 height" 199 data-natural-width="60" data-natural-height="150"> 200 <img src="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' height='60' viewBox='0 0 0 0'></svg>" 201 title="SVG image, with natural height, viewBox with 0 width/height" 202 data-natural-width="300" data-natural-height="60"> 203 <img src="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' height='60' viewBox='0 0 10 0'></svg>" 204 title="SVG image, with natural height, viewBox with 0 width" 205 data-natural-width="300" data-natural-height="60"> 206 <img src="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' height='60' viewBox='0 0 0 10'></svg>" 207 title="SVG image, with natural height, viewBox with 0 height" 208 data-natural-width="300" data-natural-height="60"> 209 210 <!~- Final group of SVG images: we have pixel-valued width/height on the root 211 svg element, and possibly viewBox as well. The width and height attrs should 212 determine the natural dimensions, with no impact from viewBox. --> 213 <img src="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' width='60' height='40'></svg>" 214 title="SVG image, with natural width and height" 215 data-natural-width="60" data-natural-height="40"> 216 <img src="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' width='60' height='40' viewBox='0 0 600 200'></svg>" 217 title="SVG image, with natural width and height, and aspect ratio from viewBox" 218 data-natural-width="60" data-natural-height="40"> 219 <img src="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' width='0' height='0' viewBox='0 0 600 200'></svg>" 220 title="SVG image, with natural width and height of 0, and aspect ratio from viewBox" 221 data-natural-width="0" data-natural-height="0"> 222 <img src="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' width='-5' height='-5' viewBox='0 0 600 200'></svg>" 223 title="SVG image, with natural width and height being negative, and aspect ratio from viewBox" 224 data-natural-width="300" data-natural-height="100" 225 data-width="720" data-height="240"> 226 </template> 227 </head> 228 <body> 229 <div id="scroller"> 230 <div id="containingBlock"> 231 </div> 232 </div> 233 <!-- We generate and append all of the tested <img> elements while we're inside 234 the <body>, so that all of the <img> elements' "load" events will block 235 the window onload event: --> 236 <script> 237 setup({explicit_done:true}); 238 239 // Utility function to generate a clone of imgTemplates using "srcset" and a 240 // given density value, with expectations adjusted per the density: 241 function cloneTemplateWithDensity(density) { 242 if (typeof(density) !== "number" || density <= 0) { 243 // If we get here, there's a bug in the test itself; throw an exception: 244 throw new Error("test error: param should be a positive number"); 245 } 246 let clone = imgTemplates.content.cloneNode("true"); 247 248 for (let img of clone.children) { 249 // Swap out "src" for "srcset": 250 // The srcset attribute uses a space-separated list of tokens, 251 // so we need to encode any spaces that are in the URI itself 252 // before we put it in srcset: 253 let srcVal = img.getAttribute("src").replaceAll(" ", "%20"); 254 img.removeAttribute("src"); 255 img.setAttribute("srcSet", `${srcVal} ${density}x`); 256 257 // Mention the density in the 'title' to keep the title values unique: 258 img.setAttribute("title", 259 `${img.getAttribute("title")} (with srcset/${density}x)`); 260 261 const isUsingConcreteObjectWidth = (img.dataset.naturalWidth == "300"); 262 const isUsingConcreteObjectHeight = (img.dataset.naturalHeight == "150"); 263 264 // Scale our 'data-natural-{width,height}' expectations by the density. 265 // But also: 266 // * Preserve the original 'data-natural-{width,height}' as the 267 // 'data-{width,height}' expectation if it's just the concrete object size 268 // (which doesn't actually get scaled by the density in practice when 269 // the image actually renders). 270 // * Preserve the original 'data-natural-{width,height}' as the 271 // 'data-not-rendered-{width,height}' expectation (if we don't already have 272 // one) since browsers don't do density-correction on .width and .height when 273 // the image is not being rendered, as discussed in 274 // https://github.com/whatwg/html/issues/11287#issuecomment-2923467541 275 for (let name in img.dataset) { 276 if (name.startsWith("natural")) { 277 let origExpectation = img.dataset[name]; 278 279 // Scale our img.natural{Width,Height} expectation: 280 img.dataset[name] = origExpectation / density; 281 282 let isWidthAxis = (name == "naturalWidth"); 283 let nameWithoutNatural = (isWidthAxis ? "width" : "height"); 284 285 let isConcreteObjectSize = 286 (isWidthAxis ? isUsingConcreteObjectWidth : isUsingConcreteObjectHeight); 287 if (isConcreteObjectSize && !(nameWithoutNatural in img.dataset)) { 288 img.dataset[nameWithoutNatural] = origExpectation; 289 } 290 291 // Construct a string for "data-not-rendered-{width,height}" for 292 // whichever axis we're currently handling, and stash the 293 // origExpectation in there if we don't already have some expectation 294 // set there: 295 let notRenderedName = name.replace("natural", "notRendered"); 296 if (!(notRenderedName in img.dataset)) { 297 img.dataset[notRenderedName] = origExpectation; 298 } 299 } 300 } 301 } 302 return clone; 303 } 304 305 // Clone and append a copy of the contents of imgTemplates, for testing: 306 let clone = imgTemplates.content.cloneNode("true"); 307 containingBlock.appendChild(clone); 308 309 // Append additional clones, modified to use srcset with specified density. 310 // Note: 311 // * '1' is the trivial case; we're just testing that for completeness, 312 // to be sure there aren't any unexpected differences between 313 // <img src="..."> and <img srcset="... 1x">). 314 // * It's best for whatever density we test here to be something that evenly 315 // divides all of the image sizes in this test (so 1 and 2 are easy choices). 316 containingBlock.appendChild(cloneTemplateWithDensity(1)); 317 containingBlock.appendChild(cloneTemplateWithDensity(2)); 318 319 // After all the img elements have loaded (indicated by the window load event), 320 // we run the various tests: 321 onload = function() { 322 Array.from(document.images).forEach(img => { 323 const expectedNaturalWidth = parseFloat(img.dataset.naturalWidth); 324 const expectedNaturalHeight = parseFloat(img.dataset.naturalHeight); 325 326 test(function() { 327 // We expect naturalWidth to match the provided data-natural-width 328 // (and similar for 'height'). 329 assert_equals(img.naturalWidth, expectedNaturalWidth, 'naturalWidth'); 330 assert_equals(img.naturalHeight, expectedNaturalHeight, 'naturalHeight'); 331 332 let shouldCheckWidthAndHeight = 333 !img.hasAttribute("size-comes-from-broken-image-icon"); 334 335 if (shouldCheckWidthAndHeight) { 336 // If 'data-width' is provided, then we expect img.width to match it. 337 // Otherwise we expect img.width to match the 'data-natural-width'. 338 // (And similar for 'height'.) 339 const expectedWidth = 'width' in img.dataset ? 340 parseFloat(img.dataset.width) : expectedNaturalWidth; 341 const expectedHeight = 'height' in img.dataset ? 342 parseFloat(img.dataset.height) : expectedNaturalHeight; 343 assert_equals(img.width, expectedWidth, 'width'); 344 assert_equals(img.height, expectedHeight, 'height'); 345 } 346 }, `${img.title}`); 347 348 test(function() { 349 // Now test what we get when the img is not rendered. 350 // * naturalWidth and naturalHeight shouldn't change. 351 // * width and height should generally match naturalWidth and 352 // naturalHeight. (Exceptions are indicated via the 353 // 'data-not-rendered-{width/height} attributes). 354 this.add_cleanup(function() { 355 img.style.display = ""; 356 }); 357 358 img.style.display = "none"; 359 img.getBoundingClientRect(); // Flush layout. 360 361 assert_equals(img.naturalWidth, expectedNaturalWidth, 362 'naturalWidth when not rendered'); 363 assert_equals(img.naturalHeight, expectedNaturalHeight, 364 'naturalHeight when not rendered'); 365 366 const expectedNotRenderedWidth = 'notRenderedWidth' in img.dataset ? 367 parseFloat(img.dataset.notRenderedWidth) : expectedNaturalWidth; 368 const expectedNotRenderedHeight = 'notRenderedHeight' in img.dataset ? 369 parseFloat(img.dataset.notRenderedHeight) : expectedNaturalHeight; 370 371 assert_equals(img.width, expectedNotRenderedWidth, 372 'width when not rendered'); 373 assert_equals(img.height, expectedNotRenderedHeight, 374 'height when not rendered'); 375 }, `${img.title} (when not rendered)`); 376 }); 377 done(); 378 }; 379 </script> 380 </body>