summaryrefslogtreecommitdiffstats
path: root/testing/web-platform/tests/soft-navigation-heuristics
diff options
context:
space:
mode:
Diffstat (limited to 'testing/web-platform/tests/soft-navigation-heuristics')
-rw-r--r--testing/web-platform/tests/soft-navigation-heuristics/back.tentative.html36
-rw-r--r--testing/web-platform/tests/soft-navigation-heuristics/dropped-entries.tentative.html41
-rw-r--r--testing/web-platform/tests/soft-navigation-heuristics/hash.tentative.html29
-rw-r--r--testing/web-platform/tests/soft-navigation-heuristics/image-lcp-followed-by-image-softnav-lcp.tentative.html32
-rw-r--r--testing/web-platform/tests/soft-navigation-heuristics/image-lcp-followed-by-text-softnav-lcp.tentative.html31
-rw-r--r--testing/web-platform/tests/soft-navigation-heuristics/image-lcp-followed-by-two-image-softnavs-lcp.tentative.html75
-rw-r--r--testing/web-platform/tests/soft-navigation-heuristics/multiple-paint-entries-buffered.tentative.html40
-rw-r--r--testing/web-platform/tests/soft-navigation-heuristics/navigate-child.html30
-rw-r--r--testing/web-platform/tests/soft-navigation-heuristics/navigation-api-after-transition-commit.tentative.html28
-rw-r--r--testing/web-platform/tests/soft-navigation-heuristics/navigation-api-back.tentative.html45
-rw-r--r--testing/web-platform/tests/soft-navigation-heuristics/navigation-api-forward.tentative.html48
-rw-r--r--testing/web-platform/tests/soft-navigation-heuristics/navigation-api-hash.tentative.html28
-rw-r--r--testing/web-platform/tests/soft-navigation-heuristics/navigation-api-preventDefault.tentative.html35
-rw-r--r--testing/web-platform/tests/soft-navigation-heuristics/navigation-api-rejected.tentative.html28
-rw-r--r--testing/web-platform/tests/soft-navigation-heuristics/navigation-api-traverseto.tentative.html49
-rw-r--r--testing/web-platform/tests/soft-navigation-heuristics/navigation-api-view-transition.tentative.html54
-rw-r--r--testing/web-platform/tests/soft-navigation-heuristics/navigation-api.tentative.html26
-rw-r--r--testing/web-platform/tests/soft-navigation-heuristics/popstate.tentative.html47
-rw-r--r--testing/web-platform/tests/soft-navigation-heuristics/replacestate-null-then-push.tentative.html34
-rw-r--r--testing/web-platform/tests/soft-navigation-heuristics/replacestate.tentative.html28
-rw-r--r--testing/web-platform/tests/soft-navigation-heuristics/resources/empty.html2
-rw-r--r--testing/web-platform/tests/soft-navigation-heuristics/resources/soft-navigation-helper.js259
-rw-r--r--testing/web-platform/tests/soft-navigation-heuristics/soft-navigation-detection-main-descendent.tentative.html30
-rw-r--r--testing/web-platform/tests/soft-navigation-heuristics/soft-navigation-detection-non-main.tentative.html25
-rw-r--r--testing/web-platform/tests/soft-navigation-heuristics/soft-navigation-detection-web-component-lifecycle.tentative.html46
-rw-r--r--testing/web-platform/tests/soft-navigation-heuristics/soft-navigation-detection.tentative.html26
-rw-r--r--testing/web-platform/tests/soft-navigation-heuristics/soft-navigation-no-url.tentative.html31
-rw-r--r--testing/web-platform/tests/soft-navigation-heuristics/supported-entry-types.tentative.html15
-rw-r--r--testing/web-platform/tests/soft-navigation-heuristics/text-lcp-followed-by-image-softnav-lcp.tentative.html31
-rw-r--r--testing/web-platform/tests/soft-navigation-heuristics/text-lcp-followed-by-text-softnav-lcp.tentative.html34
30 files changed, 1263 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..fcf5c023e4
--- /dev/null
+++ b/testing/web-platform/tests/soft-navigation-heuristics/multiple-paint-entries-buffered.tentative.html
@@ -0,0 +1,40 @@
+<!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,
+ 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..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-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..5c7d8f4f7a
--- /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 => {
+ timestamps[counter]["eventStart"] = performance.now();
+ e.intercept({commit: "after-transition", handler: async () => {
+ await addImageToMain();
+ e.commit();
+ }});
+ }, 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..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-view-transition.tentative.html b/testing/web-platform/tests/soft-navigation-heuristics/navigation-api-view-transition.tentative.html
new file mode 100644
index 0000000000..2755f9f9ac
--- /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 => {
+ timestamps[counter]["eventStart"] = performance.now();
+ 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;
+ }
+ });
+ };
+
+ 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..ca11f684f4
--- /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 => {
+ timestamps[counter]["eventStart"] = performance.now();
+ e.intercept({handler: async () => {
+ await addImageToMain();
+ }});
+ }, 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..baf26be8b0
--- /dev/null
+++ b/testing/web-platform/tests/soft-navigation-heuristics/resources/soft-navigation-helper.js
@@ -0,0 +1,259 @@
+var counter = 0;
+var clicked;
+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");
+ 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, clicks + 1);
+ }, testName);
+ };
+
+const testNavigationApi = (testName, navigateEventHandler, link) => {
+ promise_test(async t => {
+ const preClickLcp = await getLcpEntries();
+ navigation.addEventListener('navigate', navigateEventHandler);
+ const navigated = new Promise(resolve => {
+ navigation.addEventListener('navigatesuccess', resolve);
+ navigation.addEventListener('navigateerror', resolve);
+ });
+ click(link);
+ await new Promise(resolve => {
+ (new PerformanceObserver(() => resolve())).observe({
+ type: 'soft-navigation'
+ });
+ });
+ await navigated;
+ 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, entries_expected_number = 2) => {
+ await validatePaintEntries('first-contentful-paint', entries_expected_number);
+ await validatePaintEntries('first-paint', entries_expected_number);
+ 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');
+ 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');
+};
+
+const click = link => {
+ if (test_driver) {
+ test_driver.click(link);
+ timestamps[counter] = {"syncPostClick": performance.now()};
+ }
+}
+
+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 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) => {
+ 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);
+ }
+};
+
+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) => {
+ 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', includeSoftNavigationObservations: true});
+ });
+};
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>
+
+
+