Source code
Revision control
Copy as Markdown
Other Tools
Test Info:
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<title>Image srcset mutations</title>
<script src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
</head>
<body>
<script type="application/javascript">
"use strict";
// Tests the relevant mutations part of the spec for <img> inside <picture> tags
SimpleTest.waitForExplicitFinish();
// 50x50 png
var testPNG50 = new URL("image_50.png", location).href;
// 100x100 png
var testPNG100 = new URL("image_100.png", location).href;
// 200x200 png
var testPNG200 = new URL("image_200.png", location).href;
var tests = [];
var img;
var picture;
var source1;
var source2;
var source3;
var expectingErrors = 0;
var expectingLoads = 0;
var afterExpectCallback;
function onImgLoad() {
ok(expectingLoads > 0, "expected load");
if (expectingLoads > 0) {
expectingLoads--;
}
if (!expectingLoads && !expectingErrors) {
setTimeout(afterExpectCallback, 0);
}
}
function onImgError() {
ok(expectingErrors > 0, "expected error");
if (expectingErrors > 0) {
expectingErrors--;
}
if (!expectingLoads && !expectingErrors) {
setTimeout(afterExpectCallback, 0);
}
}
function expectEvents(loads, errors, callback) {
if (!loads && !errors) {
setTimeout(callback, 0);
} else {
expectingLoads += loads;
expectingErrors += errors;
info("Waiting for " + expectingLoads + " load and " + expectingErrors + " error events");
afterExpectCallback = callback;
}
}
// Setup image outside the tree dom, make sure it loads
tests.push(function() {
info("test 1");
img.srcset = testPNG100;
img.src = testPNG50;
is(img.currentSrc, '', "Should not have synchronously selected source");
// No events should have fired synchronously, now we should get just one load (and no 404 error)
expectEvents(1, 0, nextTest);
});
// Binding to an empty picture should trigger an event, even if source doesn't change
tests.push(function() {
info("test 2");
is(img.currentSrc, testPNG100, "Should have loaded testPNG100");
document.body.appendChild(picture);
picture.appendChild(img);
is(img.currentSrc, testPNG100, "Should still have testPNG100");
expectEvents(1, 0, nextTest);
});
// inserting and removing an empty source before the image should both trigger a no-op reload
tests.push(function() {
info("test 3");
is(img.currentSrc, testPNG100, "Should still have testPNG100");
picture.insertBefore(source1, img);
is(img.currentSrc, testPNG100, "Should still have testPNG100");
// should fire one event, not change source
expectEvents(1, 0, function() {
is(img.currentSrc, testPNG100, "Should still have testPNG100");
picture.removeChild(source1);
is(img.currentSrc, testPNG100, "Should still have testPNG100");
// Should also no-op fire
expectEvents(1, 0, nextTest);
});
});
// insert and remove valid source before
tests.push(function() {
info("test 4");
is(img.currentSrc, testPNG100, "Should still have testPNG100");
// Insert source1 before img with valid candidate
source1.srcset = testPNG50;
picture.insertBefore(source1, img);
is(img.currentSrc, testPNG100, "Should still have testPNG100");
// should fire one event, change to the source
expectEvents(1, 0, function() {
is(img.currentSrc, testPNG50, "Should have switched to testPNG50");
picture.removeChild(source1);
is(img.currentSrc, testPNG50, "Should still have testPNG50");
// Should also no-op fire
expectEvents(1, 0, function() {
is(img.currentSrc, testPNG100, "Should have returned to testPNG100");
nextTest();
});
});
});
// insert and remove valid source after
tests.push(function() {
info("test 5");
is(img.currentSrc, testPNG100, "Should still have testPNG100");
// Insert source1 before img with valid candidate
source1.srcset = testPNG50;
picture.appendChild(source1);
is(img.currentSrc, testPNG100, "Should still have testPNG100");
// should fire nothing, no action
expectEvents(0, 0, function() {
is(img.currentSrc, testPNG100, "Should still have testPNG100");
// Same with removing
picture.removeChild(source1);
expectEvents(0, 0, function() {
is(img.currentSrc, testPNG100, "Should still have testPNG100");
nextTest();
});
});
});
// Should re-consider earlier sources when a load event occurs.
tests.push(function() {
info("test 6");
// Insert two valid sources, with MQ causing us to select the second
source1.srcset = testPNG50 + " 1x";
source1.media = "(min-resolution: 2dppx)"; // Wont match, test starts at 1x
source2.srcset = testPNG200;
picture.insertBefore(source1, img);
picture.insertBefore(source2, img);
is(img.currentSrc, testPNG100, "Should still have testPNG100");
// should get one load, selecting source2
expectEvents(1, 0, function() {
is(img.currentSrc, testPNG200, "Should have selected testPNG200");
expectEvents(1, 0, function() {
is(img.currentSrc, testPNG50, "Should have switched to testPNG50");
// Now add a source *also* wanting that DPI *just before* the
// selected source. Properly re-running the algorithm should
// re-consider all sources and thus go back to the first
// source, not just the valid source just inserted before us.
source3.media = source1.media;
source3.srcset = testPNG100;
picture.insertBefore(source3, source2);
// This should trigger a reload, but we should re-consider
// source1 and remain with that, not just the newly added source2
expectEvents(1, 0, function() {
is(img.currentSrc, testPNG50, "Should have remained on testPNG50");
expectEvents(0, 0, nextTest);
});
});
// Switch DPI to match the first source.
SpecialPowers.pushPrefEnv({'set': [ ["layout.css.devPixelsPerPx", "2.0"] ] });
});
});
// insert and remove valid source after our current source should
// trigger a reload, but not switch source
tests.push(function() {
info("test 7");
// Should be using source1 from last test
is(img.currentSrc, testPNG50, "Should still have testPNG50");
// Remove source2, should trigger an event even though we would
// not switch
picture.removeChild(source2);
expectEvents(1, 0, function() {
is(img.currentSrc, testPNG50, "Should still have testPNG50");
// Same with re-adding
picture.insertBefore(source2, img);
expectEvents(1, 0, function() {
is(img.currentSrc, testPNG50, "Should still have testPNG50");
expectEvents(0, 0, nextTest);
});
});
});
// Changing source attributes should trigger updates
tests.push(function() {
info("test 8");
// Should be using source1 from last test
is(img.currentSrc, testPNG50, "Should still have testPNG50");
// Reconfigure source1 to have empty srcset. Should switch to
// next source due to becoming invalid.
source1.srcset = "";
is(img.currentSrc, testPNG50, "Should still have testPNG50");
expectEvents(1, 0, function() {
is(img.currentSrc, testPNG100, "Should have switched to testPNG100");
// Give source1 valid sizes. Should trigger an event but not switch anywhere.
source1.sizes = "100vw";
expectEvents(1, 0, function() {
is(img.currentSrc, testPNG100, "Should still have testPNG100");
// And a valid MQ
source1.media = "(min-resolution: 1dppx)";
expectEvents(1, 0, function() {
// And a valid type...
source1.type = "image/png";
expectEvents(1, 0, function() {
// Finally restore srcset, should trigger load and re-consider source1 which is valid again
source1.srcset = testPNG50;
expectEvents(1, 0, function() {
is(img.currentSrc, testPNG50, "Should have selected testPNG50");
expectEvents(0, 0, nextTest);
});
});
});
});
});
});
// Inserting a source after <img> and touching its attributes should all be no-ops
tests.push(function() {
info("test 9");
// Setup: source2
picture.removeChild(source2);
expectEvents(1, 0, function() {
is(img.currentSrc, testPNG50, "Should still have testPNG50");
source2.srcset = testPNG200;
source2.media = "";
source2.sizes = "100vw";
source2.type = "image/png";
// Append valid source
picture.appendChild(source2);
// Touch all the things (but keep it valid)
source2.srcset = testPNG100;
source2.media = "(min-resolution: 2dppx)";
source2.sizes = "50vw";
source2.type = "image/png";
// No event should fire. Source should not change.
expectEvents(0, 0, function() {
is(img.currentSrc, testPNG50, "Should still have testPNG50");
expectEvents(0, 0, nextTest);
});
});
});
function nextTest() {
if (tests.length) {
// Spin event loop to make sure no unexpected image events are
// pending (unexpected events will assert in the handlers)
setTimeout(function() {
(tests.shift())();
}, 0);
} else {
// We'll get a flood of load events due to prefs being popped while cleaning up.
// Ignore it all.
img.removeEventListener("load", onImgLoad);
img.removeEventListener("error", onImgError);
SimpleTest.finish();
}
}
addEventListener("load", function() {
SpecialPowers.pushPrefEnv({'set': [["layout.css.devPixelsPerPx", "1.0" ]] },
function() {
// Create these after the pref is set, as it is guarding webIDL attributes
img = document.createElement("img");
img.addEventListener("load", onImgLoad);
img.addEventListener("error", onImgError);
picture = document.createElement("picture");
source1 = document.createElement("source");
source2 = document.createElement("source");
source3 = document.createElement("source");
setTimeout(nextTest, 0);
});
});
</script>
</body>
</html>