summaryrefslogtreecommitdiffstats
path: root/testing/web-platform/tests/soft-navigation-heuristics/resources
diff options
context:
space:
mode:
Diffstat (limited to 'testing/web-platform/tests/soft-navigation-heuristics/resources')
-rw-r--r--testing/web-platform/tests/soft-navigation-heuristics/resources/empty.html2
-rw-r--r--testing/web-platform/tests/soft-navigation-heuristics/resources/soft-navigation-helper.js234
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'});
+ });
+};