diff options
Diffstat (limited to 'testing/web-platform/tests/soft-navigation-heuristics/resources')
-rw-r--r-- | testing/web-platform/tests/soft-navigation-heuristics/resources/empty.html | 2 | ||||
-rw-r--r-- | testing/web-platform/tests/soft-navigation-heuristics/resources/soft-navigation-helper.js | 234 |
2 files changed, 236 insertions, 0 deletions
diff --git a/testing/web-platform/tests/soft-navigation-heuristics/resources/empty.html b/testing/web-platform/tests/soft-navigation-heuristics/resources/empty.html new file mode 100644 index 0000000000..5fa1cdf5e6 --- /dev/null +++ b/testing/web-platform/tests/soft-navigation-heuristics/resources/empty.html @@ -0,0 +1,2 @@ +<!DOCTYPE HTML> + diff --git a/testing/web-platform/tests/soft-navigation-heuristics/resources/soft-navigation-helper.js b/testing/web-platform/tests/soft-navigation-heuristics/resources/soft-navigation-helper.js new file mode 100644 index 0000000000..d1a60c15ef --- /dev/null +++ b/testing/web-platform/tests/soft-navigation-heuristics/resources/soft-navigation-helper.js @@ -0,0 +1,234 @@ +var counter = 0; +var clicked; +var timestamps = [] +const MAX_CLICKS = 50; +const URL = "foobar.html"; +const readValue = (value, defaultValue) => { + return value != undefined ? value : defaultValue; +} +const testSoftNavigation = + options => { + const addContent = options.addContent; + const link = options.link; + const pushState = readValue(options.pushState, + url=>{history.pushState({}, '', url)}); + const clicks = readValue(options.clicks, 1); + const extraValidations = readValue(options.extraValidations, + () => {}); + const testName = options.testName; + const pushUrl = readValue(options.pushUrl, true); + const eventType = readValue(options.eventType, "click"); + promise_test(async t => { + const preClickLcp = await getLcpEntries(); + setEvent(t, link, pushState, addContent, pushUrl, eventType); + for (let i = 0; i < clicks; ++i) { + let paint_entries_promise = waitOnPaintEntriesPromise(); + clicked = false; + click(link); + + await new Promise(resolve => { + (new PerformanceObserver(() => resolve())).observe({ + type: 'soft-navigation' + }); + }); + // Ensure paint timing entries are fired before moving on to the next + // click. + await paint_entries_promise; + } + assert_equals( + document.softNavigations, clicks, + 'Soft Navigations detected are the same as the number of clicks'); + await validateSoftNavigationEntry( + clicks, extraValidations, pushUrl); + + await runEntryValidations(preClickLcp); + }, testName); + }; + +const testNavigationApi = (testName, navigateEventHandler, link) => { + promise_test(async t => { + const preClickLcp = await getLcpEntries(); + navigation.addEventListener('navigate', navigateEventHandler); + click(link); + await new Promise(resolve => { + (new PerformanceObserver(() => resolve())).observe({ + type: 'soft-navigation' + }); + }); + assert_equals(document.softNavigations, 1, 'Soft Navigation detected'); + await validateSoftNavigationEntry(1, () => {}, 'foobar.html'); + + await runEntryValidations(preClickLcp); + }, testName); +}; + +const testSoftNavigationNotDetected = options => { + promise_test(async t => { + const preClickLcp = await getLcpEntries(); + options.eventTarget.addEventListener(options.eventName, options.eventHandler); + click(options.link); + await new Promise((resolve, reject) => { + (new PerformanceObserver(() => + reject("Soft navigation should not be triggered"))).observe({ + type: 'soft-navigation', + buffered: true + }); + t.step_timeout(resolve, 1000); + }); + assert_equals( + document.softNavigations, 0, 'Soft Navigation not detected'); + }, options.testName); + }; + +const runEntryValidations = async preClickLcp => { + await doubleRaf(); + validatePaintEntries('first-contentful-paint'); + validatePaintEntries('first-paint'); + const postClickLcp = await getLcpEntries(); + assert_greater_than( + postClickLcp.length, preClickLcp.length, + 'Soft navigation should have triggered at least an LCP entry'); + assert_not_equals( + postClickLcp[postClickLcp.length - 1].size, + preClickLcp[preClickLcp.length - 1].size, + 'Soft navigation LCP element should not have identical size to the hard ' + + 'navigation LCP element'); +}; + +const click = link => { + if (test_driver) { + test_driver.click(link); + timestamps[counter] = {"syncPostClick": performance.now()}; + } +} + +const doubleRaf = () => { + return new Promise(r => { + requestAnimationFrame(()=>requestAnimationFrame(r)); + }); +}; + +const setEvent = (t, button, pushState, addContent, pushUrl, eventType) => { + const eventObject = (eventType == "click") ? button : window; + eventObject.addEventListener(eventType, async e => { + timestamps[counter]["eventStart"] = performance.now(); + // Jump through a task, to ensure task tracking is working properly. + await new Promise(r => t.step_timeout(r, 0)); + + const url = URL + "?" + counter; + if (pushState) { + // Change the URL + if (pushUrl) { + pushState(url); + } else { + pushState(); + } + } + + // Wait 10 ms to make sure the timestamps are correct. + await new Promise(r => t.step_timeout(r, 10)); + + await addContent(url); + ++counter; + + clicked = true; + }); +}; + +const validateSoftNavigationEntry = async (clicks, extraValidations, + pushUrl) => { + const [entries, options] = await new Promise(resolve => { + (new PerformanceObserver((list, obs, options) => resolve( + [list.getEntries(), options]))).observe( + {type: 'soft-navigation', buffered: true}); + }); + const expectedClicks = Math.min(clicks, MAX_CLICKS); + + assert_equals(entries.length, expectedClicks, + "Performance observer got an entry"); + for (let i = 0; i < entries.length; ++i) { + const entry = entries[i]; + assert_true(entry.name.includes(pushUrl ? URL : document.location.href), + "The soft navigation name is properly set"); + const entryTimestamp = entry.startTime; + assert_less_than_equal(timestamps[i]["syncPostClick"], entryTimestamp); + assert_greater_than_equal( + timestamps[i]['eventStart'], entryTimestamp, + 'Event start timestamp matches'); + assert_not_equals(entry.navigationId, + performance.getEntriesByType("navigation")[0].navigationId, + "The navigation ID was incremented"); + if (i > 0) { + assert_not_equals(entry.navigationId, + entries[i-1].navigationId, + "The navigation ID was incremented between clicks"); + } + } + assert_equals(performance.getEntriesByType("soft-navigation").length, + expectedClicks, "Performance timeline got an entry"); + await extraValidations(entries, options); + +}; + +const validatePaintEntries = async (type, entries_number = 2) => { + const entries = await new Promise(resolve => { + (new PerformanceObserver(list => resolve( + list.getEntriesByName(type)))).observe( + {type: 'paint', buffered: true}); + }); + // TODO(crbug/1372997): investigate why this is not failing when multiple + // clicks are fired. Also, make sure the observer waits on the number of + // required clicks, instead of counting on double rAF. + assert_equals(entries.length, entries_number, + `There are ${entries_number} entries for ${type}`); + if (entries_number > 1) { + assert_not_equals(entries[0].startTime, entries[1].startTime, + "Entries have different timestamps for " + type); + } +}; + +const getLcpEntries = async () => { + const entries = await new Promise(resolve => { + (new PerformanceObserver(list => resolve( + list.getEntries()))).observe( + {type: 'largest-contentful-paint', buffered: true}); + }); + return entries; +}; + +const addImage = async (element) => { + const img = new Image(); + img.src = '/images/blue.png' + "?" + Math.random(); + await img.decode(); + element.appendChild(img); +}; +const addImageToMain = async () => { + await addImage(document.getElementById('main')); +}; + +const addTextToDivOnMain = () => { + const main = document.getElementById("main"); + const prevDiv = document.getElementsByTagName("div")[0]; + if (prevDiv) { + main.removeChild(prevDiv); + } + const div = document.createElement("div"); + const text = document.createTextNode("Lorem Ipsum"); + div.appendChild(text); + div.style="font-size: 3em"; + main.appendChild(div); +} + +const waitOnPaintEntriesPromise = () => { + return new Promise((resolve, reject) => { + const paint_entries = [] + new PerformanceObserver(list => { + paint_entries.push(...list.getEntries()); + if (paint_entries.length == 2) { + resolve(); + } else if (paint_entries.length > 2) { + reject(); + } + }).observe({type: 'paint'}); + }); +}; |