diff options
Diffstat (limited to 'testing/web-platform/tests/soft-navigation-heuristics')
28 files changed, 1156 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/dropped-entries.tentative.html b/testing/web-platform/tests/soft-navigation-heuristics/dropped-entries.tentative.html new file mode 100644 index 0000000000..a1db721837 --- /dev/null +++ b/testing/web-platform/tests/soft-navigation-heuristics/dropped-entries.tentative.html @@ -0,0 +1,41 @@ +<!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(); + }, + link: link, + clicks: 52, + extraValidations: async (entries, options)=>{ + 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/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-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..27e9bbbd13 --- /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/ggrr-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..5aa1b1da2e --- /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/ggrr-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..7f5bafb5e6 --- /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/ggrr-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(); + + click(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(); + click(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/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..f88fb95301 --- /dev/null +++ b/testing/web-platform/tests/soft-navigation-heuristics/multiple-paint-entries-buffered.tentative.html @@ -0,0 +1,39 @@ +<!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)=>{ + const paint_entries = await new Promise(resolve => { + new PerformanceObserver(list => resolve(list.getEntries())).observe( + {type: 'paint', buffered: 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..63a8adb208 --- /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"); + click(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-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..a63b177a63 --- /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 => { + timestamps[counter]["eventStart"] = performance.now(); + e.intercept({handler: async () => { + await addImageToMain(); + main.appendChild(img); + }}); + }, 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..d6c61efb3c --- /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 => { + timestamps[counter]["eventStart"] = performance.now(); + e.intercept({handler: async () => { + await addImageToMain(); + main.appendChild(img); + }}); + e.preventDefault(); + }, + 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..bcc0451d5b --- /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 => { + timestamps[counter]["eventStart"] = performance.now(); + e.intercept({handler: async () => { + await addImageToMain(); + throw new Error("This navigation handler rejected"); + }}); + }, 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.tentative.html b/testing/web-platform/tests/soft-navigation-heuristics/navigation-api.tentative.html new file mode 100644 index 0000000000..c4f2786ccf --- /dev/null +++ b/testing/web-platform/tests/soft-navigation-heuristics/navigation-api.tentative.html @@ -0,0 +1,27 @@ +<!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 => { + timestamps[counter]["eventStart"] = performance.now(); + e.intercept({handler: async () => { + await addImageToMain(); + main.appendChild(img); + }}); + }, link); + </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..60a5ff7229 --- /dev/null +++ b/testing/web-platform/tests/soft-navigation-heuristics/popstate.tentative.html @@ -0,0 +1,47 @@ +<!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()); + 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/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'}); + }); +}; 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/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-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> + + + |