1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175
|
<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>
|