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 src and srcset
// and that img.src still behaves by the older spec. (Bug 1076583)
SimpleTest.waitForExplicitFinish();
// 50x50 png
var testPNG50 = new URL("image_50.png?noCache=" + Math.random(), location).href;
// 100x100 png
var testPNG100 = new URL("image_100.png?noCache=" + Math.random(), location).href;
// 200x200 png
var testPNG200 = new URL("image_200.png?noCache=" + Math.random(), location).href;
var tests = [];
var img;
var expectingErrors = 0;
var expectingLoads = 0;
var afterExpectCallback;
function onImgLoad() {
ok(expectingLoads > 0, "expected load");
if (expectingLoads > 0) {
expectingLoads--;
}
if (!expectingLoads && !expectingErrors && afterExpectCallback) {
setTimeout(afterExpectCallback, 0);
afterExpectCallback = null;
}
}
function onImgError() {
ok(expectingErrors > 0, "expected error");
if (expectingErrors > 0) {
expectingErrors--;
}
if (!expectingLoads && !expectingErrors && afterExpectCallback) {
setTimeout(afterExpectCallback, 0);
afterExpectCallback = null;
}
}
function expectEvents(loads, errors, callback) {
let p = new Promise(resolve => {
if (!loads && !errors) {
setTimeout(resolve, 0);
} else {
expectingLoads += loads;
expectingErrors += errors;
info("Waiting for " + expectingLoads + " load and " + expectingErrors + " error events");
afterExpectCallback = resolve;
}
});
return p.then(() => callback && callback());
}
//
// Test that img.src still does some work synchronously per the older spec (bug 1076583)
//
tests.push(async function test1() {
info("test 1");
img.src = testPNG50;
is(img.currentSrc, "", "Should not have synchronously selected source");
await expectEvents(1, 0);
is(img.currentSrc, testPNG50, "Should now have testPNG50 as current request");
// Assigning a wrong URL should not trigger error event (bug 1321300).
img.src = '//:0'; // Wrong URL
is(img.currentSrc, "", "Should have dropped current request sync");
img.src = "non_existent_image.404";
is(img.currentSrc, "", "Should still have empty current request");
await expectEvents(0, 1);
ok(img.currentSrc.endsWith("non_existent_image.404"), "Should have asynchronously selected source");
img.removeAttribute("src");
is(img.currentSrc, "", "Should have dropped currentSrc sync");
// Load another image while previous load is still pending
img.src = testPNG200;
is(img.currentSrc, "", "Should asynchronously load selected source");
await expectEvents(1, 0);
is(img.currentSrc, testPNG200, "Should have asynchronously loaded selected source");
nextTest();
});
// Setting srcset should be async
tests.push(function () {
info("test 2");
img.srcset = testPNG100;
is(img.currentSrc, testPNG200, "Should still have testPNG200 as current request");
expectEvents(1, 0, function() {
is(img.currentSrc, testPNG100, "Should now have testPNG100 as current request");
nextTest();
});
});
// Setting srcset, even to no ultimate effect, should trigger a reload
tests.push(function () {
info("test 3");
img.srcset = testPNG100 + " 1x, " + testPNG200 + " 2x";
is(img.currentSrc, testPNG100, "Should still have testPNG100 as current request");
expectEvents(1, 0, function() {
is(img.currentSrc, testPNG100, "Should still have testPNG100 as current request");
nextTest();
});
});
// Should switch to src as 1x source
tests.push(function () {
info("test 4");
img.srcset = testPNG50 + " 2x";
is(img.currentSrc, testPNG100, "Should still have testPNG100 as current request");
expectEvents(1, 0, function() {
is(img.currentSrc, testPNG200, "Should now have testPNG200 as current request");
nextTest();
});
});
// Changing src while we have responsive attributes should not be sync
tests.push(function () {
info("test 5");
img.src = testPNG100;
is(img.currentSrc, testPNG200, "Should still have testPNG200 as current request");
expectEvents(1, 0, function() {
is(img.currentSrc, testPNG100, "Should now have testPNG100 as current request");
// Switch to using srcset again for next test
img.srcset = testPNG100;
expectEvents(1, 0, nextTest);
});
});
// img.src = img.src should trigger an async event even in responsive mode
tests.push(function () {
info("test 6");
is(img.currentSrc, testPNG100, "Should now have testPNG100 as current request");
// eslint-disable-next-line no-self-assign
img.src = img.src;
is(img.currentSrc, testPNG100, "Should still have testPNG100 as current request");
expectEvents(1, 0, nextTest);
});
// img.srcset = img.srcset should be a no-op
tests.push(function () {
info("test 7");
// eslint-disable-next-line no-self-assign
img.srcset = img.srcset;
is(img.currentSrc, testPNG100, "Should still have testPNG100 as current request");
expectEvents(0, 0, nextTest);
});
// re-binding the image to the document should be a no-op
tests.push(function () {
info("test 8");
document.body.appendChild(img);
is(img.currentSrc, testPNG100, "Should still have testPNG100 as current request");
expectEvents(0, 0, nextTest);
});
// We should re-run our selection algorithm when any load event occurs
tests.push(function () {
info("test 9");
img.srcset = testPNG50 + " 1x, " + testPNG200 + " 2x";
is(img.currentSrc, testPNG100, "Should still have testPNG100 as current request");
expectEvents(1, 0, function() {
is(img.currentSrc, testPNG50, "Should now have testPNG50 as current request");
// The preference change will trigger a load, as the image will change
SpecialPowers.pushPrefEnv({'set': [ ["layout.css.devPixelsPerPx", "2.0"] ] });
expectEvents(1, 0, function() {
is(img.currentSrc, testPNG200, "Should now have testPNG200 as current request");
// eslint-disable-next-line no-self-assign
img.src = img.src;
is(img.currentSrc, testPNG200, "Should still have testPNG200 as current request");
// img.src = img.src is special-cased by the spec. It should always
// trigger an load event
expectEvents(1, 0, function() {
is(img.currentSrc, testPNG200, "Should still have testPNG200 as current request");
expectEvents(0, 0, nextTest);
});
})
});
});
// Removing srcset attr should async switch back to src
tests.push(async function () {
info("test 10");
is(img.currentSrc, testPNG200, "Should have testPNG200 as current request");
img.removeAttribute("srcset");
is(img.currentSrc, testPNG100, "Should testPNG100 as current request (hits sync load case for src)");
await expectEvents(1, 0);
is(img.currentSrc, testPNG100, "Should now have testPNG100 as current request");
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(async function() {
await (tests.shift())();
}, 0);
} else {
// Remove the event listeners to prevent the prefenv being popped from
// causing test failures.
img.removeEventListener("load", onImgLoad);
img.removeEventListener("error", onImgError);
SimpleTest.finish();
}
}
addEventListener("load", function() {
SpecialPowers.pushPrefEnv({'set': [["layout.css.devPixelsPerPx", "1.0"]] },
function() {
// Create this after the pref is set, as it is guarding webIDL attributes
img = document.createElement("img");
img.addEventListener("load", onImgLoad);
img.addEventListener("error", onImgError);
document.body.appendChild(img);
setTimeout(nextTest, 0);
});
});
</script>
</body>
</html>