test_picture_mutations.html (10948B)
1 <!DOCTYPE HTML> 2 <html> 3 <head> 4 <meta charset="utf-8"> 5 <title>Image srcset mutations</title> 6 <script src="/tests/SimpleTest/SimpleTest.js"></script> 7 <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> 8 </head> 9 <body> 10 <script type="application/javascript"> 11 "use strict"; 12 13 // Tests the relevant mutations part of the spec for <img> inside <picture> tags 14 // https://html.spec.whatwg.org/#relevant-mutations 15 SimpleTest.waitForExplicitFinish(); 16 17 // 50x50 png 18 var testPNG50 = new URL("image_50.png", location).href; 19 // 100x100 png 20 var testPNG100 = new URL("image_100.png", location).href; 21 // 200x200 png 22 var testPNG200 = new URL("image_200.png", location).href; 23 24 var tests = []; 25 var img; 26 var picture; 27 var source1; 28 var source2; 29 var source3; 30 var expectingErrors = 0; 31 var expectingLoads = 0; 32 var afterExpectCallback; 33 34 function onImgLoad() { 35 ok(expectingLoads > 0, "expected load"); 36 if (expectingLoads > 0) { 37 expectingLoads--; 38 } 39 if (!expectingLoads && !expectingErrors) { 40 setTimeout(afterExpectCallback, 0); 41 } 42 } 43 function onImgError() { 44 ok(expectingErrors > 0, "expected error"); 45 if (expectingErrors > 0) { 46 expectingErrors--; 47 } 48 if (!expectingLoads && !expectingErrors) { 49 setTimeout(afterExpectCallback, 0); 50 } 51 } 52 function expectEvents(loads, errors, callback) { 53 if (!loads && !errors) { 54 setTimeout(callback, 0); 55 } else { 56 expectingLoads += loads; 57 expectingErrors += errors; 58 info("Waiting for " + expectingLoads + " load and " + expectingErrors + " error events"); 59 afterExpectCallback = callback; 60 } 61 } 62 63 // Setup image outside the tree dom, make sure it loads 64 tests.push(function() { 65 info("test 1"); 66 img.srcset = testPNG100; 67 img.src = testPNG50; 68 is(img.currentSrc, '', "Should not have synchronously selected source"); 69 70 // No events should have fired synchronously, now we should get just one load (and no 404 error) 71 expectEvents(1, 0, nextTest); 72 }); 73 74 // Binding to an empty picture should trigger an event, even if source doesn't change 75 tests.push(function() { 76 info("test 2"); 77 is(img.currentSrc, testPNG100, "Should have loaded testPNG100"); 78 document.body.appendChild(picture); 79 picture.appendChild(img); 80 is(img.currentSrc, testPNG100, "Should still have testPNG100"); 81 82 expectEvents(1, 0, nextTest); 83 }); 84 85 // inserting and removing an empty source before the image should both trigger a no-op reload 86 tests.push(function() { 87 info("test 3"); 88 is(img.currentSrc, testPNG100, "Should still have testPNG100"); 89 picture.insertBefore(source1, img); 90 is(img.currentSrc, testPNG100, "Should still have testPNG100"); 91 92 // should fire one event, not change source 93 expectEvents(1, 0, function() { 94 is(img.currentSrc, testPNG100, "Should still have testPNG100"); 95 picture.removeChild(source1); 96 is(img.currentSrc, testPNG100, "Should still have testPNG100"); 97 98 // Should also no-op fire 99 expectEvents(1, 0, nextTest); 100 }); 101 }); 102 103 // insert and remove valid source before 104 tests.push(function() { 105 info("test 4"); 106 is(img.currentSrc, testPNG100, "Should still have testPNG100"); 107 108 // Insert source1 before img with valid candidate 109 source1.srcset = testPNG50; 110 picture.insertBefore(source1, img); 111 is(img.currentSrc, testPNG100, "Should still have testPNG100"); 112 113 // should fire one event, change to the source 114 expectEvents(1, 0, function() { 115 is(img.currentSrc, testPNG50, "Should have switched to testPNG50"); 116 picture.removeChild(source1); 117 is(img.currentSrc, testPNG50, "Should still have testPNG50"); 118 119 // Should also no-op fire 120 expectEvents(1, 0, function() { 121 is(img.currentSrc, testPNG100, "Should have returned to testPNG100"); 122 nextTest(); 123 }); 124 }); 125 }); 126 127 // insert and remove valid source after 128 tests.push(function() { 129 info("test 5"); 130 is(img.currentSrc, testPNG100, "Should still have testPNG100"); 131 132 // Insert source1 before img with valid candidate 133 source1.srcset = testPNG50; 134 picture.appendChild(source1); 135 is(img.currentSrc, testPNG100, "Should still have testPNG100"); 136 137 // should fire nothing, no action 138 expectEvents(0, 0, function() { 139 is(img.currentSrc, testPNG100, "Should still have testPNG100"); 140 141 // Same with removing 142 picture.removeChild(source1); 143 expectEvents(0, 0, function() { 144 is(img.currentSrc, testPNG100, "Should still have testPNG100"); 145 nextTest(); 146 }); 147 }); 148 }); 149 150 // Should re-consider earlier sources when a load event occurs. 151 tests.push(function() { 152 info("test 6"); 153 154 // Insert two valid sources, with MQ causing us to select the second 155 source1.srcset = testPNG50 + " 1x"; 156 source1.media = "(min-resolution: 2dppx)"; // Wont match, test starts at 1x 157 source2.srcset = testPNG200; 158 picture.insertBefore(source1, img); 159 picture.insertBefore(source2, img); 160 is(img.currentSrc, testPNG100, "Should still have testPNG100"); 161 162 // should get one load, selecting source2 163 expectEvents(1, 0, function() { 164 is(img.currentSrc, testPNG200, "Should have selected testPNG200"); 165 166 expectEvents(1, 0, function() { 167 is(img.currentSrc, testPNG50, "Should have switched to testPNG50"); 168 169 // Now add a source *also* wanting that DPI *just before* the 170 // selected source. Properly re-running the algorithm should 171 // re-consider all sources and thus go back to the first 172 // source, not just the valid source just inserted before us. 173 source3.media = source1.media; 174 source3.srcset = testPNG100; 175 picture.insertBefore(source3, source2); 176 // This should trigger a reload, but we should re-consider 177 // source1 and remain with that, not just the newly added source2 178 expectEvents(1, 0, function() { 179 is(img.currentSrc, testPNG50, "Should have remained on testPNG50"); 180 expectEvents(0, 0, nextTest); 181 }); 182 }); 183 184 // Switch DPI to match the first source. 185 SpecialPowers.pushPrefEnv({'set': [ ["layout.css.devPixelsPerPx", "2.0"] ] }); 186 }); 187 }); 188 189 // insert and remove valid source after our current source should 190 // trigger a reload, but not switch source 191 tests.push(function() { 192 info("test 7"); 193 // Should be using source1 from last test 194 is(img.currentSrc, testPNG50, "Should still have testPNG50"); 195 196 // Remove source2, should trigger an event even though we would 197 // not switch 198 199 picture.removeChild(source2); 200 expectEvents(1, 0, function() { 201 is(img.currentSrc, testPNG50, "Should still have testPNG50"); 202 203 // Same with re-adding 204 picture.insertBefore(source2, img); 205 expectEvents(1, 0, function() { 206 is(img.currentSrc, testPNG50, "Should still have testPNG50"); 207 expectEvents(0, 0, nextTest); 208 }); 209 }); 210 }); 211 212 // Changing source attributes should trigger updates 213 tests.push(function() { 214 info("test 8"); 215 // Should be using source1 from last test 216 is(img.currentSrc, testPNG50, "Should still have testPNG50"); 217 218 // Reconfigure source1 to have empty srcset. Should switch to 219 // next source due to becoming invalid. 220 source1.srcset = ""; 221 is(img.currentSrc, testPNG50, "Should still have testPNG50"); 222 223 expectEvents(1, 0, function() { 224 is(img.currentSrc, testPNG100, "Should have switched to testPNG100"); 225 226 // Give source1 valid sizes. Should trigger an event but not switch anywhere. 227 source1.sizes = "100vw"; 228 229 expectEvents(1, 0, function() { 230 is(img.currentSrc, testPNG100, "Should still have testPNG100"); 231 232 // And a valid MQ 233 source1.media = "(min-resolution: 1dppx)"; 234 expectEvents(1, 0, function() { 235 // And a valid type... 236 source1.type = "image/png"; 237 expectEvents(1, 0, function() { 238 // Finally restore srcset, should trigger load and re-consider source1 which is valid again 239 source1.srcset = testPNG50; 240 expectEvents(1, 0, function() { 241 is(img.currentSrc, testPNG50, "Should have selected testPNG50"); 242 expectEvents(0, 0, nextTest); 243 }); 244 }); 245 }); 246 }); 247 }); 248 }); 249 250 // Inserting a source after <img> and touching its attributes should all be no-ops 251 tests.push(function() { 252 info("test 9"); 253 // Setup: source2 254 picture.removeChild(source2); 255 256 expectEvents(1, 0, function() { 257 is(img.currentSrc, testPNG50, "Should still have testPNG50"); 258 259 source2.srcset = testPNG200; 260 source2.media = ""; 261 source2.sizes = "100vw"; 262 source2.type = "image/png"; 263 // Append valid source 264 picture.appendChild(source2); 265 // Touch all the things (but keep it valid) 266 source2.srcset = testPNG100; 267 source2.media = "(min-resolution: 2dppx)"; 268 source2.sizes = "50vw"; 269 source2.type = "image/png"; 270 271 // No event should fire. Source should not change. 272 expectEvents(0, 0, function() { 273 is(img.currentSrc, testPNG50, "Should still have testPNG50"); 274 expectEvents(0, 0, nextTest); 275 }); 276 }); 277 }); 278 279 function nextTest() { 280 if (tests.length) { 281 // Spin event loop to make sure no unexpected image events are 282 // pending (unexpected events will assert in the handlers) 283 setTimeout(function() { 284 (tests.shift())(); 285 }, 0); 286 } else { 287 // We'll get a flood of load events due to prefs being popped while cleaning up. 288 // Ignore it all. 289 img.removeEventListener("load", onImgLoad); 290 img.removeEventListener("error", onImgError); 291 SimpleTest.finish(); 292 } 293 } 294 295 addEventListener("load", function() { 296 SpecialPowers.pushPrefEnv({'set': [["layout.css.devPixelsPerPx", "1.0" ]] }, 297 function() { 298 // Create these after the pref is set, as it is guarding webIDL attributes 299 img = document.createElement("img"); 300 img.addEventListener("load", onImgLoad); 301 img.addEventListener("error", onImgError); 302 picture = document.createElement("picture"); 303 source1 = document.createElement("source"); 304 source2 = document.createElement("source"); 305 source3 = document.createElement("source"); 306 setTimeout(nextTest, 0); 307 }); 308 }); 309 </script> 310 </body> 311 </html>