diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
commit | 26a029d407be480d791972afb5975cf62c9360a6 (patch) | |
tree | f435a8308119effd964b339f76abb83a57c29483 /testing/web-platform/tests/soft-navigation-heuristics | |
parent | Initial commit. (diff) | |
download | firefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz firefox-26a029d407be480d791972afb5975cf62c9360a6.zip |
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'testing/web-platform/tests/soft-navigation-heuristics')
51 files changed, 2284 insertions, 0 deletions
diff --git a/testing/web-platform/tests/soft-navigation-heuristics/back.tentative.html b/testing/web-platform/tests/soft-navigation-heuristics/back.tentative.html new file mode 100644 index 0000000000..349eaf465c --- /dev/null +++ b/testing/web-platform/tests/soft-navigation-heuristics/back.tentative.html @@ -0,0 +1,36 @@ +<!DOCTYPE HTML> +<html> +<head> +<meta charset="utf-8"> +<title>Detect simple soft navigation.</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/resources/testdriver.js"></script> +<script src="/resources/testdriver-vendor.js"></script> +<script src="resources/soft-navigation-helper.js"></script> +</head> +<body> + <main id=main> + <div> + <a id=link>Click me!</a> + </div> + </main> + <script> + // Push state a couple of times + history.pushState({}, "", "foobar.html"); + history.pushState({}, "", "anotherOne.html"); + const link = document.getElementById("link"); + testSoftNavigation({ + addContent: () => { + addTextToDivOnMain(); + }, + link: link, + pushState: url=>{history.back()}, + test: "`history.back() properly works with SoftNavigationHeuristics"}); + </script> +</body> +</html> + + + + diff --git a/testing/web-platform/tests/soft-navigation-heuristics/click-event-bubbles.tentative.html b/testing/web-platform/tests/soft-navigation-heuristics/click-event-bubbles.tentative.html new file mode 100644 index 0000000000..ee9d1e1233 --- /dev/null +++ b/testing/web-platform/tests/soft-navigation-heuristics/click-event-bubbles.tentative.html @@ -0,0 +1,28 @@ +<!DOCTYPE HTML> +<html> +<head> +<meta charset="utf-8"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/resources/testdriver.js"></script> +<script src="/resources/testdriver-vendor.js"></script> +<script src="resources/soft-navigation-helper.js"></script> +</head> +<body> + <main id=main> + <a id=link>Click me!</a> + </main> + <script> + const link = document.getElementById("link"); + // Adding a noop event that the "click" would bubble to. + document.getElementById("main").addEventListener("click", () => {}); + + testSoftNavigation({ + addContent: () => { + addTextParagraphToMain("Lorem Ipsum"); + }, + link: link, + test: "Ensure event bubbling works well with soft navigations."}); + </script> +</body> +</html> diff --git a/testing/web-platform/tests/soft-navigation-heuristics/disabled.html b/testing/web-platform/tests/soft-navigation-heuristics/disabled.html new file mode 100644 index 0000000000..b53c2f7012 --- /dev/null +++ b/testing/web-platform/tests/soft-navigation-heuristics/disabled.html @@ -0,0 +1,31 @@ +<!DOCTYPE HTML> +<html> +<head> +<meta charset="utf-8"> +<title>Detect simple soft navigation.</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/resources/testdriver.js"></script> +<script src="/resources/testdriver-vendor.js"></script> +<script src="resources/soft-navigation-helper.js"></script> +</head> +<body> + <main id=main> + <a id=link>Click me!</a> + </main> + <script> + const link = document.getElementById("link"); + testSoftNavigationNotDetected({ + eventHandler: url => { + addTextToDivOnMain(); + history.pushState({}, '', 'foobar.html'); + }, + link: link, + eventName: "click", + eventTarget: link, + testName: "Test that a soft navigation is not detected when the feature " + + "is disabled"}); + </script> +</body> +</html> + diff --git a/testing/web-platform/tests/soft-navigation-heuristics/dropped-entries.tentative.html b/testing/web-platform/tests/soft-navigation-heuristics/dropped-entries.tentative.html new file mode 100644 index 0000000000..d27ad452be --- /dev/null +++ b/testing/web-platform/tests/soft-navigation-heuristics/dropped-entries.tentative.html @@ -0,0 +1,42 @@ +<!DOCTYPE HTML> +<html> +<head> +<meta charset="utf-8"> +<meta name="timeout" content="long"> +<title>Detect simple soft navigation.</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/resources/testdriver.js"></script> +<script src="/resources/testdriver-vendor.js"></script> +<script src="resources/soft-navigation-helper.js"></script> +</head> +<body> + <main id=main> + <a id=link>Click me!</a> + </main> + <script> + const link = document.getElementById("link"); + + testSoftNavigation({ + addContent: async () => { + await addImageToMain('green-16x16.png'); + }, + link: link, + clicks: 52, + extraValidations: async (entries, options)=>{ + if (!performance.softNavPaintMetricsSupported) { + return; + } + const [paint_entries, paint_options] = await new Promise(resolve => { + new PerformanceObserver((list, obs, options) => + resolve([list.getEntries(), options])).observe( + {type: 'paint', buffered: true}); + }); + assert_equals(options['droppedEntriesCount'], 2); + assert_equals(paint_options['droppedEntriesCount'], 4); + }, + test: "Test that a soft navigation entries get dropped when buffer limits" + + " get exceeded."}); + </script> +</body> +</html> diff --git a/testing/web-platform/tests/soft-navigation-heuristics/first-interaction-not-softnav.tentative.html b/testing/web-platform/tests/soft-navigation-heuristics/first-interaction-not-softnav.tentative.html new file mode 100644 index 0000000000..e026202183 --- /dev/null +++ b/testing/web-platform/tests/soft-navigation-heuristics/first-interaction-not-softnav.tentative.html @@ -0,0 +1,36 @@ +<!DOCTYPE HTML> +<html> +<head> +<meta charset="utf-8"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/resources/testdriver.js"></script> +<script src="/resources/testdriver-vendor.js"></script> +<script src="resources/soft-navigation-helper.js"></script> +</head> +<body> + <main id=main> + <p><a id=firstlink>Click me!</a></p> + <p><a id=secondlink>Then click me!</a></p> + </main> + <script> + (async () => { + if (test_driver) { + const firstlink = document.getElementById("firstlink"); + const clickPromise = new Promise(r => { + firstlink.addEventListener("click", r); + }); + test_driver.click(firstlink); + await clickPromise; + } + })(); + const secondlink = document.getElementById("secondlink"); + testSoftNavigation({ + addContent: () => { + addImageToMain(); + }, + link: secondlink, + test: "first interaction in the middle of a soft navigation"}); + </script> +</body> +</html> diff --git a/testing/web-platform/tests/soft-navigation-heuristics/hash.tentative.html b/testing/web-platform/tests/soft-navigation-heuristics/hash.tentative.html new file mode 100644 index 0000000000..3a8419c71d --- /dev/null +++ b/testing/web-platform/tests/soft-navigation-heuristics/hash.tentative.html @@ -0,0 +1,29 @@ +<!DOCTYPE HTML> +<html> +<head> +<meta charset="utf-8"> +<title>Detect hashchange event.</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/resources/testdriver.js"></script> +<script src="/resources/testdriver-vendor.js"></script> +<script src="resources/soft-navigation-helper.js"></script> +</head> +<body> + <main id=main> + <div> + <a id=link>Click me!</a> + </div> + </main> + <script> + const link = document.getElementById("link"); + testSoftNavigation({ + addContent: () => { + addTextToDivOnMain(); + }, + link: link, + pushState: (url)=>{location.hash=url;}, + test: "Location hash changes properly works with SoftNavigationHeuristics"}); + </script> +</body> +</html>
\ No newline at end of file diff --git a/testing/web-platform/tests/soft-navigation-heuristics/image-lcp-before-detection-second-softnav.tentative.html b/testing/web-platform/tests/soft-navigation-heuristics/image-lcp-before-detection-second-softnav.tentative.html new file mode 100644 index 0000000000..4d26bb9269 --- /dev/null +++ b/testing/web-platform/tests/soft-navigation-heuristics/image-lcp-before-detection-second-softnav.tentative.html @@ -0,0 +1,60 @@ +<!DOCTYPE HTML> +<html> +<head> +<meta charset="utf-8"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/resources/testdriver.js"></script> +<script src="/resources/testdriver-vendor.js"></script> +<script src="resources/soft-navigation-helper.js"></script> +</head> +<body> + <main id=main> + <div> + <a id=link><img src="/images/lcp-256x256.png" id="img"></a> + </div> + </main> + <script> + const link = document.getElementById("link"); + let first_lcp_painted; + let second_lcp_painted; + (async () => { + await new Promise(r => { first_lcp_painted = r; }); + addImageToMain("lcp-133x106.png", "no_lcp"); + (new PerformanceObserver(second_lcp_painted)).observe({type: "element"}); + })(); + testSoftNavigation({ + pushState: null, + clicks: 2, + addContent: async () => { + // Add an LCP element. + await new Promise(resolve => { + addImageToMain("lcp-100x50.png", "first_lcp" + counter); + (new PerformanceObserver(resolve)).observe({type: "element"}); + }); + if(counter) { + first_lcp_painted(); + // Wait for the unrelated LCP to be painted. + await new Promise(r => { second_lcp_painted = r; }); + } + const url = URL + "?" + counter; + history.pushState({}, '', url); + }, + link: link, + validate: async () => { + const lcps = await getLcpEntries(); + const ref_counter = counter-1; + assert_equals(lcps.length, 2 + ref_counter, "Got 2 LCP entries"); + assert_equals(lcps[lcps.length - 1].id, "first_lcp" + ref_counter, + "Got the first LCP"); + }, + test: "Second soft navigation image LCP discovered between user " + + "interaction and soft navigation detection are properly " + + "reported, while unrelated LCPs are ignored during that time. " + + "As a side effect, we also test element timing."}); + </script> +</body> +</html> + + + diff --git a/testing/web-platform/tests/soft-navigation-heuristics/image-lcp-before-detection.tentative.html b/testing/web-platform/tests/soft-navigation-heuristics/image-lcp-before-detection.tentative.html new file mode 100644 index 0000000000..0de675d372 --- /dev/null +++ b/testing/web-platform/tests/soft-navigation-heuristics/image-lcp-before-detection.tentative.html @@ -0,0 +1,54 @@ +<!DOCTYPE HTML> +<html> +<head> +<meta charset="utf-8"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/resources/testdriver.js"></script> +<script src="/resources/testdriver-vendor.js"></script> +<script src="resources/soft-navigation-helper.js"></script> +</head> +<body> + <main id=main> + <div> + <a id=link><img src="/images/lcp-256x256.png" id="img"></a> + </div> + </main> + <script> + const link = document.getElementById("link"); + let first_lcp_painted; + let second_lcp_painted; + (async () => { + await new Promise(r => { first_lcp_painted = r; }); + addImageToMain("lcp-133x106.png", "no_lcp"); + (new PerformanceObserver(second_lcp_painted)).observe({type: "element"}); + })(); + testSoftNavigation({ + pushState: null, + addContent: async () => { + // Add an LCP element. + await new Promise(resolve => { + addImageToMain("lcp-100x50.png", "first_lcp"); + (new PerformanceObserver(resolve)).observe({type: "element"}); + }); + first_lcp_painted(); + // Wait for the unrelated LCP to be painted. + await new Promise(r => { second_lcp_painted = r; }); + const url = URL + "?" + counter; + history.pushState({}, '', url); + }, + link: link, + validate: async () => { + const lcps = await getLcpEntries(); + assert_equals(lcps.length, 2, "Got 2 LCP entries"); + assert_equals(lcps[lcps.length - 1].id, "first_lcp", "Got the first LCP"); + }, + test: "Image LCP discovered between user interaction and soft " + + "navigation detection are properly reported, while unrelated " + + "LCPs are ignored during that time. As a side effect, we also " + + "test element timing."}); + </script> +</body> +</html> + + diff --git a/testing/web-platform/tests/soft-navigation-heuristics/image-lcp-followed-by-image-softnav-lcp.tentative.html b/testing/web-platform/tests/soft-navigation-heuristics/image-lcp-followed-by-image-softnav-lcp.tentative.html new file mode 100644 index 0000000000..7a2018d20e --- /dev/null +++ b/testing/web-platform/tests/soft-navigation-heuristics/image-lcp-followed-by-image-softnav-lcp.tentative.html @@ -0,0 +1,32 @@ +<!DOCTYPE HTML> +<html> +<head> +<meta charset="utf-8"> +<title>Detect simple soft navigation.</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/resources/testdriver.js"></script> +<script src="/resources/testdriver-vendor.js"></script> +<script src="resources/soft-navigation-helper.js"></script> +</head> +<body> + <main id=main> + <div> + <a id=link><img src="/images/lcp-256x256.png"></a> + </div> + </main> + <script> + const link = document.getElementById("link"); + testSoftNavigation({ + addContent: async () => { + const main = document.getElementById("main"); + main.removeChild(document.getElementsByTagName("div")[0]); + await addImageToMain(); + }, + link: link, + test: "Test that an image LCP followup by a smaller soft navigation image LCP" + + " properly queues an LCP entry"}); + </script> +</body> +</html> + diff --git a/testing/web-platform/tests/soft-navigation-heuristics/image-lcp-followed-by-text-softnav-lcp.tentative.html b/testing/web-platform/tests/soft-navigation-heuristics/image-lcp-followed-by-text-softnav-lcp.tentative.html new file mode 100644 index 0000000000..501d1c5fb6 --- /dev/null +++ b/testing/web-platform/tests/soft-navigation-heuristics/image-lcp-followed-by-text-softnav-lcp.tentative.html @@ -0,0 +1,31 @@ +<!DOCTYPE HTML> +<html> +<head> +<meta charset="utf-8"> +<title>Detect simple soft navigation.</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/resources/testdriver.js"></script> +<script src="/resources/testdriver-vendor.js"></script> +<script src="resources/soft-navigation-helper.js"></script> +</head> +<body> + <main id=main> + <div> + <a id=link><img src="/images/lcp-256x256.png"></a> + </div> + </main> + <script> + const link = document.getElementById("link"); + testSoftNavigation({ + addContent: () => { + addTextToDivOnMain(); + }, + link: link, + test: "Test that an image LCP followup by a smaller soft navigation text LCP" + + " properly queues an LCP entry"}); + </script> +</body> +</html> + + diff --git a/testing/web-platform/tests/soft-navigation-heuristics/image-lcp-followed-by-two-image-softnavs-lcp.tentative.html b/testing/web-platform/tests/soft-navigation-heuristics/image-lcp-followed-by-two-image-softnavs-lcp.tentative.html new file mode 100644 index 0000000000..66a9e5136f --- /dev/null +++ b/testing/web-platform/tests/soft-navigation-heuristics/image-lcp-followed-by-two-image-softnavs-lcp.tentative.html @@ -0,0 +1,75 @@ +<!DOCTYPE HTML> +<html> +<head> +<meta charset="utf-8"> +<title>Detect simple soft navigation.</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/resources/testdriver.js"></script> +<script src="/resources/testdriver-vendor.js"></script> +<script src="resources/soft-navigation-helper.js"></script> +</head> +<body> + <main id=main> + <div> + <a id=link><img src="/images/lcp-256x256.png"></a> + </div> + </main> + <script> + const link = document.getElementById("link"); + + promise_test(async t => { + validatePaintEntries('first-contentful-paint', 1); + validatePaintEntries('first-paint', 1); + const preClickLcp = await getLcpEntries(); + setEvent(t, link, /*pushState=*/url=>history.pushState({}, '', url), + /*addContent=*/async () => await addImageToMain(), /*pushUrl=*/true, + /*eventType=*/"click"); + + const first_click_paint_promise = waitOnPaintEntriesPromise(); + + interact(link); + await new Promise(resolve => { + (new PerformanceObserver(resolve)).observe({ + type: 'soft-navigation' + }); + }); + assert_equals( + document.softNavigations, 1, + 'One Soft Navigation detected'); + + await first_click_paint_promise; + const postClickLcp = await getLcpEntries(); + assert_greater_than( + postClickLcp.length, preClickLcp.length, + 'Soft navigation should have triggered at least an LCP entry'); + assert_less_than( + postClickLcp[postClickLcp.length - 1].size, + preClickLcp[preClickLcp.length - 1].size, + 'Soft navigation LCP element should have a smaller size than the hard' + + ' navigation LCP element'); + + const second_click_paint_promise = waitOnPaintEntriesPromise(); + const preClickLcp2 = await getLcpEntries(); + interact(link); + await new Promise(resolve => { + (new PerformanceObserver(() => resolve())).observe({ + type: 'soft-navigation' + }); + }); + assert_equals( + document.softNavigations, 2, + 'Two Soft Navigations detected'); + + await second_click_paint_promise; + const postClickLcp2 = await getLcpEntries(); + assert_equals(postClickLcp2.length, 3, 'We expected 3 LCP entries at this point'); + assert_greater_than( + postClickLcp2.length, preClickLcp2.length, + 'Soft navigation should have triggered at least an LCP entry'); + }, "Multiple soft navigations get FP, FCP and LCP for each one"); + </script> +</body> +</html> + + diff --git a/testing/web-platform/tests/soft-navigation-heuristics/innertext.tentative.html b/testing/web-platform/tests/soft-navigation-heuristics/innertext.tentative.html new file mode 100644 index 0000000000..d40b604792 --- /dev/null +++ b/testing/web-platform/tests/soft-navigation-heuristics/innertext.tentative.html @@ -0,0 +1,32 @@ +<!DOCTYPE HTML> +<html> +<head> +<meta charset="utf-8"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/resources/testdriver.js"></script> +<script src="/resources/testdriver-vendor.js"></script> +<script src="resources/soft-navigation-helper.js"></script> +</head> +<body> + <main id=main> + <a id=link>Click me!</a> + <!-- This test fails if the paragraph below already has content. The reason + is that secondary paints are not being properly recorded, and hence don't + count for soft navigation heuristics. --> + <p id="softnav-content"></p> + </main> + <script> + const link = document.getElementById("link"); + testSoftNavigation({ + addContent: async () => { + document.getElementById("softnav-content").innerText = + "Lorem Ipsum dolor sit amet"; + }, + link: link, + test: "Soft navigation when only innerText was modified"}); + </script> +</body> +</html> + + diff --git a/testing/web-platform/tests/soft-navigation-heuristics/interaction-with-paint-before-back.tentative.html b/testing/web-platform/tests/soft-navigation-heuristics/interaction-with-paint-before-back.tentative.html new file mode 100644 index 0000000000..b587411991 --- /dev/null +++ b/testing/web-platform/tests/soft-navigation-heuristics/interaction-with-paint-before-back.tentative.html @@ -0,0 +1,76 @@ +<!DOCTYPE HTML> +<html> +<head> +<meta charset="utf-8"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/resources/testdriver.js"></script> +<script src="/resources/testdriver-vendor.js"></script> +<script src="resources/soft-navigation-helper.js"></script> +</head> +<body> + <main id=main> + <div> + <a id=link><img src="/images/lcp-256x256.png" id="img"></a> + <a id=not_nav><img src="/images/lcp-16x16.png"></a> + </div> + </main> + <script> + // Push state a couple of times + history.pushState({}, "", "foobar.html"); + history.pushState({}, "", "anotherOne.html"); + + (async () => { + const link = document.getElementById("link"); + // Trigger a user interaction that doesn't result in a soft navigation, but + // does paint. + await (async () => { + const not_nav = document.getElementById("not_nav"); + let non_soft_nav_click; + const non_soft_nav_click_promise = + new Promise(r => { non_soft_nav_click = r; }); + not_nav.addEventListener("click", () => { + addImageToMain("lcp-133x106.png", "not_soft_nav_image"); + (new PerformanceObserver(non_soft_nav_click)).observe({type: "element"}); + }); + if (test_driver) { + test_driver.click(not_nav); + } + await non_soft_nav_click_promise; + })(); + const url = URL + "?" + counter; + link.addEventListener("click", () => { + // Add an LCP element. + const img = new Image(); + img.src = '/images/lcp-100x500.png' + "?" + Math.random(); + document.getElementById("main").appendChild(img); + history.back(); + }); + promise_test(async t => { + if (test_driver) { + test_driver.click(link); + } + await waitOnSoftNav(); + assert_equals( + document.softNavigations, 1, + 'Single Soft Navigation detected'); + const [entries, options] = await new Promise(resolve => { + (new PerformanceObserver((list, obs, options) => resolve( + [list.getEntries(), options]))).observe( + {type: 'soft-navigation', buffered: true}); + }); + + assert_equals(entries.length, 1, + "Performance observer got an entry"); + }, "Ensure that soft navigation entry emitted through a synchronous " + + "event that modified DOM and committed a same document navigation, " + + "and that was preceded by a user intreaction that resulted in a " + + "contentful paint is properly detected."); + })(); + </script> +</body> +</html> + + + + diff --git a/testing/web-platform/tests/soft-navigation-heuristics/load-classic-script-history-push.tentative.html b/testing/web-platform/tests/soft-navigation-heuristics/load-classic-script-history-push.tentative.html new file mode 100644 index 0000000000..4e375b2164 --- /dev/null +++ b/testing/web-platform/tests/soft-navigation-heuristics/load-classic-script-history-push.tentative.html @@ -0,0 +1,31 @@ +<!DOCTYPE HTML> +<html> +<head> +<meta charset="utf-8"> +<title>Detect a soft navigation triggered by an external classic script.</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/resources/testdriver.js"></script> +<script src="/resources/testdriver-vendor.js"></script> +<script src="resources/soft-navigation-helper.js"></script> +</head> +<body> + <main id=main> + <a id=link>Click me!</a> + </main> + <script> + const link = document.getElementById("link"); + testSoftNavigation({ + addContent: async () => { + await addImageToMain(); + }, + link: link, + pushState: async (url) => { + const script = document.createElement("script"); + script.src = "resources/history_push.js"; + document.body.appendChild(script); + }, + test: "Detect a soft navigation triggered from external classic script"}); + </script> +</body> +</html> diff --git a/testing/web-platform/tests/soft-navigation-heuristics/load-module-script-history-push.tentative.html b/testing/web-platform/tests/soft-navigation-heuristics/load-module-script-history-push.tentative.html new file mode 100644 index 0000000000..e82cd13f4a --- /dev/null +++ b/testing/web-platform/tests/soft-navigation-heuristics/load-module-script-history-push.tentative.html @@ -0,0 +1,32 @@ +<!DOCTYPE HTML> +<html> +<head> +<meta charset="utf-8"> +<title>Detect a soft navigation triggered by an external module script.</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/resources/testdriver.js"></script> +<script src="/resources/testdriver-vendor.js"></script> +<script src="resources/soft-navigation-helper.js"></script> +</head> +<body> + <main id=main> + <a id=link>Click me!</a> + </main> + <script> + const link = document.getElementById("link"); + testSoftNavigation({ + addContent: async () => { + await addImageToMain(); + }, + link: link, + pushState: async (url) => { + const script = document.createElement("script"); + script.src = "resources/history_push.js"; + script.type = "module"; + document.body.appendChild(script); + }, + test: "Detect a soft navigation triggered from external module script"}); + </script> +</body> +</html> diff --git a/testing/web-platform/tests/soft-navigation-heuristics/multiple-nested-events.tentative.html b/testing/web-platform/tests/soft-navigation-heuristics/multiple-nested-events.tentative.html new file mode 100644 index 0000000000..e51841865d --- /dev/null +++ b/testing/web-platform/tests/soft-navigation-heuristics/multiple-nested-events.tentative.html @@ -0,0 +1,40 @@ +<!DOCTYPE HTML> +<html> +<head> +<meta charset="utf-8"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/resources/testdriver.js"></script> +<script src="/resources/testdriver-vendor.js"></script> +<script src="resources/soft-navigation-helper.js"></script> +</head> +<body> + <main id=main> + <a id=link>Click me!</a> + </main> + <script> + const link = document.getElementById("link"); + let should_navigate = true; + navigation.addEventListener("navigate", () => { + if (should_navigate) { + // It's the last sync navigation that determines the soft nav URL. + history.pushState({}, '', 'foobar.html'); + should_navigate = false; + } + }); + testSoftNavigation({ + eventPrepWork: url => { + addTextToDivOnMain(); + history.pushState({}, '', 'foobar1.html'); + // Here we're bypassing the regular test's event logic, as this test is + // fully sync. + return false; + }, + link: link, + eventName: "click", + eventTarget: link, + testName: "Test multiple nested navigate events"}); + </script> +</body> +</html> + diff --git a/testing/web-platform/tests/soft-navigation-heuristics/multiple-paint-entries-buffered.tentative.html b/testing/web-platform/tests/soft-navigation-heuristics/multiple-paint-entries-buffered.tentative.html new file mode 100644 index 0000000000..dbb945a0a8 --- /dev/null +++ b/testing/web-platform/tests/soft-navigation-heuristics/multiple-paint-entries-buffered.tentative.html @@ -0,0 +1,43 @@ +<!DOCTYPE HTML> +<html> +<head> +<meta charset="utf-8"> +<meta name="timeout" content="long"> +<title>Detect multiple soft navigations and ensure they buffer paint entries.</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/resources/testdriver.js"></script> +<script src="/resources/testdriver-vendor.js"></script> +<script src="resources/soft-navigation-helper.js"></script> +</head> +<body> + <main id=main> + <a id=link>Click me!</a> + </main> + <script> + const link = document.getElementById("link"); + + testSoftNavigation({ + addContent: async () => { + await addImageToMain(); + }, + link: link, + clicks: 4, + extraValidations: async (entries, options)=>{ + if (!performance.softNavPaintMetricsSupported) { + return; + } + const paint_entries = await new Promise(resolve => { + new PerformanceObserver(list => resolve(list.getEntries())).observe( + {type: 'paint', buffered: true, + includeSoftNavigationObservations: true}); + }); + assert_equals(paint_entries.length, 10); + }, + test: "Test that multiple soft navigation buffer paint entries"}); + </script> +</body> +</html> + + + diff --git a/testing/web-platform/tests/soft-navigation-heuristics/navigate-child.html b/testing/web-platform/tests/soft-navigation-heuristics/navigate-child.html new file mode 100644 index 0000000000..e3c17e2dba --- /dev/null +++ b/testing/web-platform/tests/soft-navigation-heuristics/navigate-child.html @@ -0,0 +1,30 @@ +<!DOCTYPE HTML> +<html> +<head> +<meta charset="utf-8"> +<title>Navigate a child window.</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/resources/testdriver.js"></script> +<script src="/resources/testdriver-vendor.js"></script> +<script src="resources/soft-navigation-helper.js"></script> +</head> +<body> + <main id=main> + <a href="empty.html?2" rel=opener target=child id=link>Click me!</a> + </main> + <script> + promise_test(async t => { + const child = window.open("resources/empty.html?1", "child"); + while (!child.document) { + await new Promise(r => t.step_timeout(r, 10)); + } + const link = document.getElementById("link"); + interact(link); + while (!child.location.href.includes("2")) { + await new Promise(r => t.step_timeout(r, 10)); + } + }, "Test that a navigated child window doesn't crash"); + </script> +</body> +</html> diff --git a/testing/web-platform/tests/soft-navigation-heuristics/navigation-api-after-transition-commit.tentative.html b/testing/web-platform/tests/soft-navigation-heuristics/navigation-api-after-transition-commit.tentative.html new file mode 100644 index 0000000000..ae17db7d59 --- /dev/null +++ b/testing/web-platform/tests/soft-navigation-heuristics/navigation-api-after-transition-commit.tentative.html @@ -0,0 +1,28 @@ +<!DOCTYPE HTML> +<html> +<head> +<meta charset="utf-8"> +<title>Detect intercepted navigate event with after-transition commit.</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/resources/testdriver.js"></script> +<script src="/resources/testdriver-vendor.js"></script> +<script src="resources/soft-navigation-helper.js"></script> +</head> +<body> + <main id=main> + <a href="foobar.html" id=link>Click me!</a> + </main> + <script> + const link = document.getElementById("link"); + testNavigationApi("Test soft navigation when navigate event intecepts with { commit: 'after-transition' }", e => { + e.intercept({commit: "after-transition", handler: async () => { + await addImageToMain(); + e.commit(); + }}); + timestamps[counter]["eventEnd"] = performance.now(); + }, link); + </script> +</body> +</html> + diff --git a/testing/web-platform/tests/soft-navigation-heuristics/navigation-api-back.tentative.html b/testing/web-platform/tests/soft-navigation-heuristics/navigation-api-back.tentative.html new file mode 100644 index 0000000000..cb96d9caf9 --- /dev/null +++ b/testing/web-platform/tests/soft-navigation-heuristics/navigation-api-back.tentative.html @@ -0,0 +1,45 @@ +<!DOCTYPE HTML> +<html> +<head> +<meta charset="utf-8"> +<title>Detect navigation.back()</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/resources/testdriver.js"></script> +<script src="/resources/testdriver-vendor.js"></script> +<script src="resources/soft-navigation-helper.js"></script> +</head> +<body> + <main id=main> + <div> + <a id=link>Click me!</a> + </div> + </main> + <script> + window.onload = async () => { + // Wait for after the load event so that the navigation doesn't get converted + // into a replace navigation. + await new Promise(r => step_timeout(r, 0)); + + navigation.onnavigate = e => { + e.intercept(); + }; + // Push a couple of navigation entries, so that we'd have a navigation entry to go back to. + await navigation.navigate("foobar.html").finished; + await navigation.navigate("another.html").committed; + + const link = document.getElementById("link"); + testSoftNavigation({ + addContent: () => { + addTextToDivOnMain(); + }, + link: link, + pushState: async () =>{ + await navigation.back().committed; + }, + test: "`navigation.back()` properly works with SoftNavigationHeuristics"}); + }; + </script> +</body> +</html> + diff --git a/testing/web-platform/tests/soft-navigation-heuristics/navigation-api-forward.tentative.html b/testing/web-platform/tests/soft-navigation-heuristics/navigation-api-forward.tentative.html new file mode 100644 index 0000000000..f483ad376b --- /dev/null +++ b/testing/web-platform/tests/soft-navigation-heuristics/navigation-api-forward.tentative.html @@ -0,0 +1,48 @@ +<!DOCTYPE HTML> +<html> +<head> +<meta charset="utf-8"> +<title>Detect navigation.forward()</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/resources/testdriver.js"></script> +<script src="/resources/testdriver-vendor.js"></script> +<script src="resources/soft-navigation-helper.js"></script> +</head> +<body> + <main id=main> + <div> + <a id=link>Click me!</a> + </div> + </main> + <script> + window.onload = async () => { + // Wait for after the load event so that the navigation doesn't get converted + // into a replace navigation. + await new Promise(r => step_timeout(r, 0)); + + navigation.onnavigate = e => { + e.intercept(); + }; + // Push a couple of navigation entries, then go back so that we'd have a + // navigation entry to go forward to. + await navigation.navigate("other.html").finished; + await navigation.navigate("foobar.html").finished; + await navigation.back().finished; + + const link = document.getElementById("link"); + testSoftNavigation({ + addContent: () => { + addTextToDivOnMain(); + }, + link: link, + pushState: async () =>{ + await navigation.forward().committed; + }, + test: "`navigation.forward()` properly works with SoftNavigationHeuristics"}); + }; + </script> +</body> +</html> + + diff --git a/testing/web-platform/tests/soft-navigation-heuristics/navigation-api-hash.tentative.html b/testing/web-platform/tests/soft-navigation-heuristics/navigation-api-hash.tentative.html new file mode 100644 index 0000000000..e20578ea79 --- /dev/null +++ b/testing/web-platform/tests/soft-navigation-heuristics/navigation-api-hash.tentative.html @@ -0,0 +1,28 @@ +<!DOCTYPE HTML> +<html> +<head> +<meta charset="utf-8"> +<title>Detect intercepted navigate event.</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/resources/testdriver.js"></script> +<script src="/resources/testdriver-vendor.js"></script> +<script src="resources/soft-navigation-helper.js"></script> +</head> +<body> + <main id=main> + <a href="#foobar.html" id=link>Click me!</a> + </main> + <script> + const link = document.getElementById("link"); + testNavigationApi("Test soft navigation with the Navigation API", e => { + e.intercept({handler: async () => { + await addImageToMain(); + main.appendChild(img); + }}); + timestamps[counter]["eventEnd"] = performance.now(); + }, link); + </script> +</body> +</html> + diff --git a/testing/web-platform/tests/soft-navigation-heuristics/navigation-api-preventDefault.tentative.html b/testing/web-platform/tests/soft-navigation-heuristics/navigation-api-preventDefault.tentative.html new file mode 100644 index 0000000000..b7b2a24c94 --- /dev/null +++ b/testing/web-platform/tests/soft-navigation-heuristics/navigation-api-preventDefault.tentative.html @@ -0,0 +1,35 @@ +<!DOCTYPE HTML> +<html> +<head> +<meta charset="utf-8"> +<title>Don't detect a navigate event which got aborted as a soft navigation. +</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/resources/testdriver.js"></script> +<script src="/resources/testdriver-vendor.js"></script> +<script src="resources/soft-navigation-helper.js"></script> +</head> +<body> + <main id=main> + <a href="foobar.html" id=link>Click me!</a> + </main> + <script> + const link = document.getElementById("link"); + testSoftNavigationNotDetected({ + testName: "Aborted navigate event is not a soft navigation", + eventHandler: e => { + e.intercept({handler: async () => { + await addImageToMain(); + main.appendChild(img); + }}); + e.preventDefault(); + timestamps[counter]["eventEnd"] = performance.now(); + }, + eventTarget: navigation, + eventName: "navigate", + link: link}); + </script> +</body> +</html> + diff --git a/testing/web-platform/tests/soft-navigation-heuristics/navigation-api-rejected.tentative.html b/testing/web-platform/tests/soft-navigation-heuristics/navigation-api-rejected.tentative.html new file mode 100644 index 0000000000..693f876b6e --- /dev/null +++ b/testing/web-platform/tests/soft-navigation-heuristics/navigation-api-rejected.tentative.html @@ -0,0 +1,28 @@ +<!DOCTYPE HTML> +<html> +<head> +<meta charset="utf-8"> +<title>Detect a rejected intercepted navigate event.</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/resources/testdriver.js"></script> +<script src="/resources/testdriver-vendor.js"></script> +<script src="resources/soft-navigation-helper.js"></script> +</head> +<body> + <main id=main> + <a href="foobar.html" id=link>Click me!</a> + </main> + <script> + const link = document.getElementById("link"); + testNavigationApi("Test intercepted and rejected navigate event", e => { + e.intercept({handler: async () => { + await addImageToMain(); + throw new Error("This navigation handler rejected"); + }}); + timestamps[counter]["eventEnd"] = performance.now(); + }, link); + </script> +</body> +</html> + diff --git a/testing/web-platform/tests/soft-navigation-heuristics/navigation-api-traverseto.tentative.html b/testing/web-platform/tests/soft-navigation-heuristics/navigation-api-traverseto.tentative.html new file mode 100644 index 0000000000..e4cabb095a --- /dev/null +++ b/testing/web-platform/tests/soft-navigation-heuristics/navigation-api-traverseto.tentative.html @@ -0,0 +1,49 @@ +<!DOCTYPE HTML> +<html> +<head> +<meta charset="utf-8"> +<title>Detect navigation.traverseTo()</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/resources/testdriver.js"></script> +<script src="/resources/testdriver-vendor.js"></script> +<script src="resources/soft-navigation-helper.js"></script> +</head> +<body> + <main id=main> + <div> + <a id=link>Click me!</a> + </div> + </main> + <script> + let key; + window.onload = async () => { + // Wait for after the load event so that the navigation doesn't get converted + // into a replace navigation. + await new Promise(r => step_timeout(r, 0)); + + navigation.onnavigate = e => { + e.intercept(); + }; + // Push a couple of navigation entries, so that we'd have a navigation entry to traverse to. + await navigation.navigate("foobar.html").finished; + key = navigation.currentEntry.key; + await navigation.navigate("another.html").finished; + + const link = document.getElementById("link"); + testSoftNavigation({ + addContent: () => { + addTextToDivOnMain(); + }, + link: link, + pushState: async () =>{ + await navigation.traverseTo(key).committed; + }, + test: "`navigation.traverseTo()` properly works with SoftNavigationHeuristics"}); + }; + </script> +</body> +</html> + + + diff --git a/testing/web-platform/tests/soft-navigation-heuristics/navigation-api-view-transition.tentative.html b/testing/web-platform/tests/soft-navigation-heuristics/navigation-api-view-transition.tentative.html new file mode 100644 index 0000000000..4d88f3d0b7 --- /dev/null +++ b/testing/web-platform/tests/soft-navigation-heuristics/navigation-api-view-transition.tentative.html @@ -0,0 +1,54 @@ +<!DOCTYPE HTML> +<html> +<head> +<meta charset="utf-8"> +<title>Navigation API + ViewTransition trigger a soft navigation.</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/resources/testdriver.js"></script> +<script src="/resources/testdriver-vendor.js"></script> +<script src="resources/soft-navigation-helper.js"></script> +</head> +<body> + <main id=main> + <div> + <a id=link href="foobar.html">Click me!</a> + </div> + </main> + <script> + let key; + window.onload = async () => { + // Wait for after the load event so that the navigation doesn't get + // converted into a replace navigation. + await new Promise(r => step_timeout(r, 0)); + + const navigate_callback = e => { + e.intercept({ + async handler() { + const lcp_promise = new Promise(resolve => { + (new PerformanceObserver(list => resolve())).observe( + {type: 'largest-contentful-paint', + includeSoftNavigationObservations: true}); + }); + const transition = document.startViewTransition(async () => { + const main = document.getElementById('main'); + main.innerHTML = '<img id="image" src="/images/blue.png?' + + Math.random() + '">'; + const img = document.getElementById("image"); + }); + await transition.updateCallbackDone; + await lcp_promise; + } + }); + timestamps[counter]["eventEnd"] = performance.now(); + }; + + const link = document.getElementById("link"); + testNavigationApi("Navigation API interception handler + " + + "startViewTransition properly detects soft navigations", + navigate_callback, link); + }; + </script> +</body> +</html> + diff --git a/testing/web-platform/tests/soft-navigation-heuristics/navigation-api.tentative.html b/testing/web-platform/tests/soft-navigation-heuristics/navigation-api.tentative.html new file mode 100644 index 0000000000..2d61736a48 --- /dev/null +++ b/testing/web-platform/tests/soft-navigation-heuristics/navigation-api.tentative.html @@ -0,0 +1,26 @@ +<!DOCTYPE HTML> +<html> +<head> +<meta charset="utf-8"> +<title>Detect intercepted navigate event.</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/resources/testdriver.js"></script> +<script src="/resources/testdriver-vendor.js"></script> +<script src="resources/soft-navigation-helper.js"></script> +</head> +<body> + <main id=main> + <a href="foobar.html" id=link>Click me!</a> + </main> + <script> + const link = document.getElementById("link"); + testNavigationApi("Test soft navigation with the Navigation API", e => { + e.intercept({handler: async () => { + await addImageToMain(); + }}); + timestamps[counter]["eventEnd"] = performance.now(); + }, link); + </script> +</body> +</html> diff --git a/testing/web-platform/tests/soft-navigation-heuristics/popstate-multiple-backs.tentative.html b/testing/web-platform/tests/soft-navigation-heuristics/popstate-multiple-backs.tentative.html new file mode 100644 index 0000000000..fd87f5f03e --- /dev/null +++ b/testing/web-platform/tests/soft-navigation-heuristics/popstate-multiple-backs.tentative.html @@ -0,0 +1,64 @@ +<!DOCTYPE HTML> +<html> +<head> +<meta charset="utf-8"> +<title>Soft navigation with multiple popstate calls.</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/resources/testdriver.js"></script> +<script src="/resources/testdriver-vendor.js"></script> +<script src="resources/soft-navigation-helper.js"></script> +</head> +<body> + <main id=main> + <div> + <a id=link>Click me!</a> + </div> + </main> + <script> + // Push state 4 times, as history.back() calls will trigger popstate + // events. + history.pushState({}, "", "foobar.html"); + history.pushState({}, "", "another_one.html"); + history.pushState({}, "", "and_another.html"); + history.pushState({}, "", "and_yet_another.html"); + + // This function runs at the start of the popstate event. + const eventPrepWork = t => { + // If this is an event due to the first click, go back() twice more. + if (!t.popped) { + step_timeout(()=>history.back(), 0); + step_timeout(()=>history.back(), 0); + t.popped = 0; + } + ++t.popped; + // return true for the second time the event fires, which is the first + // back() triggered by the popstate event. The means that the first one + // of those back() navigations would trigger a soft navigation, but not + // the last one. + return t.popped == 2; + } + const link = document.getElementById("link"); + link.addEventListener("click", () => { + history.back(); + timestamps[counter]["eventEnd"] = performance.now(); + }); + testSoftNavigation({ + addContent: () => { + // Add the content to the main element + const main = document.getElementById("main"); + main.removeChild(document.getElementsByTagName("div")[0]); + const div = document.createElement("div"); + const text = document.createTextNode("Lorem ipsum"); + div.appendChild(text); + div.style="font-size: 3em"; + main.appendChild(div); + }, + link: link, + eventPrepWork: eventPrepWork, + testName: "A soft navigation that started from a back() call inside a " + + "popstate event is recognized by SoftNavigationHeuristics", + eventType: "popstate"}); + </script> +</body> +</html> diff --git a/testing/web-platform/tests/soft-navigation-heuristics/popstate.tentative.html b/testing/web-platform/tests/soft-navigation-heuristics/popstate.tentative.html new file mode 100644 index 0000000000..f89991d76b --- /dev/null +++ b/testing/web-platform/tests/soft-navigation-heuristics/popstate.tentative.html @@ -0,0 +1,50 @@ +<!DOCTYPE HTML> +<html> +<head> +<meta charset="utf-8"> +<title>Detect simple soft navigation.</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/resources/testdriver.js"></script> +<script src="/resources/testdriver-vendor.js"></script> +<script src="resources/soft-navigation-helper.js"></script> +</head> +<body> + <main id=main> + <div> + <a id=link>Click me!</a> + </div> + </main> + <script> + // Push state twice, so that history.back() will trigger a popstate event, + // when the first push state is restored. + history.pushState({}, "", "foobar.html"); + history.pushState({}, "", "another_one.html"); + + const link = document.getElementById("link"); + link.addEventListener("click", () => { + history.back(); + timestamps[counter]["eventEnd"] = performance.now(); + }); + testSoftNavigation({ + addContent: () => { + // Add the content to the main element + const main = document.getElementById("main"); + main.removeChild(document.getElementsByTagName("div")[0]); + const div = document.createElement("div"); + const text = document.createTextNode("Lorem ipsum"); + div.appendChild(text); + div.style="font-size: 3em"; + main.appendChild(div); + }, + link: link, + testName: "A soft navigation that uses a same-document initiated popstate" + + " event is recognized by SoftNavigationHeuristics", + eventType: "popstate"}); + </script> +</body> +</html> + + + + diff --git a/testing/web-platform/tests/soft-navigation-heuristics/replacestate-null-then-push.tentative.html b/testing/web-platform/tests/soft-navigation-heuristics/replacestate-null-then-push.tentative.html new file mode 100644 index 0000000000..8a81c6be20 --- /dev/null +++ b/testing/web-platform/tests/soft-navigation-heuristics/replacestate-null-then-push.tentative.html @@ -0,0 +1,34 @@ +<!DOCTYPE HTML> +<html> +<head> +<meta charset="utf-8"> +<title>Detect soft navigation with replaceState that has a null URL, then + pushState with the URL.</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/resources/testdriver.js"></script> +<script src="/resources/testdriver-vendor.js"></script> +<script src="resources/soft-navigation-helper.js"></script> +</head> +<body> + <main id=main> + <a id=link>Click me!</a> + </main> + <script> + const link = document.getElementById("link"); + testSoftNavigation({ + addContent: async (url) => { + await addImageToMain(); + history.pushState({}, '', url); + }, + link: link, + pushState: async (url) =>{ + history.replaceState({}, ''); + }, + test: "Detect soft navigation with replaceState that has a null URL," + + " then pushState with the URL"}); + </script> +</body> +</html> + + diff --git a/testing/web-platform/tests/soft-navigation-heuristics/replacestate.tentative.html b/testing/web-platform/tests/soft-navigation-heuristics/replacestate.tentative.html new file mode 100644 index 0000000000..42e9a71899 --- /dev/null +++ b/testing/web-platform/tests/soft-navigation-heuristics/replacestate.tentative.html @@ -0,0 +1,28 @@ +<!DOCTYPE HTML> +<html> +<head> +<meta charset="utf-8"> +<title>Detect soft navigation with replaceState.</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/resources/testdriver.js"></script> +<script src="/resources/testdriver-vendor.js"></script> +<script src="resources/soft-navigation-helper.js"></script> +</head> +<body> + <main id=main> + <a id=link>Click me!</a> + </main> + <script> + const link = document.getElementById("link"); + testSoftNavigation({ + addContent: async () => { + await addImageToMain(); + }, + link: link, + pushState: (url)=>{history.replaceState({}, '', url);}, + test: "Detect soft navigation with replaceState"}); + </script> +</body> +</html> + 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/history_push.js b/testing/web-platform/tests/soft-navigation-heuristics/resources/history_push.js new file mode 100644 index 0000000000..6647dd740a --- /dev/null +++ b/testing/web-platform/tests/soft-navigation-heuristics/resources/history_push.js @@ -0,0 +1 @@ +history.pushState({}, '', URL); 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..d405adb4e7 --- /dev/null +++ b/testing/web-platform/tests/soft-navigation-heuristics/resources/soft-navigation-helper.js @@ -0,0 +1,356 @@ +var counter = 0; +var interacted; +var timestamps = [] +const MAX_CLICKS = 50; +// Entries for one hard navigation + 50 soft navigations. +const MAX_PAINT_ENTRIES = 51; +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"); + const interactionFunc = options.interactionFunc; + const eventPrepWork = options.eventPrepWork; + promise_test(async t => { + await waitInitialLCP(); + const preClickLcp = await getLcpEntries(); + setEvent(t, link, pushState, addContent, pushUrl, eventType, + eventPrepWork); + let first_navigation_id; + for (let i = 0; i < clicks; ++i) { + const firstClick = (i === 0); + let paint_entries_promise = + waitOnPaintEntriesPromise(firstClick); + interacted = false; + interact(link, interactionFunc); + + const navigation_id = await waitOnSoftNav(); + if (!first_navigation_id) { + first_navigation_id = navigation_id; + } + // 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, first_navigation_id, clicks + 1, options.validate); + }, testName); + }; + +const testNavigationApi = (testName, navigateEventHandler, link) => { + promise_test(async t => { + navigation.addEventListener('navigate', navigateEventHandler); + const navigated = new Promise(resolve => { + navigation.addEventListener('navigatesuccess', resolve); + navigation.addEventListener('navigateerror', resolve); + }); + await waitInitialLCP(); + const preClickLcp = await getLcpEntries(); + let paint_entries_promise = waitOnPaintEntriesPromise(); + interact(link); + const first_navigation_id = await waitOnSoftNav(); + await navigated; + await paint_entries_promise; + assert_equals(document.softNavigations, 1, 'Soft Navigation detected'); + await validateSoftNavigationEntry(1, () => {}, 'foobar.html'); + + await runEntryValidations(preClickLcp, first_navigation_id); + }, testName); +}; + +const testSoftNavigationNotDetected = options => { + promise_test(async t => { + const preClickLcp = await getLcpEntries(); + options.eventTarget.addEventListener(options.eventName, options.eventHandler); + interact(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); + }); + if (document.softNavigations) { + assert_equals( + document.softNavigations, 0, 'Soft Navigation not detected'); + } + const postClickLcp = await getLcpEntries(); + assert_equals( + preClickLcp.length, postClickLcp.length, 'No LCP entries accumulated'); + }, options.testName); + }; + +const runEntryValidations = + async (preClickLcp, first_navigation_id, entries_expected_number = 2, + validate = null) => { + await validatePaintEntries('first-contentful-paint', entries_expected_number, + first_navigation_id); + await validatePaintEntries('first-paint', entries_expected_number, + first_navigation_id); + const postClickLcp = await getLcpEntries(); + const postClickLcpWithoutSoftNavs = await getLcpEntriesWithoutSoftNavs(); + assert_greater_than( + postClickLcp.length, preClickLcp.length, + 'Soft navigation should have triggered at least an LCP entry'); + + if (validate) { + await validate(); + } + assert_equals( + postClickLcpWithoutSoftNavs.length, preClickLcp.length, + 'Soft navigation should not have triggered an LCP entry when the ' + + 'observer did not opt in'); + 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'); + assert_equals( + postClickLcp[preClickLcp.length].navigationId, + first_navigation_id, 'Soft navigation LCP should have the same navigation ' + + 'ID as the last soft nav entry') +}; + +const interact = + (link, interactionFunc = undefined) => { + if (test_driver) { + if (interactionFunc) { + interactionFunc(); + } else { + test_driver.click(link); + } + timestamps[counter] = {"syncPostInteraction": performance.now()}; + } + } + +const setEvent = (t, button, pushState, addContent, pushUrl, eventType, prepWork) => { + const eventObject = + (eventType == 'click' || eventType.startsWith("key")) ? button : window; + eventObject.addEventListener(eventType, async e => { + let prepWorkFailed = false; + if (prepWork &&!prepWork(t)) { + prepWorkFailed = true; + } + // This is the end of the event's sync processing. + if (!timestamps[counter]["eventEnd"]) { + timestamps[counter]["eventEnd"] = performance.now(); + } + if (prepWorkFailed) { + return; + } + // 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); + + interacted = true; + ++counter; + }); +}; + +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]["syncPostInteraction"], entryTimestamp, + "Entry timestamp is lower than the post interaction one"); + assert_greater_than_equal( + entryTimestamp, timestamps[i]['eventEnd'], + 'Event start timestamp matches'); + assert_not_equals(entry.navigationId, + performance.getEntriesByType("navigation")[0].navigationId, + "The navigation ID was re-generated and different from the initial one."); + if (i > 0) { + assert_not_equals(entry.navigationId, + entries[i-1].navigationId, + "The navigation ID was re-generated 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, first_navigation_id) => { + if (!performance.softNavPaintMetricsSupported) { + return; + } + const expected_entries_number = Math.min(entries_number, MAX_PAINT_ENTRIES); + const entries = await new Promise(resolve => { + const entries = []; + (new PerformanceObserver(list => { + entries.push(...list.getEntriesByName(type)); + if (entries.length >= expected_entries_number) { + resolve(entries); + } + })).observe( + {type: 'paint', buffered: true, includeSoftNavigationObservations: true}); + }); + const entries_without_softnavs = await new Promise(resolve => { + (new PerformanceObserver(list => resolve( + list.getEntriesByName(type)))).observe( + {type: 'paint', buffered: true}); + }); + assert_equals(entries.length, expected_entries_number, + `There are ${entries_number} entries for ${type}`); + assert_equals(entries_without_softnavs.length, 1, + `There is one non-softnav entry for ${type}`); + if (entries_number > 1) { + assert_not_equals(entries[0].startTime, entries[1].startTime, + "Entries have different timestamps for " + type); + } + if (expected_entries_number > entries_without_softnavs.length) { + assert_equals(entries[entries_without_softnavs.length].navigationId, + first_navigation_id, + "First paint entry should have the same navigation ID as the last soft " + + "navigation entry"); + } +}; + +const waitInitialLCP = () => { + return new Promise(resolve => { + new PerformanceObserver(list => resolve()).observe({ + type: 'largest-contentful-paint', + buffered: true + }); + }); +} + +const waitOnSoftNav = () => { + return new Promise(resolve => { + (new PerformanceObserver(list => { + const entries = list.getEntries(); + assert_equals(entries.length, 1, + "Only one soft navigation entry"); + resolve(entries[0].navigationId); + })).observe({ + type: 'soft-navigation' + }); + }); +}; + +const getLcpEntries = async () => { + const entries = await new Promise(resolve => { + (new PerformanceObserver(list => resolve( + list.getEntries()))).observe( + {type: 'largest-contentful-paint', buffered: true, + includeSoftNavigationObservations: true}); + }); + return entries; +}; + +const getLcpEntriesWithoutSoftNavs = 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, url="blue.png", id = "imagelcp") => { + const img = new Image(); + img.src = '/images/'+ url + "?" + Math.random(); + img.id=id + img.setAttribute("elementtiming", id); + await img.decode(); + element.appendChild(img); +}; +const addImageToMain = async (url="blue.png", id = "imagelcp") => { + await addImage(document.getElementById('main'), url, id); +}; + +const addTextParagraphToMain = (text, element_timing = "") => { + const main = document.getElementById("main"); + const p = document.createElement("p"); + const textNode = document.createTextNode(text); + p.appendChild(textNode); + if (element_timing) { + p.setAttribute("elementtiming", element_timing); + } + p.style = "font-size: 3em"; + main.appendChild(p); + return p; +}; +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 = (expectLCP = true) => { + return new Promise((resolve, reject) => { + if (performance.softNavPaintMetricsSupported) { + 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', includeSoftNavigationObservations: true}); + } else if (expectLCP) { + new PerformanceObserver(list => { + resolve(); + }).observe({ + type: 'largest-contentful-paint', + includeSoftNavigationObservations: true + }); + } else { + step_timeout( + () => requestAnimationFrame(() => requestAnimationFrame(resolve)), + 100); + } + }); +}; diff --git a/testing/web-platform/tests/soft-navigation-heuristics/second-interaction-not-softnav.tentative.html b/testing/web-platform/tests/soft-navigation-heuristics/second-interaction-not-softnav.tentative.html new file mode 100644 index 0000000000..a9bb337060 --- /dev/null +++ b/testing/web-platform/tests/soft-navigation-heuristics/second-interaction-not-softnav.tentative.html @@ -0,0 +1,35 @@ +<!DOCTYPE HTML> +<html> +<head> +<meta charset="utf-8"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/resources/testdriver.js"></script> +<script src="/resources/testdriver-vendor.js"></script> +<script src="resources/soft-navigation-helper.js"></script> +</head> +<body> + <main id=main> + <p><a id=link>Click me!</a></p> + <p><a id=secondlink>Then click me!</a></p> + </main> + <script> + const link = document.getElementById("link"); + testSoftNavigation({ + addContent: async () => { + if (test_driver) { + const secondlink = document.getElementById("secondlink"); + const clickPromise = new Promise(r => { + secondlink.addEventListener("click", r); + }); + test_driver.click(secondlink); + await clickPromise; + } + addImageToMain(); + }, + link: link, + test: "Second interaction in the middle of a soft navigation"}); + </script> +</body> +</html> + diff --git a/testing/web-platform/tests/soft-navigation-heuristics/soft-navigation-detection-main-descendent.tentative.html b/testing/web-platform/tests/soft-navigation-heuristics/soft-navigation-detection-main-descendent.tentative.html new file mode 100644 index 0000000000..96ff55260c --- /dev/null +++ b/testing/web-platform/tests/soft-navigation-heuristics/soft-navigation-detection-main-descendent.tentative.html @@ -0,0 +1,30 @@ +<!DOCTYPE HTML> +<html> +<head> +<meta charset="utf-8"> +<title>Detect soft navigation adding content to a main descendent.</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/resources/testdriver.js"></script> +<script src="/resources/testdriver-vendor.js"></script> +<script src="resources/soft-navigation-helper.js"></script> +</head> +<body> + <main id=main> + <a id=link>Click me!</a> + <div id=descendent></div> + </main> + <script> + const link = document.getElementById("link"); + testSoftNavigation({ + addContent: () => { + const descendent= document.getElementById("descendent"); + const content = document.createTextNode("Lorem Ipsum"); + descendent.appendChild(content); + }, + link: link, + test: "Test that a soft navigation is detected even when DOM change is " + + "done on a main descendent"}); + </script> +</body> +</html> diff --git a/testing/web-platform/tests/soft-navigation-heuristics/soft-navigation-detection-non-main.tentative.html b/testing/web-platform/tests/soft-navigation-heuristics/soft-navigation-detection-non-main.tentative.html new file mode 100644 index 0000000000..86da167c33 --- /dev/null +++ b/testing/web-platform/tests/soft-navigation-heuristics/soft-navigation-detection-non-main.tentative.html @@ -0,0 +1,25 @@ +<!DOCTYPE HTML> +<html> +<head> +<meta charset="utf-8"> +<title>Detect simple soft navigation.</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/resources/testdriver.js"></script> +<script src="/resources/testdriver-vendor.js"></script> +<script src="resources/soft-navigation-helper.js"></script> +</head> +<body> + <a id=link>Click me!</a> + <script> + const link = document.getElementById("link"); + testSoftNavigation({ + addContent: async () => { + await addImage(document.body); + }, + link: link, + test: "Test that a soft navigation is detected on a non-main element"}); + </script> +</body> +</html> + diff --git a/testing/web-platform/tests/soft-navigation-heuristics/soft-navigation-detection-web-component-lifecycle.tentative.html b/testing/web-platform/tests/soft-navigation-heuristics/soft-navigation-detection-web-component-lifecycle.tentative.html new file mode 100644 index 0000000000..7e27b0073c --- /dev/null +++ b/testing/web-platform/tests/soft-navigation-heuristics/soft-navigation-detection-web-component-lifecycle.tentative.html @@ -0,0 +1,46 @@ +<!DOCTYPE HTML> +<html> +<head> +<meta charset="utf-8"> +<title>Detect simple soft navigation.</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/resources/testdriver.js"></script> +<script src="/resources/testdriver-vendor.js"></script> +<script src="resources/soft-navigation-helper.js"></script> +</head> +<body> + <script> + // Define a custom element + class SPAContent extends HTMLDivElement { + constructor() { + super(); + } + connectedCallback() { + // Change the URL + history.pushState({}, '', "/foobar.html"); + } + } + customElements.define("spa-content", SPAContent, { extends: "div"}); + + </script> + <main id=main> + <a id=link>Click me!</a> + </main> + <script> + const link = document.getElementById("link"); + testSoftNavigation({ + addContent: () => { + const main = document.getElementById("main"); + const spaContent = document.createElement("div", {is: "spa-content"}); + const content = document.createTextNode("Lorem Ipsum"); + spaContent.appendChild(content); + main.appendChild(spaContent); + }, + link: link, + test: "Test that a soft navigation is detected when the click is done " + + "on a custom element."}); + </script> +</body> +</html> + diff --git a/testing/web-platform/tests/soft-navigation-heuristics/soft-navigation-detection.tentative.html b/testing/web-platform/tests/soft-navigation-heuristics/soft-navigation-detection.tentative.html new file mode 100644 index 0000000000..618984d859 --- /dev/null +++ b/testing/web-platform/tests/soft-navigation-heuristics/soft-navigation-detection.tentative.html @@ -0,0 +1,26 @@ +<!DOCTYPE HTML> +<html> +<head> +<meta charset="utf-8"> +<title>Detect simple soft navigation.</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/resources/testdriver.js"></script> +<script src="/resources/testdriver-vendor.js"></script> +<script src="resources/soft-navigation-helper.js"></script> +</head> +<body> + <main id=main> + <a id=link>Click me!</a> + </main> + <script> + const link = document.getElementById("link"); + testSoftNavigation({ + addContent: async () => { + await addImageToMain(); + }, + link: link, + test: "Test that a soft navigation is detected"}); + </script> +</body> +</html> diff --git a/testing/web-platform/tests/soft-navigation-heuristics/soft-navigation-no-url.tentative.html b/testing/web-platform/tests/soft-navigation-heuristics/soft-navigation-no-url.tentative.html new file mode 100644 index 0000000000..a0055c654c --- /dev/null +++ b/testing/web-platform/tests/soft-navigation-heuristics/soft-navigation-no-url.tentative.html @@ -0,0 +1,31 @@ +<!DOCTYPE HTML> +<html> +<head> +<meta charset="utf-8"> +<title>Detect simple soft navigation.</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/resources/testdriver.js"></script> +<script src="/resources/testdriver-vendor.js"></script> +<script src="resources/soft-navigation-helper.js"></script> +</head> +<body> + <main id=main> + <a id=link>Click me!</a> + </main> + <script> + const link = document.getElementById("link"); + testSoftNavigationNotDetected({ + eventHandler: url => { + addTextToDivOnMain(); + history.pushState({}, ''); + }, + link: link, + eventName: "click", + eventTarget: link, + testName: "Test that a soft navigation is not detected when a URL is not" + + " passed to the history API."}); + </script> +</body> +</html> + diff --git a/testing/web-platform/tests/soft-navigation-heuristics/softnav-after-lcp-paint-larger-than-viewport.tentative.html b/testing/web-platform/tests/soft-navigation-heuristics/softnav-after-lcp-paint-larger-than-viewport.tentative.html new file mode 100644 index 0000000000..3c930d8be4 --- /dev/null +++ b/testing/web-platform/tests/soft-navigation-heuristics/softnav-after-lcp-paint-larger-than-viewport.tentative.html @@ -0,0 +1,70 @@ +<!DOCTYPE HTML> +<html> +<head> +<meta charset="utf-8"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/resources/testdriver.js"></script> +<script src="/resources/testdriver-vendor.js"></script> +<script src="/soft-navigation-heuristics/resources/soft-navigation-helper.js"></script> +<link rel="stylesheet" type="text/css" href="/fonts/ahem.css" /> +</head> +<body> + <main id=main> + <div> + <a id=link><img id="initial_image1" src="/images/lcp-256x256.png?1" style="width: 90vw; height: 90vh"> + <div id=extra></div> + </a> + </div> + </main> + <script> + (async () => { + const link = document.getElementById("link"); + + // Inject a second image that takes a large part of the viewport. + await new Promise(resolve => { + requestAnimationFrame(() => requestAnimationFrame(() => { + document.getElementById("extra").innerHTML = ` + <div style="position: absolute; bottom: 0; right: 0"> + <img id="initial_image2" src="/images/lcp-256x256.png?2" style="width: 90vw;height: 90vh"> + </div>`; + resolve(); + })); + }); + // Wait until the second image is rendered. + await new Promise(resolve => { + requestAnimationFrame(() => requestAnimationFrame(() => { + resolve(); + })); + }); + testSoftNavigation({ + addContent: async () => { + // Remove the initial image, so that the text would be painted on screen. + document.getElementById("initial_image1").remove(); + document.getElementById("initial_image2").remove(); + let lcp_element_painted; + const lcp_element_paint_promise = new Promise((r) => { lcp_element_painted = r; }); + // Add an LCP element, but have it be small enough to not trigger the + // Soft Navigation heuristics. + const p = addTextParagraphToMain( + "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod", + /*element_timing=*/"lcp"); + (new PerformanceObserver(list => { + // Once the first element is fully painted: + lcp_element_painted(); + })).observe({type: "element", buffered: true}); + await lcp_element_paint_promise; + // Add a smaller element that gets us over that threshold. + addTextParagraphToMain("dolore magna aliqua."); + }, + link: link, + test: "Test that an image LCP followed by a smaller soft navigation LCP" + + " properly queues an LCP entry, even when the soft navigation is" + + " detected after the LCP, even when initial paints significantly" + + " exceed the viewport dimensions."}); + })(); + </script> +</body> +</html> + + diff --git a/testing/web-platform/tests/soft-navigation-heuristics/softnav-after-lcp-paint.tentative.html b/testing/web-platform/tests/soft-navigation-heuristics/softnav-after-lcp-paint.tentative.html new file mode 100644 index 0000000000..cf9a9942ca --- /dev/null +++ b/testing/web-platform/tests/soft-navigation-heuristics/softnav-after-lcp-paint.tentative.html @@ -0,0 +1,42 @@ +<!DOCTYPE HTML> +<html> +<head> +<meta charset="utf-8"> +<title>Detect simple soft navigation.</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/resources/testdriver.js"></script> +<script src="/resources/testdriver-vendor.js"></script> +<script src="resources/soft-navigation-helper.js"></script> +<link rel="stylesheet" type="text/css" href="/fonts/ahem.css" /> +</head> +<body> + <main id=main> + <div> + <a id=link><img src="/images/lcp-256x256.png"></a> + </div> + </main> + <script> + const link = document.getElementById("link"); + testSoftNavigation({ + addContent: async () => { + let lcp_element_painted; + const lcp_element_paint_promise = new Promise((r) => { lcp_element_painted = r; }); + // Add an LCP element, but have it be small enough to not trigger the + // Soft Navigation heuristics. + const p = addTextParagraphToMain("Lorem Ipsu", /*element_timing=*/"lcp"); + (new PerformanceObserver(list => { + // Once the first element is fully painted: + lcp_element_painted(); + })).observe({type: "element", buffered: true}); + await lcp_element_paint_promise; + // Add a smaller element that gets us over that threshold. + addTextParagraphToMain("m"); + }, + link: link, + test: "Test that an image LCP followed by a smaller soft navigation LCP" + + " properly queues an LCP entry, even when the soft navigation is" + + " detected after the LCP."}); + </script> +</body> +</html> diff --git a/testing/web-platform/tests/soft-navigation-heuristics/softnav-before-lcp-paint.tentative.html b/testing/web-platform/tests/soft-navigation-heuristics/softnav-before-lcp-paint.tentative.html new file mode 100644 index 0000000000..d5dc4b74fb --- /dev/null +++ b/testing/web-platform/tests/soft-navigation-heuristics/softnav-before-lcp-paint.tentative.html @@ -0,0 +1,52 @@ +<!DOCTYPE HTML> +<html> +<head> +<meta charset="utf-8"> +<title>Detect simple soft navigation.</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/resources/testdriver.js"></script> +<script src="/resources/testdriver-vendor.js"></script> +<script src="resources/soft-navigation-helper.js"></script> +<link rel="stylesheet" type="text/css" href="/fonts/ahem.css" /> +</head> +<body> + <main id=main> + <div> + <a id=link><img src="/images/lcp-256x256.png"></a> + </div> + </main> + <script> + const link = document.getElementById("link"); + testSoftNavigation({ + addContent: async () => { + // Add an LCP element, large enough to trigger the Soft Navigation + // heuristics. + const p = addTextParagraphToMain("Lorem Ipsum", /*element_timing=*/"lcp"); + p.id = "first_lcp"; + // Once the first element is fully painted. + const observer = new PerformanceObserver(list => { + // Add a larger element to be the new LCP. + window.lcp_observer_promise = new Promise(resolve => { + (new PerformanceObserver(resolve)).observe({type: "element"}); + }); + const p2 = addTextParagraphToMain("LOREM IPSUMER", "real_lcp"); + p2.id = "real_lcp"; + observer.disconnect(); + }); + observer.observe({type: "element", buffered: true}); + }, + link: link, + validate: async () => { + await window.lcp_observer_promise; + const lcps = await getLcpEntries(); + assert_greater_than_equal(lcps.length, 3, "Got at least 3 LCP entries"); + assert_equals(lcps[lcps.length - 2].id, "first_lcp", "Got the first LCP"); + assert_equals(lcps[lcps.length - 1].id, "real_lcp", "Got the real LCP"); + }, + test: "Test that an image LCP followed by 2 smaller soft navigation LCPs" + + " properly queues both LCP entries, even when the soft navigation" + + " is detected in between them."}); + </script> +</body> +</html> diff --git a/testing/web-platform/tests/soft-navigation-heuristics/softnav-between-lcp-render-and-paint.tentative.html b/testing/web-platform/tests/soft-navigation-heuristics/softnav-between-lcp-render-and-paint.tentative.html new file mode 100644 index 0000000000..56c7073de6 --- /dev/null +++ b/testing/web-platform/tests/soft-navigation-heuristics/softnav-between-lcp-render-and-paint.tentative.html @@ -0,0 +1,36 @@ +<!DOCTYPE HTML> +<html> +<head> +<meta charset="utf-8"> +<title>Detect simple soft navigation.</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/resources/testdriver.js"></script> +<script src="/resources/testdriver-vendor.js"></script> +<script src="resources/soft-navigation-helper.js"></script> +<link rel="stylesheet" type="text/css" href="/fonts/ahem.css" /> +</head> +<body> + <main id=main> + <div> + <a id=link><img src="/images/lcp-256x256.png"></a> + </div> + </main> + <script> + const link = document.getElementById("link"); + testSoftNavigation({ + addContent: async () => { + // Add an LCP element, but have it be small enough to not trigger the + // Soft Navigation heuristics. + const p = addTextParagraphToMain("Lorem Ipsu"); + requestAnimationFrame(() => { + const p2 = addTextParagraphToMain("m"); + }); + }, + link: link, + test: "Test that an image LCP followed by a smaller soft navigation LCP" + + " properly queues an LCP entry, even when the soft navigation is" + + " detected between the LCP's render and paint."}); + </script> +</body> +</html> diff --git a/testing/web-platform/tests/soft-navigation-heuristics/supported-entry-types.tentative.html b/testing/web-platform/tests/soft-navigation-heuristics/supported-entry-types.tentative.html new file mode 100644 index 0000000000..4ab408e10b --- /dev/null +++ b/testing/web-platform/tests/soft-navigation-heuristics/supported-entry-types.tentative.html @@ -0,0 +1,15 @@ +<!DOCTYPE HTML> +<html> +<head> +<meta charset="utf-8"> +<title>Soft navigations are a supported entry type</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script> + promise_test(async () => { + assert_true(PerformanceObserver.supportedEntryTypes.includes( + "soft-navigation")); + }, "Soft navigations are a supported entry type"); +</script> + + diff --git a/testing/web-platform/tests/soft-navigation-heuristics/text-lcp-before-detection-second-softnav.tentative.html b/testing/web-platform/tests/soft-navigation-heuristics/text-lcp-before-detection-second-softnav.tentative.html new file mode 100644 index 0000000000..bed27c3506 --- /dev/null +++ b/testing/web-platform/tests/soft-navigation-heuristics/text-lcp-before-detection-second-softnav.tentative.html @@ -0,0 +1,62 @@ +<!DOCTYPE HTML> +<html> +<head> +<meta charset="utf-8"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/resources/testdriver.js"></script> +<script src="/resources/testdriver-vendor.js"></script> +<script src="resources/soft-navigation-helper.js"></script> +<link rel="stylesheet" type="text/css" href="/fonts/ahem.css" /> +</head> +<body> + <main id=main> + <div> + <a id=link><img src="/images/lcp-256x256.png" id="img"></a> + </div> + </main> + <script> + const link = document.getElementById("link"); + let first_lcp_painted; + let second_lcp_painted; + (async () => { + await new Promise(r => { first_lcp_painted = r; }); + addTextParagraphToMain("LOREM IPSUMR", "no lcp"); + (new PerformanceObserver(second_lcp_painted)).observe({type: "element"}); + })(); + testSoftNavigation({ + pushState: null, + clicks: 2, + addContent: async () => { + // Add an LCP element. + await new Promise(resolve => { + const p = addTextParagraphToMain("Lorem Ipsum", /*element_timing=*/"first_lcp" + counter); + p.id = "first_lcp" + counter; + (new PerformanceObserver(resolve)).observe({type: "element"}); + }); + if(counter) { + first_lcp_painted(); + // Wait for the unrelated LCP to be painted. + await new Promise(r => { second_lcp_painted = r; }); + } + const url = URL + "?" + counter; + history.pushState({}, '', url); + }, + link: link, + validate: async () => { + const lcps = await getLcpEntries(); + const ref_counter = counter-1; + assert_equals(lcps.length, 2 + ref_counter, "Got 2 LCP entries"); + assert_equals(lcps[lcps.length - 1].id, "first_lcp" + ref_counter, "Got the first LCP"); + }, + test: "Second soft navigation text LCP discovered between user " + + "interaction and soft navigation detection are properly " + + "reported, while unrelated LCPs are ignored during that time. " + + "As a side effect, we also test element timing."}); + </script> +</body> +</html> + + + + diff --git a/testing/web-platform/tests/soft-navigation-heuristics/text-lcp-before-detection.tentative.html b/testing/web-platform/tests/soft-navigation-heuristics/text-lcp-before-detection.tentative.html new file mode 100644 index 0000000000..11e82e539f --- /dev/null +++ b/testing/web-platform/tests/soft-navigation-heuristics/text-lcp-before-detection.tentative.html @@ -0,0 +1,55 @@ +<!DOCTYPE HTML> +<html> +<head> +<meta charset="utf-8"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/resources/testdriver.js"></script> +<script src="/resources/testdriver-vendor.js"></script> +<script src="resources/soft-navigation-helper.js"></script> +<link rel="stylesheet" type="text/css" href="/fonts/ahem.css" /> +</head> +<body> + <main id=main> + <div> + <a id=link><img src="/images/lcp-256x256.png" id="img"></a> + </div> + </main> + <script> + const link = document.getElementById("link"); + let first_lcp_painted; + let second_lcp_painted; + (async () => { + await new Promise(r => { first_lcp_painted = r; }); + addTextParagraphToMain("LOREM IPSUMR", "no lcp"); + (new PerformanceObserver(second_lcp_painted)).observe({type: "element"}); + })(); + testSoftNavigation({ + pushState: null, + addContent: async () => { + // Add an LCP element. + await new Promise(resolve => { + const p = addTextParagraphToMain("Lorem Ipsum", /*element_timing=*/"first_lcp"); + p.id = "first_lcp"; + (new PerformanceObserver(resolve)).observe({type: "element"}); + }); + first_lcp_painted(); + // Wait for the unrelated LCP to be painted. + await new Promise(r => { second_lcp_painted = r; }); + const url = URL + "?" + counter; + history.pushState({}, '', url); + }, + link: link, + validate: async () => { + const lcps = await getLcpEntries(); + assert_equals(lcps.length, 2, "Got 2 LCP entries"); + assert_equals(lcps[lcps.length - 1].id, "first_lcp", "Got the first LCP"); + }, + test: "Text LCP discovered between user interaction and soft " + + "navigation detection are properly reported, while unrelated " + + "LCPs are ignored during that time. As a side effect, we also " + + "test element timing."}); + </script> +</body> +</html> + diff --git a/testing/web-platform/tests/soft-navigation-heuristics/text-lcp-followed-by-anim-image-softnav-lcp.tentative.html b/testing/web-platform/tests/soft-navigation-heuristics/text-lcp-followed-by-anim-image-softnav-lcp.tentative.html new file mode 100644 index 0000000000..0615b513e6 --- /dev/null +++ b/testing/web-platform/tests/soft-navigation-heuristics/text-lcp-followed-by-anim-image-softnav-lcp.tentative.html @@ -0,0 +1,32 @@ +<!DOCTYPE HTML> +<html> +<head> +<meta charset="utf-8"> +<title>Detect simple soft navigation.</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/resources/testdriver.js"></script> +<script src="/resources/testdriver-vendor.js"></script> +<script src="resources/soft-navigation-helper.js"></script> +</head> +<body> + <main id=main> + <div> + <a id=link style="font-size: 4em">Click me!</a> + </div> + </main> + <script> + const link = document.getElementById("link"); + testSoftNavigation({ + addContent: async () => { + const main = document.getElementById("main"); + main.removeChild(document.getElementsByTagName("div")[0]); + await addImageToMain("anim-gr.png"); + }, + link: link, + test: "Test that a text LCP followup by a smaller soft navigation image" + + " LCP properly queues an LCP entry"}); + </script> +</body> +</html> + diff --git a/testing/web-platform/tests/soft-navigation-heuristics/text-lcp-followed-by-image-softnav-lcp.tentative.html b/testing/web-platform/tests/soft-navigation-heuristics/text-lcp-followed-by-image-softnav-lcp.tentative.html new file mode 100644 index 0000000000..3a8398347c --- /dev/null +++ b/testing/web-platform/tests/soft-navigation-heuristics/text-lcp-followed-by-image-softnav-lcp.tentative.html @@ -0,0 +1,31 @@ +<!DOCTYPE HTML> +<html> +<head> +<meta charset="utf-8"> +<title>Detect simple soft navigation.</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/resources/testdriver.js"></script> +<script src="/resources/testdriver-vendor.js"></script> +<script src="resources/soft-navigation-helper.js"></script> +</head> +<body> + <main id=main> + <div> + <a id=link style="font-size: 4em">Click me!</a> + </div> + </main> + <script> + const link = document.getElementById("link"); + testSoftNavigation({ + addContent: async () => { + const main = document.getElementById("main"); + main.removeChild(document.getElementsByTagName("div")[0]); + await addImageToMain(); + }, + link: link, + test: "Test that a text LCP followup by a smaller soft navigation image" + + " LCP properly queues an LCP entry"}); + </script> +</body> +</html> diff --git a/testing/web-platform/tests/soft-navigation-heuristics/text-lcp-followed-by-text-softnav-lcp.tentative.html b/testing/web-platform/tests/soft-navigation-heuristics/text-lcp-followed-by-text-softnav-lcp.tentative.html new file mode 100644 index 0000000000..929f59f019 --- /dev/null +++ b/testing/web-platform/tests/soft-navigation-heuristics/text-lcp-followed-by-text-softnav-lcp.tentative.html @@ -0,0 +1,34 @@ +<!DOCTYPE HTML> +<html> +<head> +<meta charset="utf-8"> +<title>Detect simple soft navigation.</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/resources/testdriver.js"></script> +<script src="/resources/testdriver-vendor.js"></script> +<script src="resources/soft-navigation-helper.js"></script> +</head> +<body> + <main id=main> + <div> + <a id=link style="font-size: 4em">Click me!</a> + </div> + </main> + <script> + const link = document.getElementById("link"); + testSoftNavigation({ + addContent: () => { + const main = document.getElementById("main"); + main.removeChild(document.getElementsByTagName("div")[0]); + addTextToDivOnMain(); + }, + link: link, + test: "Test that a text LCP followup by a smaller soft navigation text" + + " LCP properly queues an LCP entry"}); + </script> +</body> +</html> + + + diff --git a/testing/web-platform/tests/soft-navigation-heuristics/visited-link.tentative.html b/testing/web-platform/tests/soft-navigation-heuristics/visited-link.tentative.html new file mode 100644 index 0000000000..0bb149f00e --- /dev/null +++ b/testing/web-platform/tests/soft-navigation-heuristics/visited-link.tentative.html @@ -0,0 +1,48 @@ +<!DOCTYPE HTML> +<html> +<head> +<meta charset="utf-8"> +<title>Soft navigation visited link paint tests.</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/resources/testdriver.js"></script> +<script src="/resources/testdriver-vendor.js"></script> +<script src="resources/soft-navigation-helper.js"></script> +</head> +<body> + <main id=main> + <a id=link>Click me!</a> + <a id=visited>link that is really long so it is the LCP</a> + </main> + <script> + const visited = document.getElementById("visited"); + const fake_url = "./fake_" + Math.random(); + visited.href = fake_url; + const visitFakeURLAndAddInvisibleText = () => { + requestAnimationFrame(() => requestAnimationFrame(() => { + history.replaceState({}, "", fake_url); + })); + const main = document.getElementById("main"); + const div = document.createElement("div"); + const text = document.createTextNode("Lorem Ipsum"); + div.appendChild(text); + main.appendChild(div); + } + const link = document.getElementById("link"); + testSoftNavigation({ + addContent: async () => { + await visitFakeURLAndAddInvisibleText(); + }, + link: link, + validate: async () => { + await new Promise(r => step_timeout(r, 100)); + const postVisitedLcp = await getLcpEntries(); + assert_not_equals(postVisitedLcp[postVisitedLcp.length - 1].id, + "visited", + "Soft Nav LCP ID should not be visited"); + }, + test: "Test that a visited link doesn't trigger LCP after a soft " + + "navigation is detected"}); + </script> +</body> +</html> |