<script src="resources/testharness.js"></script>
<script>
// Tell testharness.js to not wait for 'real' tests; we only want
// testharness.js for its assertion helpers.
setup({'output': false});
</script>
<script>
// 'AsyncBuffer' serves as a helper to buffer LCP reports asynchronously.
class AsyncBuffer {
constructor() {
// 'pending' is an array that will buffer entries reported through the
// PerformanceObserver and can be collected with 'pop'.
this.pending = [];
// 'resolve_fn' is a reference to the 'resolve' function of a
// Promise that blocks for new entries to arrive via 'push()'. Calling
// the function resolves the promise and unblocks calls to 'pop()'.
this.resolve_fn = null;
}
// Concatenates the given 'entries' list to this AsyncBuffer.
push(entries) {
if (entries.length == 0) {
throw new Error("Must not push an empty list of entries!");
}
this.pending = this.pending.concat(entries);
// If there are calls to 'pop' that are blocked waiting for items, signal
// that they can continue.
if (this.resolve_fn != null) {
this.resolve_fn();
this.resolve_fn = null;
}
}
// Takes the current pending entries from this AsyncBuffer. If there are no
// entries queued already, this will block until some show up.
async pop() {
if (this.pending.length == 0) {
// Need to instantiate a promise to block on. The next call to 'push'
// will resolve the promise once it has queued the entries.
await new Promise(resolve => {
this.resolve_fn = resolve;
});
}
assert_true(this.pending.length > 0);
const result = this.pending;
this.pending = [];
return result;
}
}
const buffer = new AsyncBuffer();
const po = new PerformanceObserver(entryList => {
buffer.push(entryList.getEntries());
});
po.observe({type: 'largest-contentful-paint', buffered: true});
</script>
<div id="content_div_1"></div>
<div id="content_div_2"></div>
<script>
const block_for_next_lcp = async () => {
return buffer.pop().then(seen_events => {
// This test case assumes each LCP entry is handled before the next could
// possibly be generated.
assert_equals(seen_events.length, 1);
return seen_events[0];
});
};
// Adds the first image "lcp-16x16.png" to "content_div_1". We expect this
// operation to trigger a new LCP entry.
const add_first_image = () => {
let img = document.createElement("img");
content_div_1.appendChild(img);
img.src = "images/lcp-16x16.png";
};
// Adds another image that is larger than "lcp-16x16.png". We expect this
// operation to trigger a new LCP entry.
const add_larger_image = () => {
let new_img = document.createElement("img");
content_div_2.appendChild(new_img);
new_img.src = "images/lcp-96x96.png";
new_img.id = "larger_image";
};
// Adds the largest image. We expect this
// operation to trigger a new LCP entry.
const add_largest_image = () => {
let new_img = document.createElement("img");
content_div_2.appendChild(new_img);
new_img.src = "images/lcp-256x256.png";
};
// Removes the image added by 'add_larger_image'. We expect this operation to
// not trigger a new LCP entry.
const remove_larger_image = () => {
const larger_image = document.getElementById("larger_image");
assert_not_equals(larger_image, null);
content_div_2.removeChild(larger_image);
};
const waitForAnimationFrames = frameCount => {
return new Promise(resolve => {
const handleFrame = () => {
if (--frameCount <= 0)
resolve();
else
requestAnimationFrame(handleFrame);
};
requestAnimationFrame(handleFrame);
});
};
const test_first_image = async () => {
// This test exercises the following scenario
// - have an initial page load with an image
// - assert that LCP fires for that image
add_first_image();
const lcp = await block_for_next_lcp();
// Now that we've run through the scenario and collected our measurements,
// return them in a structure that the C++ side can easily query.
let output = [
// lcp
{
url: lcp.url,
time: lcp.startTime
}];
return output;
};
const test_larger_image = async () => {
// This test exercises the following scenario
// - add a larger image to the page
// - assert that LCP fires for the new image
// - remove the larger image
// - wait for some rAFs
add_larger_image();
const lcp = await block_for_next_lcp();
remove_larger_image();
await waitForAnimationFrames(3);
// Now that we've run through the scenario and collected our measurements,
// return them in a structure that the C++ side can easily query.
let output = [
// lcp
{
url: lcp.url,
time: lcp.startTime
}];
return output;
};
const test_largest_image = async () => {
// This test exercises the following scenario
// - add the largest image to the page
// - assert that the new LCP is for the largest
add_largest_image();
const lcp = await block_for_next_lcp();
// Now that we've run through the scenario and collected our measurements,
// return them in a structure that the C++ side can easily query.
let output = [
// lcp
{
url: lcp.url,
time: lcp.startTime
}];
return output;
};
</script>