let counter = 0; const loadImage = size => { return event => { let zoom; if (location.search.includes("replace")) { zoom = document.getElementById("image"); } else { zoom = new Image(); } zoom.src=`/images/green-${size}.png`; ++counter; zoom.elementTiming = "zoom" + counter; document.body.appendChild(zoom); } }; const loadBackgroundImage = size => { return event => { const div = document.createElement("div"); const [width, height] = size.split("x"); ++counter; div.style = `background-image: url(/images/green-${size}.png?${counter}); width: ${width}px; height: ${height}px`; div.elementTiming = "zoom" + counter; document.body.appendChild(div); } }; const registerMouseover = background => { const image = document.getElementById("image"); const span = document.getElementById("span"); const func = background ? loadBackgroundImage : loadImage; image.addEventListener("mouseover", func("100x50")); span.addEventListener("mouseover", func("256x256")); } const dispatch_mouseover = () => { span.dispatchEvent(new Event("mouseover")) }; const wait_for_lcp_entries = async entries_expected => { await new Promise(resolve => { let entries_seen = 0; const PO = new PerformanceObserver(list => { const entries = list.getEntries(); for (let entry of entries) { if (entry.url) { entries_seen++; } } if (entries_seen == entries_expected) { PO.disconnect(); resolve() } else if (entries_seen > entries_expected) { PO.disconnect(); reject(); } }); PO.observe({type: "largest-contentful-paint", buffered: true}); }); }; const wait_for_element_timing_entry = async identifier => { await new Promise(resolve => { const PO = new PerformanceObserver(list => { const entries = list.getEntries(); for (let entry of entries) { if (entry.identifier == identifier) { PO.disconnect(); resolve() } } }); PO.observe({type: "element", buffered: true}); }); }; const wait_for_resource_timing_entry = async name => { await new Promise(resolve => { const PO = new PerformanceObserver(list => { const entries = list.getEntries(); for (let entry of entries) { if (entry.name.includes(name)) { PO.disconnect(); resolve() } } }); PO.observe({type: "resource", buffered: true}); }); }; const run_mouseover_test = background => { promise_test(async t => { // await the first LCP entry await wait_for_lcp_entries(1); // Hover over the image registerMouseover(background); if (test_driver) { await new test_driver.Actions().pointerMove(0, 0, {origin: image}).send(); } if (!background) { await wait_for_element_timing_entry("zoom1"); } else { await wait_for_resource_timing_entry("png?1"); await new Promise(r => requestAnimationFrame(r)); } // There's only a single LCP entry, because the zoom was skipped. await wait_for_lcp_entries(1); // Wait 600 ms as the heuristic is 500 ms. // This will no longer be necessary once the heuristic relies on Task // Attribution. await new Promise(r => step_timeout(r, 600)); // Hover over the span. if (test_driver) { await new test_driver.Actions().pointerMove(0, 0, {origin: span}).send(); } if (!background) { await wait_for_element_timing_entry("zoom2"); } else { await wait_for_resource_timing_entry("png?2"); await new Promise(r => requestAnimationFrame(r)); } // There are 2 LCP entries, as the image loaded due to span hover is a // valid LCP candidate. await wait_for_lcp_entries(2); }, `LCP mouseover heuristics ignore ${background ? "background" : "element"}-based zoom widgets`); }