summaryrefslogtreecommitdiffstats
path: root/testing/web-platform/tests/permissions-policy/experimental-features
diff options
context:
space:
mode:
Diffstat (limited to 'testing/web-platform/tests/permissions-policy/experimental-features')
-rw-r--r--testing/web-platform/tests/permissions-policy/experimental-features/focus-without-user-activation-disabled-tentative.html51
-rw-r--r--testing/web-platform/tests/permissions-policy/experimental-features/focus-without-user-activation-enabled-tentative.sub.html51
-rw-r--r--testing/web-platform/tests/permissions-policy/experimental-features/permissions-policy-header-host-wildcard.https.sub.html14
-rw-r--r--testing/web-platform/tests/permissions-policy/experimental-features/permissions-policy-header-host-wildcard.https.sub.html.sub.headers1
-rw-r--r--testing/web-platform/tests/permissions-policy/experimental-features/permissions-policy-header-port-wildcard.https.sub.html14
-rw-r--r--testing/web-platform/tests/permissions-policy/experimental-features/permissions-policy-header-port-wildcard.https.sub.html.sub.headers1
-rw-r--r--testing/web-platform/tests/permissions-policy/experimental-features/permissions-policy-header-scheme-only.https.sub.html14
-rw-r--r--testing/web-platform/tests/permissions-policy/experimental-features/permissions-policy-header-scheme-only.https.sub.html.sub.headers1
-rw-r--r--testing/web-platform/tests/permissions-policy/experimental-features/private-state-token-redemption-default-permissions-policy.tentative.https.sub.html66
-rw-r--r--testing/web-platform/tests/permissions-policy/experimental-features/private-state-token-redemption-supported-by-permissions-policy.tentative.html9
-rw-r--r--testing/web-platform/tests/permissions-policy/experimental-features/resources/common.js94
-rw-r--r--testing/web-platform/tests/permissions-policy/experimental-features/resources/focus-without-user-activation-iframe-tentative.html47
-rw-r--r--testing/web-platform/tests/permissions-policy/experimental-features/resources/lazyload-contents.html13
-rw-r--r--testing/web-platform/tests/permissions-policy/experimental-features/resources/lazyload.pngbin0 -> 20819 bytes
-rw-r--r--testing/web-platform/tests/permissions-policy/experimental-features/resources/permissions-policy-private-state-token-redemption.html61
-rw-r--r--testing/web-platform/tests/permissions-policy/experimental-features/resources/unload-helper.js41
-rw-r--r--testing/web-platform/tests/permissions-policy/experimental-features/resources/vertical-scroll-scrollable-content.html16
-rw-r--r--testing/web-platform/tests/permissions-policy/experimental-features/resources/vertical-scroll-scrollbar-ref.html13
-rw-r--r--testing/web-platform/tests/permissions-policy/experimental-features/resources/vertical-scroll-scrollintoview.html45
-rw-r--r--testing/web-platform/tests/permissions-policy/experimental-features/resources/vertical-scroll-touch-action.html14
-rw-r--r--testing/web-platform/tests/permissions-policy/experimental-features/resources/vertical-scroll-touch-block.html42
-rw-r--r--testing/web-platform/tests/permissions-policy/experimental-features/resources/vertical-scroll-wheel-block.html22
-rw-r--r--testing/web-platform/tests/permissions-policy/experimental-features/resources/vertical-scroll.js25
-rw-r--r--testing/web-platform/tests/permissions-policy/experimental-features/unload-allowed-by-default.tentative.window.js22
-rw-r--r--testing/web-platform/tests/permissions-policy/experimental-features/unload-disallowed-subframe.tentative.window.js21
-rw-r--r--testing/web-platform/tests/permissions-policy/experimental-features/unload-disallowed.tentative.window.js21
-rw-r--r--testing/web-platform/tests/permissions-policy/experimental-features/vertical-scroll-disabled-frame-no-scroll-manual.tentative.html113
-rw-r--r--testing/web-platform/tests/permissions-policy/experimental-features/vertical-scroll-disabled-scrollbar-tentative.html4
-rw-r--r--testing/web-platform/tests/permissions-policy/experimental-features/vertical-scroll-main-frame-manual.tentative.html46
-rw-r--r--testing/web-platform/tests/permissions-policy/experimental-features/vertical-scroll-main-frame-manual.tentative.html.headers1
-rw-r--r--testing/web-platform/tests/permissions-policy/experimental-features/vertical-scroll-scrollintoview.tentative.html117
-rw-r--r--testing/web-platform/tests/permissions-policy/experimental-features/vertical-scroll-touch-action-manual.tentative.html103
-rw-r--r--testing/web-platform/tests/permissions-policy/experimental-features/vertical-scroll-touch-block-manual.tentative.html237
-rw-r--r--testing/web-platform/tests/permissions-policy/experimental-features/vertical-scroll-wheel-block-manual.tentative.html145
34 files changed, 1485 insertions, 0 deletions
diff --git a/testing/web-platform/tests/permissions-policy/experimental-features/focus-without-user-activation-disabled-tentative.html b/testing/web-platform/tests/permissions-policy/experimental-features/focus-without-user-activation-disabled-tentative.html
new file mode 100644
index 0000000000..3980fd1219
--- /dev/null
+++ b/testing/web-platform/tests/permissions-policy/experimental-features/focus-without-user-activation-disabled-tentative.html
@@ -0,0 +1,51 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="./resources/common.js"></script>
+<title> 'focus-without-user-activation' Policy : Correctly block automatic focus when policy disabled
+</title>
+<body>
+<script>
+ "use strict"
+ // Note: According to html spec: https://html.spec.whatwg.org/#attr-fe-autofocus,
+ // topDocument's autofocus processed flag initially is false and is set to true
+ // after flushing autofocus candidates, i.e. flush of autofocus candidates
+ // only happens once per page load.
+ // In order to test the behaviour with both focus-without-user-activation on and off:
+ // two test files are necessary:
+ // - focus-without-user-activation-disabled-tentative.html
+ // - focus-without-user-activation-enabled-tentative.sub.html
+
+ // Use same origin url here because when iframe document has cross origin
+ // url, autofocus will be blocked by default with following console error:
+ // "Blocked autofocusing on a form control in a cross-origin subframe."
+ const url = "/permissions-policy/experimental-features/resources/focus-without-user-activation-iframe-tentative.html";
+
+ function subframe_focused(subframe, event_name, timeout) {
+ return new Promise(resolve => {
+ window.onmessage = m => resolve(m.data.focused);
+ subframe.contentWindow.postMessage({
+ event: event_name,
+ timeout: timeout
+ }, "*");
+ });
+ }
+
+ promise_test( async (instance) => {
+ const frame = createIframe(document.body, {
+ sandbox: "allow-scripts allow-same-origin",
+ allow: "focus-without-user-activation 'none'",
+ src: url
+ });
+
+ await wait_for_load(frame);
+ assert_false(await subframe_focused(frame, "autofocus", 400), "'autofocus' should not work.");
+ window.focus(); // Reset focus state in subframe.
+ assert_false(await subframe_focused(frame, "focus-input", 400), "'element.focus' should not work.");
+ window.focus(); // Reset focus state in subframe.
+ assert_false(await subframe_focused(frame, "focus-window", 400), "'window.focus' should not work.");
+ window.focus(); // Reset focus state in subframe.
+ }, "When the policy is disabled, 'autofocus' and scripted focus do not focus " +
+ "the document.");
+</script>
+</body>
diff --git a/testing/web-platform/tests/permissions-policy/experimental-features/focus-without-user-activation-enabled-tentative.sub.html b/testing/web-platform/tests/permissions-policy/experimental-features/focus-without-user-activation-enabled-tentative.sub.html
new file mode 100644
index 0000000000..5722947f86
--- /dev/null
+++ b/testing/web-platform/tests/permissions-policy/experimental-features/focus-without-user-activation-enabled-tentative.sub.html
@@ -0,0 +1,51 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="./resources/common.js"></script>
+<title> 'focus-without-user-activation' Policy : Correctly block automatic focus when policy disabled
+</title>
+<body>
+<script>
+ "use strict"
+ // Note: According to html spec: https://html.spec.whatwg.org/#attr-fe-autofocus,
+ // topDocument's autofocus processed flag initially is false and is set to true
+ // after flushing autofocus candidates, i.e. flush of autofocus candidates
+ // only happens once per page load.
+ // In order to test the behaviour with both focus-without-user-activation on and off:
+ // two test files are necessary:
+ // - focus-without-user-activation-disabled-tentative.html
+ // - focus-without-user-activation-enabled-tentative.sub.html
+
+ // Cross origin subframe should not be able to use autofocus to steal focus
+ // from main frame by default. However, with focus-without-user-activation
+ // enabled for subframe, subframe should be able to autofocus.
+ const url = "http://{{hosts[alt][www1]}}:{{ports[http][0]}}/permissions-policy/experimental-features/resources/focus-without-user-activation-iframe-tentative.html";
+
+ function subframe_focused(subframe, event_name, timeout) {
+ return new Promise(resolve => {
+ window.onmessage = m => resolve(m.data.focused);
+ subframe.contentWindow.postMessage({
+ event: event_name,
+ timeout: timeout
+ }, "*");
+ });
+ }
+
+ promise_test( async (instance) => {
+ const frame = createIframe(document.body, {
+ sandbox: "allow-scripts allow-same-origin",
+ allow: "focus-without-user-activation *",
+ src: url
+ });
+
+ await wait_for_load(frame);
+ assert_true(await subframe_focused(frame, "autofocus"), "'autofocus' should work.");
+ window.focus(); // Reset focus state in subframe.
+ assert_true(await subframe_focused(frame, "focus-input"), "'element.focus' should work.");
+ window.focus(); // Reset focus state in subframe.
+ assert_true(await subframe_focused(frame, "focus-window"), "'window.focus' should work.");
+ window.focus(); // Reset focus state in subframe.
+ }, "When the policy is enabled, 'autofocus' and scripted focus do focus " +
+ "the document.");
+</script>
+</body>
diff --git a/testing/web-platform/tests/permissions-policy/experimental-features/permissions-policy-header-host-wildcard.https.sub.html b/testing/web-platform/tests/permissions-policy/experimental-features/permissions-policy-header-host-wildcard.https.sub.html
new file mode 100644
index 0000000000..211ca7445d
--- /dev/null
+++ b/testing/web-platform/tests/permissions-policy/experimental-features/permissions-policy-header-host-wildcard.https.sub.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<body>
+ <script src=/resources/testharness.js></script>
+ <script src=/resources/testharnessreport.js></script>
+ <script>
+ test(function() {
+ var wildcard_origin = 'https://*:{{ports[https][0]}}';
+ assert_array_equals(
+ document.featurePolicy.getAllowlistForFeature('fullscreen'),
+ [wildcard_origin]);
+ assert_true(document.featurePolicy.allowsFeature('fullscreen'));
+ }, 'Test wildcard host policy works as expected');
+ </script>
+</body> \ No newline at end of file
diff --git a/testing/web-platform/tests/permissions-policy/experimental-features/permissions-policy-header-host-wildcard.https.sub.html.sub.headers b/testing/web-platform/tests/permissions-policy/experimental-features/permissions-policy-header-host-wildcard.https.sub.html.sub.headers
new file mode 100644
index 0000000000..81d0de836e
--- /dev/null
+++ b/testing/web-platform/tests/permissions-policy/experimental-features/permissions-policy-header-host-wildcard.https.sub.html.sub.headers
@@ -0,0 +1 @@
+Permissions-Policy: fullscreen=("https://*:{{ports[https][0]}}")
diff --git a/testing/web-platform/tests/permissions-policy/experimental-features/permissions-policy-header-port-wildcard.https.sub.html b/testing/web-platform/tests/permissions-policy/experimental-features/permissions-policy-header-port-wildcard.https.sub.html
new file mode 100644
index 0000000000..adb657023e
--- /dev/null
+++ b/testing/web-platform/tests/permissions-policy/experimental-features/permissions-policy-header-port-wildcard.https.sub.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<body>
+ <script src=/resources/testharness.js></script>
+ <script src=/resources/testharnessreport.js></script>
+ <script>
+ test(function() {
+ var wildcard_origin = 'https://{{domains[]}}:*';
+ assert_array_equals(
+ document.featurePolicy.getAllowlistForFeature('fullscreen'),
+ [wildcard_origin]);
+ assert_true(document.featurePolicy.allowsFeature('fullscreen'));
+ }, 'Test wildcard port policy works as expected');
+ </script>
+</body>
diff --git a/testing/web-platform/tests/permissions-policy/experimental-features/permissions-policy-header-port-wildcard.https.sub.html.sub.headers b/testing/web-platform/tests/permissions-policy/experimental-features/permissions-policy-header-port-wildcard.https.sub.html.sub.headers
new file mode 100644
index 0000000000..b813167ee6
--- /dev/null
+++ b/testing/web-platform/tests/permissions-policy/experimental-features/permissions-policy-header-port-wildcard.https.sub.html.sub.headers
@@ -0,0 +1 @@
+Permissions-Policy: fullscreen=("https://{{domains[]}}:*")
diff --git a/testing/web-platform/tests/permissions-policy/experimental-features/permissions-policy-header-scheme-only.https.sub.html b/testing/web-platform/tests/permissions-policy/experimental-features/permissions-policy-header-scheme-only.https.sub.html
new file mode 100644
index 0000000000..eab3e8f697
--- /dev/null
+++ b/testing/web-platform/tests/permissions-policy/experimental-features/permissions-policy-header-scheme-only.https.sub.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<body>
+ <script src=/resources/testharness.js></script>
+ <script src=/resources/testharnessreport.js></script>
+ <script>
+ test(function() {
+ var wildcard_origin = 'https:';
+ assert_array_equals(
+ document.featurePolicy.getAllowlistForFeature('fullscreen'),
+ [wildcard_origin]);
+ assert_true(document.featurePolicy.allowsFeature('fullscreen'));
+ }, 'Test scheme only policy works as expected');
+ </script>
+</body>
diff --git a/testing/web-platform/tests/permissions-policy/experimental-features/permissions-policy-header-scheme-only.https.sub.html.sub.headers b/testing/web-platform/tests/permissions-policy/experimental-features/permissions-policy-header-scheme-only.https.sub.html.sub.headers
new file mode 100644
index 0000000000..14d0868f2b
--- /dev/null
+++ b/testing/web-platform/tests/permissions-policy/experimental-features/permissions-policy-header-scheme-only.https.sub.html.sub.headers
@@ -0,0 +1 @@
+Permissions-Policy: fullscreen=("https:")
diff --git a/testing/web-platform/tests/permissions-policy/experimental-features/private-state-token-redemption-default-permissions-policy.tentative.https.sub.html b/testing/web-platform/tests/permissions-policy/experimental-features/private-state-token-redemption-default-permissions-policy.tentative.https.sub.html
new file mode 100644
index 0000000000..4962b42721
--- /dev/null
+++ b/testing/web-platform/tests/permissions-policy/experimental-features/private-state-token-redemption-default-permissions-policy.tentative.https.sub.html
@@ -0,0 +1,66 @@
+<!DOCTYPE html>
+<title>Test that private state token redemption is enabled/disabled according to the permissions policy</title>
+
+<body>
+ <script src=/resources/testharness.js></script>
+ <script src=/resources/testharnessreport.js></script>
+ <script src=/permissions-policy/resources/permissions-policy.js></script>
+ <script>
+ 'use strict';
+ const same_origin_src = '/permissions-policy/experimental-features/resources/permissions-policy-private-state-token-redemption.html';
+ const cross_origin_src = 'https://{{domains[www]}}:{{ports[https][0]}}' +
+ same_origin_src;
+ const header = 'Default "private-state-token-redemption" permissions policy ["self"]';
+
+ test(() => {
+ try {
+ // The permissions policy gates redemption and signing via both the Fetch
+ // and XHR interfaces.
+ new Request("https://issuer.example/", {
+ privateToken: {
+ version: 1,
+ operation: "token-redemption"
+ }
+ });
+ new Request("https://destination.example/", {
+ privateToken: {
+ version: 1,
+ operation: "send-redemption-record", // signing
+ issuers: ["https://issuer.example/"]
+ }
+ });
+
+ const redemption_xhr = new XMLHttpRequest();
+ redemption_xhr.open("GET", "https://issuer.example/");
+ redemption_xhr.setPrivateToken({
+ version: 1,
+ operation: "token-redemption"
+ });
+
+ const signing_xhr = new XMLHttpRequest();
+ signing_xhr.open("GET", "https://destination.example/");
+ signing_xhr.setPrivateToken({
+ version: 1,
+ operation: "send-redemption-record", // signing
+ issuers: ["https://issuer.example/"]
+ });
+ } catch (e) {
+ assert_unreached();
+ }
+ }, header + ' allows the top-level document.');
+
+ async_test(t => {
+ test_feature_availability('Private state token redemption', t, same_origin_src,
+ (data, desc) => {
+ assert_equals(data.num_operations_enabled, 4, desc);
+ });
+ }, header + ' allows same-origin iframes.');
+
+ async_test(t => {
+ test_feature_availability('Private state token redemption', t, cross_origin_src,
+ (data, desc) => {
+ assert_equals(data.num_operations_enabled, 0, desc);
+ });
+ }, header + ' disallows cross-origin iframes.');
+ </script>
+</body>
diff --git a/testing/web-platform/tests/permissions-policy/experimental-features/private-state-token-redemption-supported-by-permissions-policy.tentative.html b/testing/web-platform/tests/permissions-policy/experimental-features/private-state-token-redemption-supported-by-permissions-policy.tentative.html
new file mode 100644
index 0000000000..0399f167fb
--- /dev/null
+++ b/testing/web-platform/tests/permissions-policy/experimental-features/private-state-token-redemption-supported-by-permissions-policy.tentative.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<title>Test that private state token redemption is advertised in the feature list</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+ test(() => {
+ assert_in_array('private-state-token-redemption', document.featurePolicy.features());
+ }, 'document.featurePolicy.features should advertise private-state-token-redemption.');
+</script>
diff --git a/testing/web-platform/tests/permissions-policy/experimental-features/resources/common.js b/testing/web-platform/tests/permissions-policy/experimental-features/resources/common.js
new file mode 100644
index 0000000000..308f787da6
--- /dev/null
+++ b/testing/web-platform/tests/permissions-policy/experimental-features/resources/common.js
@@ -0,0 +1,94 @@
+const url_base = "/permissions-policy/experimental-features/resources/";
+window.messageResponseCallback = null;
+
+function setFeatureState(iframe, feature, origins) {
+ iframe.setAttribute("allow", `${feature} ${origins};`);
+}
+
+// Returns a promise which is resolved when the <iframe> is navigated to |url|
+// and "load" handler has been called.
+function loadUrlInIframe(iframe, url) {
+ return new Promise((resolve) => {
+ iframe.addEventListener("load", resolve);
+ iframe.src = url;
+ });
+}
+
+// Posts |message| to |target| and resolves the promise with the response coming
+// back from |target|.
+function sendMessageAndGetResponse(target, message) {
+ return new Promise((resolve) => {
+ window.messageResponseCallback = resolve;
+ target.postMessage(message, "*");
+ });
+}
+
+
+function onMessage(e) {
+ if (window.messageResponseCallback) {
+ window.messageResponseCallback(e.data);
+ window.messageResponseCallback = null;
+ }
+}
+
+window.addEventListener("message", onMessage);
+
+// Waits for |load_timeout| before resolving the promise. It will resolve the
+// promise sooner if a message event with |e.data.id| of |id| is received.
+// In such a case the response is the contents of the message |e.data.contents|.
+// Otherwise, returns false (when timeout occurs).
+function waitForMessageOrTimeout(t, id, load_timeout) {
+ return new Promise((resolve) => {
+ window.addEventListener(
+ "message",
+ (e) => {
+ if (!e.data || e.data.id !== id)
+ return;
+ resolve(e.data.contents);
+ }
+ );
+ t.step_timeout(() => { resolve(false); }, load_timeout);
+ });
+}
+
+function createIframe(container, attributes) {
+ var new_iframe = document.createElement("iframe");
+ for (attr_name in attributes)
+ new_iframe.setAttribute(attr_name, attributes[attr_name]);
+ container.appendChild(new_iframe);
+ return new_iframe;
+}
+
+// Returns a promise which is resolved when |load| event is dispatched for |e|.
+function wait_for_load(e) {
+ return new Promise((resolve) => {
+ e.addEventListener("load", resolve);
+ });
+}
+
+setup(() => {
+ window.reporting_observer_instance = new ReportingObserver((reports, observer) => {
+ if (window.reporting_observer_callback) {
+ reports.forEach(window.reporting_observer_callback);
+ }
+ }, {types: ["permissions-policy-violation"]});
+ window.reporting_observer_instance.observe();
+ window.reporting_observer_callback = null;
+});
+
+// Waits for a violation in |feature| and source file containing |file_name|.
+function wait_for_violation_in_file(feature, file_name) {
+ return new Promise( (resolve) => {
+ assert_equals(null, window.reporting_observer_callback);
+ window.reporting_observer_callback = (report) => {
+ var feature_match = (feature === report.body.featureId);
+ var file_name_match =
+ !file_name ||
+ (report.body.sourceFile.indexOf(file_name) !== -1);
+ if (feature_match && file_name_match) {
+ window.reporting_observer_callback = null;
+ resolve(report);
+ }
+ };
+ });
+}
diff --git a/testing/web-platform/tests/permissions-policy/experimental-features/resources/focus-without-user-activation-iframe-tentative.html b/testing/web-platform/tests/permissions-policy/experimental-features/resources/focus-without-user-activation-iframe-tentative.html
new file mode 100644
index 0000000000..3d5aab95ad
--- /dev/null
+++ b/testing/web-platform/tests/permissions-policy/experimental-features/resources/focus-without-user-activation-iframe-tentative.html
@@ -0,0 +1,47 @@
+<!doctype html>
+<input autofocus onfocus="autofocus_onfocus()"/>
+<script>
+ let autofocused = false;
+ function autofocus_onfocus() {
+ autofocused = true;
+ }
+
+ /**
+ * @param target object: Target to call |focus()| with.
+ * @param timeout integer | undefined: Timeout to wait for the focus event.
+ * If unspecified, a timeout will not be set.
+ * @param focus_target boolean | undefined: Wether to focus the target after
+ * listening for |onfocus| event.
+ */
+ function wait_focus_event(target, timeout, focus_target) {
+ return new Promise((resolve) => {
+ if (timeout)
+ setTimeout(() => resolve(false), timeout);
+
+ target.onfocus = () => resolve(true);
+ if (focus_target)
+ target.focus();
+ });
+ }
+
+ function post_result(destination, result) {
+ destination.postMessage({focused: result}, "*");
+ }
+
+ window.addEventListener("message", (e) => {
+ if (e.data.event === "autofocus") {
+ if (autofocused)
+ post_result(e.source, true);
+
+ wait_focus_event(document.querySelector("input"), e.data.timeout)
+ .then(result => post_result(e.source, result));
+ } else if (e.data.event === "focus-window") {
+ wait_focus_event(window, e.data.timeout, true /* focus_target */)
+ .then(result => post_result(e.source, result));
+ } else if (e.data.event === "focus-input") {
+ const input_element = document.querySelector("input");
+ wait_focus_event(input_element, e.data.timeout, true /* focus_target */)
+ .then(result => post_result(e.source, result));
+ }
+});
+</script>
diff --git a/testing/web-platform/tests/permissions-policy/experimental-features/resources/lazyload-contents.html b/testing/web-platform/tests/permissions-policy/experimental-features/resources/lazyload-contents.html
new file mode 100644
index 0000000000..a6e98c24e6
--- /dev/null
+++ b/testing/web-platform/tests/permissions-policy/experimental-features/resources/lazyload-contents.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<body>
+ <p>This page is lazyloaded.</p>
+ <script>
+ let query_index = window.location.href.indexOf("?id=");
+ let id = window.location.href.substr(query_index + 4);
+ window.addEventListener("load", () => {
+ let p = document.querySelector("p");
+ let contents = p ? p.innerHTML : "null";
+ window.parent.postMessage({"id": id, "contents": contents}, "*");
+ });
+ </script>
+</body>
diff --git a/testing/web-platform/tests/permissions-policy/experimental-features/resources/lazyload.png b/testing/web-platform/tests/permissions-policy/experimental-features/resources/lazyload.png
new file mode 100644
index 0000000000..fd3da53a29
--- /dev/null
+++ b/testing/web-platform/tests/permissions-policy/experimental-features/resources/lazyload.png
Binary files differ
diff --git a/testing/web-platform/tests/permissions-policy/experimental-features/resources/permissions-policy-private-state-token-redemption.html b/testing/web-platform/tests/permissions-policy/experimental-features/resources/permissions-policy-private-state-token-redemption.html
new file mode 100644
index 0000000000..7a055f0e7b
--- /dev/null
+++ b/testing/web-platform/tests/permissions-policy/experimental-features/resources/permissions-policy-private-state-token-redemption.html
@@ -0,0 +1,61 @@
+<!DOCTYPE html>
+<script>
+ 'use strict';
+
+ window.onload = function() {
+ // When the private-state-token-redemption permissions policy is enabled, redemption
+ // and signing ("send-redemption-record") should both be available; when it's disabled,
+ // they should both be unavailable. Send the number of available operations
+ // upstream in order to enforce this in assertions.
+ let num_enabled = 4;
+ try {
+ new Request("https://issuer.example/", {
+ privateToken: {
+ version: 1,
+ operation: "token-redemption"
+ }
+ });
+ } catch (e) {
+ num_enabled--;
+ }
+ try {
+ new Request("https://destination.example/", {
+ privateToken: {
+ version: 1,
+ operation: "send-redemption-record",
+ issuers: ["https://issuer.example/"]
+ }
+ });
+ } catch (e) {
+ num_enabled--;
+ }
+
+ try {
+ const xhr = new XMLHttpRequest();
+ xhr.open("GET", "https://issuer.example/");
+ xhr.setPrivateToken({
+ version: 1,
+ operation: "token-redemption"
+ });
+ } catch (e) {
+ num_enabled--;
+ }
+
+ try {
+ const xhr = new XMLHttpRequest();
+ xhr.open("GET", "https://destination.example/");
+ xhr.setPrivateToken({
+ version: 1,
+ operation: "send-redemption-record",
+ issuers: ["https://issuer.example/"]
+ });
+ } catch (e) {
+ num_enabled--;
+ }
+
+ parent.postMessage({
+ type: 'availability-result',
+ num_operations_enabled: num_enabled,
+ }, '*');
+ }
+</script>
diff --git a/testing/web-platform/tests/permissions-policy/experimental-features/resources/unload-helper.js b/testing/web-platform/tests/permissions-policy/experimental-features/resources/unload-helper.js
new file mode 100644
index 0000000000..9739ead69d
--- /dev/null
+++ b/testing/web-platform/tests/permissions-policy/experimental-features/resources/unload-helper.js
@@ -0,0 +1,41 @@
+// Code used by controlling frame of the unload policy tests.
+
+const MAIN_FRAME = 'main';
+const SUBFRAME = 'sub';
+
+async function isUnloadAllowed(remoteContextWrapper) {
+ return remoteContextWrapper.executeScript(() => {
+ return document.featurePolicy.allowsFeature('unload');
+ });
+}
+
+// Checks whether a frame runs unload handlers.
+// This checks the policy directly and also installs an unload handler and
+// navigates the frame checking that the handler ran.
+async function assertWindowRunsUnload(
+ remoteContextWrapper, name, {shouldRunUnload}) {
+ const maybeNot = shouldRunUnload ? '' : 'not ';
+ assert_equals(
+ await isUnloadAllowed(remoteContextWrapper), shouldRunUnload,
+ `${name}: unload in ${name} should ${maybeNot}be allowed`);
+
+ // Set up recording of whether unload handler ran.
+ await remoteContextWrapper.executeScript((name) => {
+ localStorage.setItem(name, 'did not run');
+ addEventListener('unload', () => localStorage.setItem(name, 'did run'));
+ }, [name]);
+
+ // Navigate away and then back.
+ const second = await remoteContextWrapper.navigateToNew();
+ // Navigating back ensures that the unload has completed.
+ // Also if the navigation is cross-site then we have to return
+ // to the original origin in order to read the recorded unload.
+ second.historyBack();
+
+ // Check that unload handlers ran as expected.
+ const recordedUnload = await remoteContextWrapper.executeScript(
+ (name) => localStorage.getItem(name), [name]);
+ assert_equals(
+ recordedUnload, `did ${maybeNot}run`,
+ `${name}: unload should ${maybeNot}have run`);
+}
diff --git a/testing/web-platform/tests/permissions-policy/experimental-features/resources/vertical-scroll-scrollable-content.html b/testing/web-platform/tests/permissions-policy/experimental-features/resources/vertical-scroll-scrollable-content.html
new file mode 100644
index 0000000000..9f78ea4bc2
--- /dev/null
+++ b/testing/web-platform/tests/permissions-policy/experimental-features/resources/vertical-scroll-scrollable-content.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<style>
+ body, html {
+ height: 100%;
+ width: 100%;
+ }
+ #spacer {
+ width: 1500px;
+ height: 1500px;
+ background-color: red;
+ }
+</style>
+<body>
+ <p>This page is scrollable.</p>
+ <div id="spacer"></div>
+</body>
diff --git a/testing/web-platform/tests/permissions-policy/experimental-features/resources/vertical-scroll-scrollbar-ref.html b/testing/web-platform/tests/permissions-policy/experimental-features/resources/vertical-scroll-scrollbar-ref.html
new file mode 100644
index 0000000000..fd432b33f6
--- /dev/null
+++ b/testing/web-platform/tests/permissions-policy/experimental-features/resources/vertical-scroll-scrollbar-ref.html
@@ -0,0 +1,13 @@
+<!doctype html>
+<title>Ref: vertical-scroll test for scrollbar</title>
+<iframe src="/permissions-policy/experimental-features/resources/vertical-scroll-scrollable-content.html"></iframe>
+<script>
+ let iframe = document.querySelector("iframe");
+ let overflow_y = "visible";
+ if (window.location.search.indexOf("no-vertical-scrollbar") !== -1)
+ overflow_y = "hidden"
+ iframe.addEventListener("load", () => {
+ iframe.contentDocument.body.style.overflowY = overflow_y;
+ });
+</script>
+
diff --git a/testing/web-platform/tests/permissions-policy/experimental-features/resources/vertical-scroll-scrollintoview.html b/testing/web-platform/tests/permissions-policy/experimental-features/resources/vertical-scroll-scrollintoview.html
new file mode 100644
index 0000000000..7bed27c260
--- /dev/null
+++ b/testing/web-platform/tests/permissions-policy/experimental-features/resources/vertical-scroll-scrollintoview.html
@@ -0,0 +1,45 @@
+<!DOCTYPE html>
+<style>
+html, body, #container {
+ width: 100%;
+ height: 100%;
+}
+
+#spacer {
+ width: 200%;
+ height: 200%;
+}
+</style>
+<div id="container">
+ <div id="spacer"></div>
+ <button>Element To Scroll</button>
+</div>
+<script>
+ window.addEventListener('message', onMessageReceived);
+
+ function scrollingElementBounds() {
+ var rect = document.querySelector("button").getBoundingClientRect();
+ return {
+ x: rect.x, y: rect.y, width: rect.width, height: rect.height
+ };
+ }
+
+ function onMessageReceived(e) {
+ if (!e.data || !e.data.type)
+ return;
+ switch(e.data.type) {
+ case "scroll":
+ document.querySelector("button").scrollIntoView({behavior: "instant"});
+ ackMessage({bounds: scrollingElementBounds()}, e.source);
+ break;
+
+ case "scrolling-element-bounds":
+ ackMessage({bounds: scrollingElementBounds()}, e.source);
+ break;
+ }
+ }
+
+ function ackMessage(msg, source) {
+ source.postMessage(msg, "*");
+ }
+</script>
diff --git a/testing/web-platform/tests/permissions-policy/experimental-features/resources/vertical-scroll-touch-action.html b/testing/web-platform/tests/permissions-policy/experimental-features/resources/vertical-scroll-touch-action.html
new file mode 100644
index 0000000000..51b715f30a
--- /dev/null
+++ b/testing/web-platform/tests/permissions-policy/experimental-features/resources/vertical-scroll-touch-action.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<style>
+ body, html {
+ height: 100%;
+ width: 100%;
+ overflow: hidden;
+ }
+ body {
+ touch-action: none;
+ }
+</style>
+<body>
+ <p>This page blocks all 'touch-action'.</p>
+</body>
diff --git a/testing/web-platform/tests/permissions-policy/experimental-features/resources/vertical-scroll-touch-block.html b/testing/web-platform/tests/permissions-policy/experimental-features/resources/vertical-scroll-touch-block.html
new file mode 100644
index 0000000000..4c204055af
--- /dev/null
+++ b/testing/web-platform/tests/permissions-policy/experimental-features/resources/vertical-scroll-touch-block.html
@@ -0,0 +1,42 @@
+<!DOCTYPE html>
+<style>
+body, div, html {
+ height: 100%;
+ width: 100%;
+ overflow: hidden;
+}
+p {
+ margin-bottom: 1000px;
+ margin-right: 1000px;
+}
+</style>
+<body>
+ <div id="blocker">
+ <p>This page blocks 'touchstart' and 'touchmove'.</p>
+ </div>
+ <script>
+ function doSomethingUnimportant(e) {
+ return false !== e;
+ }
+
+ function preventDefault(e) {
+ e.preventDefault();
+ }
+
+ document.addEventListener("touchstart", doSomethingUnimportant, {passive: false});
+ document.addEventListener("touchmove", doSomethingUnimportant, {passive: false});
+
+ function messageHandler(e) {
+ let msg = e.data;
+ let element = document.querySelector(msg.query);
+ if (msg.prevent) {
+ element.addEventListener(msg.event_type, preventDefault, {passive: false});
+ } else {
+ element.addEventListener(msg.event_type, doSomethingUnimportant);
+ }
+ e.source.postMessage(msg, "*");
+ }
+
+ window.addEventListener("message", messageHandler);
+ </script>
+</body>
diff --git a/testing/web-platform/tests/permissions-policy/experimental-features/resources/vertical-scroll-wheel-block.html b/testing/web-platform/tests/permissions-policy/experimental-features/resources/vertical-scroll-wheel-block.html
new file mode 100644
index 0000000000..21fc2b9b39
--- /dev/null
+++ b/testing/web-platform/tests/permissions-policy/experimental-features/resources/vertical-scroll-wheel-block.html
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<style>
+ body, html {
+ height: 100%;
+ width: 100%;
+ overflow: hidden;
+ }
+</style>
+<body>
+ <p>This page blocks all 'mouse-wheel'.</p>
+<script>
+ function defaultScroll() {
+ window.scrollTo(0, 0);
+ }
+
+ document.body.addEventListener(
+ "wheel",
+ (e) => { e.preventDefault(); defaultScroll(); }, {passive: false});
+
+ defaultScroll();
+</script>
+</body>
diff --git a/testing/web-platform/tests/permissions-policy/experimental-features/resources/vertical-scroll.js b/testing/web-platform/tests/permissions-policy/experimental-features/resources/vertical-scroll.js
new file mode 100644
index 0000000000..88835cc602
--- /dev/null
+++ b/testing/web-platform/tests/permissions-policy/experimental-features/resources/vertical-scroll.js
@@ -0,0 +1,25 @@
+function rectMaxY(rect) {
+ return rect.height + rect.y;
+}
+
+function rectMaxX(rect) {
+ return rect.width + rect.x;
+}
+
+function isEmptyRect(rect) {
+ return !rect.width || !rect.height;
+}
+
+// Returns true if the given rectangles intersect.
+function rects_intersect(rect1, rect2) {
+ if (isEmptyRect(rect1) || isEmptyRect(rect2))
+ return false;
+ return rect1.x < rectMaxX(rect2) &&
+ rect2.x < rectMaxX(rect1) &&
+ rect1.y < rectMaxY(rect2) &&
+ rect2.y < rectMaxY(rect1);
+}
+
+function rectToString(rect) {
+ return `Location: (${rect.x}, ${rect.y}) Size: (${rect.width}, ${rect.height})`;
+}
diff --git a/testing/web-platform/tests/permissions-policy/experimental-features/unload-allowed-by-default.tentative.window.js b/testing/web-platform/tests/permissions-policy/experimental-features/unload-allowed-by-default.tentative.window.js
new file mode 100644
index 0000000000..3fdc9ed047
--- /dev/null
+++ b/testing/web-platform/tests/permissions-policy/experimental-features/unload-allowed-by-default.tentative.window.js
@@ -0,0 +1,22 @@
+// META: title='unload' Policy : allowed by default
+// META: script=/common/dispatcher/dispatcher.js
+// META: script=/common/get-host-info.sub.js
+// META: script=/common/utils.js
+// META: script=/html/browsers/browsing-the-web/remote-context-helper/resources/remote-context-helper.js
+// META: script=./resources/unload-helper.js
+// META: timeout=long
+
+'use strict';
+
+// Check that unload is allowed by policy in main frame and subframe by default.
+promise_test(async t => {
+ const rcHelper =
+ new RemoteContextHelper({scripts: ['./resources/unload-helper.js']});
+ // In the same browsing context group to ensure BFCache is not used.
+ const main = await rcHelper.addWindow();
+ const sameOriginSubframe = await main.addIframe();
+ const crossOriginSubframe = await main.addIframe({ origin: 'HTTP_REMOTE_ORIGIN' });
+ await assertWindowRunsUnload(sameOriginSubframe, 'sameOriginSubframe', { shouldRunUnload: true });
+ await assertWindowRunsUnload(crossOriginSubframe, 'crossOriginSubframe', { shouldRunUnload: true });
+ await assertWindowRunsUnload(main, 'main', {shouldRunUnload: true});
+});
diff --git a/testing/web-platform/tests/permissions-policy/experimental-features/unload-disallowed-subframe.tentative.window.js b/testing/web-platform/tests/permissions-policy/experimental-features/unload-disallowed-subframe.tentative.window.js
new file mode 100644
index 0000000000..b2fb19ae12
--- /dev/null
+++ b/testing/web-platform/tests/permissions-policy/experimental-features/unload-disallowed-subframe.tentative.window.js
@@ -0,0 +1,21 @@
+// META: title='unload' Policy : allowed in main frame but disallowed in subframe
+// META: script=/common/dispatcher/dispatcher.js
+// META: script=/common/utils.js
+// META: script=/html/browsers/browsing-the-web/remote-context-helper/resources/remote-context-helper.js
+// META: script=./resources/unload-helper.js
+// META: timeout=long
+
+'use strict';
+
+// Check that unload is allowed by policy in main but can be disabled in the
+// subframe.
+promise_test(async t => {
+ const rcHelper =
+ new RemoteContextHelper({scripts: ['./resources/unload-helper.js']});
+ // In the same browsing context group to ensure BFCache is not used.
+ const main = await rcHelper.addWindow();
+ const subframe =
+ await main.addIframe({headers: [['Permissions-Policy', 'unload=()']]});
+ await assertWindowRunsUnload(subframe, 'subframe', {shouldRunUnload: false});
+ await assertWindowRunsUnload(main, 'main', {shouldRunUnload: true});
+});
diff --git a/testing/web-platform/tests/permissions-policy/experimental-features/unload-disallowed.tentative.window.js b/testing/web-platform/tests/permissions-policy/experimental-features/unload-disallowed.tentative.window.js
new file mode 100644
index 0000000000..c93443c101
--- /dev/null
+++ b/testing/web-platform/tests/permissions-policy/experimental-features/unload-disallowed.tentative.window.js
@@ -0,0 +1,21 @@
+// META: title='unload' Policy : disallowed when header is ()
+// META: script=/common/dispatcher/dispatcher.js
+// META: script=/common/utils.js
+// META: script=/html/browsers/browsing-the-web/remote-context-helper/resources/remote-context-helper.js
+// META: script=./resources/unload-helper.js
+// META: timeout=long
+
+'use strict';
+
+// Check that unload can be disabled by policy in main frame and subframe.
+promise_test(async t => {
+ const rcHelper =
+ new RemoteContextHelper({scripts: ['./resources/unload-helper.js']});
+ // In the same browsing context group to ensure BFCache is not used.
+ const main = await rcHelper.addWindow(
+ {headers: [['Permissions-Policy', 'unload=()']]},
+ );
+ const subframe = await main.addIframe();
+ await assertWindowRunsUnload(subframe, 'subframe', {shouldRunUnload: false});
+ await assertWindowRunsUnload(main, 'main', {shouldRunUnload: false});
+});
diff --git a/testing/web-platform/tests/permissions-policy/experimental-features/vertical-scroll-disabled-frame-no-scroll-manual.tentative.html b/testing/web-platform/tests/permissions-policy/experimental-features/vertical-scroll-disabled-frame-no-scroll-manual.tentative.html
new file mode 100644
index 0000000000..67cae05ee8
--- /dev/null
+++ b/testing/web-platform/tests/permissions-policy/experimental-features/vertical-scroll-disabled-frame-no-scroll-manual.tentative.html
@@ -0,0 +1,113 @@
+<!doctype html>
+<meta name="timeout" content="long">
+<title>vertical-scroll test for touch-action</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/permissions-policy/experimental-features/resources/common.js"></script>
+<script src="/permissions-policy/experimental-features/resources/vertical-scroll.js"></script>
+<style>
+html, body {
+ height: 100%;
+ width: 100%;
+}
+
+iframe {
+ width: 90%;
+ height: 90%;
+ margin: 0;
+ padding: 0;
+}
+
+.spacer {
+ width: 100%;
+ height: 100%;
+ margin: 100%;
+}
+</style>
+<iframe></iframe>
+<br/>
+<p>Spacers below to make page scrollable</p>
+<br/>
+<div class="spacer"></div>
+<div class="spacer"></div>
+<p> EOF </p>
+
+<script>
+ "use strict";
+
+ let url = url_base + "vertical-scroll-scrollable-content.html";
+ let iframeElement = document.querySelector("iframe");
+
+ // Wait for the helper scripts to load.
+ promise_test(async() => {
+ if (window.input_api_ready)
+ return Promise.resolve();
+ await new Promise( (r) => {
+ window.resolve_on_input_api_ready = r;
+ });
+ }, "Make sure input injection API is ready.");
+
+ // Sanity-check: Verify we can scroll using the test-API (empty <iframe>).
+ promise_test(async() => {
+ window.scrollTo(0, 0);
+
+ await inject_input("down");
+ assert_greater_than(window.scrollY, 0, "Page must have scrolled down.");
+
+ await inject_input("right");
+ assert_greater_than(window.scrollX, 0, "Page must have scrolled right.");
+ }, "Verify that the page normally scrolls.");
+
+ // Sanity-check: <iframe> normally scrolls.
+ promise_test(async() => {
+ // Make sure <window> can scroll both towards right and bottom.
+ window.scrollTo(0, 0);
+
+ await loadUrlInIframe(iframeElement, url);
+ iframeElement.contentWindow.scrollTo(0, 0);
+
+ await inject_input("down");
+ assert_greater_than(
+ iframeElement.contentWindow.scrollY,
+ 0,
+ "<iframe> must have scrolled down.");
+
+
+ // Apply the scroll gesture.
+ await inject_input("right");
+ assert_greater_than(
+ iframeElement.contentWindow.scrollX,
+ 0,
+ "<iframe> must have scrolled right.");
+
+ }, "Verify that the <iframe> normally scrolls.");
+
+ // Disable 'vertical-scroll': <iframe> should only scroll horizontally.
+ promise_test(async() => {
+ window.scrollTo(0, 0);
+
+ // Disallow vertical scroll and reload the <iframe>.
+ setFeatureState(iframeElement, "vertical-scroll", "'none'");
+ await loadUrlInIframe(iframeElement, url);
+ iframeElement.contentWindow.scrollTo(0, 0);
+
+ // Apply the scroll gesture. Main frame should and <iframe> should not
+ // scroll vertically.
+ await inject_input("down");
+ assert_equals(iframeElement.contentWindow.scrollY,
+ 0,
+ "<iframe> must not scroll vertically.");
+ assert_greater_than(window.scrollY,
+ 0,
+ "Page must scroll vertically.");
+
+ window.scrollTo(0, 0);
+ iframeElement.contentWindow.scrollTo(0, 0);
+
+ await inject_input("right");
+ assert_greater_than(iframeElement.contentWindow.scrollX,
+ 0,
+ "<iframe> must have scrolled right.");
+ }, "When 'vertical-scroll' is disabled in a document, scrollable contents " +
+ "can only *horizontally* scroll.");
+</script>
diff --git a/testing/web-platform/tests/permissions-policy/experimental-features/vertical-scroll-disabled-scrollbar-tentative.html b/testing/web-platform/tests/permissions-policy/experimental-features/vertical-scroll-disabled-scrollbar-tentative.html
new file mode 100644
index 0000000000..6522254076
--- /dev/null
+++ b/testing/web-platform/tests/permissions-policy/experimental-features/vertical-scroll-disabled-scrollbar-tentative.html
@@ -0,0 +1,4 @@
+<!doctype html>
+<title>vertical-scroll test for vertical scrollbar</title>
+<link rel="match" href="/permissions-policy/experimental-features/resources/vertical-scroll-scrollbar-ref.html?no-vertical-scrollbar">
+<iframe src="/permissions-policy/experimental-features/resources/vertical-scroll-scrollable-content.html" allow="vertical-scroll 'none'"></iframe>
diff --git a/testing/web-platform/tests/permissions-policy/experimental-features/vertical-scroll-main-frame-manual.tentative.html b/testing/web-platform/tests/permissions-policy/experimental-features/vertical-scroll-main-frame-manual.tentative.html
new file mode 100644
index 0000000000..cda6c49abb
--- /dev/null
+++ b/testing/web-platform/tests/permissions-policy/experimental-features/vertical-scroll-main-frame-manual.tentative.html
@@ -0,0 +1,46 @@
+<!DOCTYPE html>
+<title>Ensure 'vertical-scroll' does not affect main frame</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/permissions-policy/experimental-features/resources/common.js"></script>
+<script src="/permissions-policy/experimental-features/resources/vertical-scroll.js"></script>
+<style>
+html, body {
+ height: 100%;
+ width: 100%;
+}
+.spacer {
+ width: 100%;
+ height: 100%;
+ margin-top: 100%;
+ margin-bottom: 100%;
+}
+</style>
+<p> Making sure there is room for vertical scroll </p>
+<div class="spacer"></div>
+<div class="spacer"></div>
+</p>EOP</p>
+<script>
+ "use strict";
+
+ // Sanity check.
+ test(() => {
+ assert_false(document.featurePolicy.allowsFeature("vertical-scroll"),
+ "Expected 'vertical-scroll' to be disabled.");
+ }, "'vertical-scroll' disabled in main document.");
+
+ // Wait for the helper scripts to load.
+ promise_test(async() => {
+ if (window.input_api_ready)
+ return Promise.resolve();
+ await new Promise( (r) => {
+ window.resolve_on_input_api_ready = r;
+ });
+ }, "Make sure input injection API is ready.");
+
+ promise_test(async() => {
+ window.scrollTo(0, 0);
+ await inject_input("down");
+ assert_greater_than(window.scrollY, 0, "Page must have scrolled down.");
+ }, "Verify that the page scrolls vertically.");
+</script>
diff --git a/testing/web-platform/tests/permissions-policy/experimental-features/vertical-scroll-main-frame-manual.tentative.html.headers b/testing/web-platform/tests/permissions-policy/experimental-features/vertical-scroll-main-frame-manual.tentative.html.headers
new file mode 100644
index 0000000000..8d9c01e148
--- /dev/null
+++ b/testing/web-platform/tests/permissions-policy/experimental-features/vertical-scroll-main-frame-manual.tentative.html.headers
@@ -0,0 +1 @@
+Permissions-Policy: vertical-scroll=()
diff --git a/testing/web-platform/tests/permissions-policy/experimental-features/vertical-scroll-scrollintoview.tentative.html b/testing/web-platform/tests/permissions-policy/experimental-features/vertical-scroll-scrollintoview.tentative.html
new file mode 100644
index 0000000000..4f78dd914a
--- /dev/null
+++ b/testing/web-platform/tests/permissions-policy/experimental-features/vertical-scroll-scrollintoview.tentative.html
@@ -0,0 +1,117 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/permissions-policy/experimental-features/resources/common.js"></script>
+<script src="/permissions-policy/experimental-features/resources/vertical-scroll.js"></script>
+<style>
+html, body {
+ height: 100%;
+ width: 100%;
+}
+
+iframe {
+ width: 95%;
+ height: 95%;
+ overflow: scroll;
+ margin-top: 200%;
+}
+
+.spacer {
+ width: 100%;
+ height: 100%;
+ margin-top: 100%;
+ margin-bottom: 100%;
+}
+
+</style>
+<p> An &lt;iframe&gt; further below which is not allowed to block scroll.</p>
+<div class="spacer"></div>
+<iframe></iframe>
+<p> Making sure there is room for vertical scroll </p>
+<script>
+ "use strict";
+
+ let url = url_base + "vertical-scroll-scrollintoview.html";
+ let iframeElement = document.querySelector("iframe");
+
+ function iframeBounds() {
+ return iframeElement.getBoundingClientRect();
+ }
+
+ // Enabled 'vertical-scroll': scrollIntoView should work in all frames.
+ promise_test(async() => {
+ window.scrollTo(0, 0);
+ await loadUrlInIframe(iframeElement, url);
+
+ await sendMessageAndGetResponse(
+ iframeElement.contentWindow,
+ {type: "scrolling-element-bounds"}).then((response) => {
+ let iframeBoundsAtOrigin = {
+ x: 0,
+ y: 0,
+ width: iframeBounds().width,
+ height: iframeBounds().height};
+ let scrollingElementBounds = response.bounds;
+ assert_false(
+ rects_intersect(iframeBoundsAtOrigin, scrollingElementBounds),
+ "Scrolling element should not be visible in <iframe>." +
+ `Scrolling element's bounds is: ${rectToString(response.bounds)} ` +
+ "but <iframe>'s size is:" +
+ `${iframeBounds().width}x${iframeBounds().height}.`);
+ });
+
+ // Scroll the scrolling element inside the <iframe>.
+ await sendMessageAndGetResponse(iframeElement.contentWindow,
+ {type: "scroll"});
+ // The page should have scrolled vertically.
+ assert_greater_than(window.scrollY,
+ 0,
+ "Main frame must scroll vertically.");
+ }, "Calling 'scrollIntoView()' inside a <iframe> will propagate up by" +
+ " default('vertical-scroll' enabled).");
+
+ // Disabled 'vertical-scroll': The scope of scrollIntoView is within the set
+ // of disabled frames (does not propagate to main frame).
+ promise_test(async() => {
+ window.scrollTo(0, 0);
+ setFeatureState(iframeElement, "vertical-scroll", "'none'");
+ await loadUrlInIframe(iframeElement, url);
+
+ await sendMessageAndGetResponse(
+ iframeElement.contentWindow,
+ {type: "scrolling-element-bounds"}).then((response) => {
+ let iframeBoundsAtOrigin = {
+ x: 0,
+ y: 0,
+ width: iframeBounds().width,
+ height: iframeBounds().height};
+ let scrollingElementBounds = response.bounds;
+ assert_false(rects_intersect(iframeBoundsAtOrigin, scrollingElementBounds),
+ "Scrolling element should not be visible in <iframe>." +
+ `Scrolling element's bounds is: ${rectToString(response.bounds)}` +
+ "but <iframe>'s size is:" +
+ `${iframeBounds().width}x${iframeBounds().height}.`);
+ });
+
+ // Scroll scrolling element inside the <iframe>.
+ await sendMessageAndGetResponse(iframeElement.contentWindow,
+ {type: "scroll"}).then((response) => {
+ // Make sure the nested <iframe> is visible.
+ let scrollingElementBounds = response.bounds;
+ let iframeBoundsAtOrigin = {
+ x: 0,
+ y: 0,
+ width: iframeBounds().width,
+ height: iframeBounds().height};
+ // The scrolling element should be visible inside <iframe>.
+ assert_true(rects_intersect(iframeBoundsAtOrigin, scrollingElementBounds),
+ "Scrolling element should be visible in <iframe>." +
+ `Scrolling element's bounds is: ${rectToString(response.bounds)}` +
+ "but <iframe>'s size is:" +
+ `${iframeBounds().width}, ${iframeBounds().height}.`);
+ // The page however should not have scrolled.
+ assert_equals(window.scrollY, 0, "Main frame must not scroll vertically.");
+ });
+ }, "Calling 'scrollIntoView()' inside a <iframe> with" +
+ " 'vertical-scroll none;'will not propagate upwards.");
+</script>
diff --git a/testing/web-platform/tests/permissions-policy/experimental-features/vertical-scroll-touch-action-manual.tentative.html b/testing/web-platform/tests/permissions-policy/experimental-features/vertical-scroll-touch-action-manual.tentative.html
new file mode 100644
index 0000000000..8c2eec93b7
--- /dev/null
+++ b/testing/web-platform/tests/permissions-policy/experimental-features/vertical-scroll-touch-action-manual.tentative.html
@@ -0,0 +1,103 @@
+<!doctype html>
+<meta name="timeout" content="long">
+<title>vertical-scroll test for touch-action</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/permissions-policy/experimental-features/resources/common.js"></script>
+<script src="/permissions-policy/experimental-features/resources/vertical-scroll.js"></script>
+<style>
+html, body {
+ height: 100%;
+ width: 100%;
+}
+
+iframe {
+ width: 90%;
+ height: 90%;
+ margin: 0;
+ padding: 0;
+}
+
+.spacer {
+ width: 100%;
+ height: 100%;
+ margin: 100%;
+}
+</style>
+<iframe></iframe>
+<br/>
+<p>Spacers below to make page scrollable</p>
+<br/>
+<div class="spacer"></div>
+<div class="spacer"></div>
+<p> EOF </p>
+
+<script>
+ "use strict";
+
+ let url = url_base + "vertical-scroll-touch-action.html";
+ let iframeElement = document.querySelector("iframe");
+
+ // Wait for the helper scripts to load.
+ promise_test(async() => {
+ if (window.input_api_ready)
+ return Promise.resolve();
+ await new Promise( (r) => {
+ window.resolve_on_input_api_ready = r;
+ });
+ }, "Make sure input injection API is ready.");
+
+ // Sanity-check: Verify we can scroll using the test-API (empty <iframe>).
+ promise_test(async() => {
+ window.scrollTo(0, 0);
+
+ await inject_input("down");
+ assert_greater_than(window.scrollY, 0, "Page must have scrolled down.");
+
+ await inject_input("right");
+ assert_greater_than(window.scrollX, 0, "Page must have scrolled right.");
+ }, "Verify that the page normally scrolls.");
+
+ // Enable 'vertical-scroll': "touch-action" should be able to block scroll.
+ promise_test(async() => {
+ // Make sure <window> can scroll both towards right and bottom.
+ window.scrollTo(0, 0);
+
+ await loadUrlInIframe(iframeElement, url);
+
+ // Apply the scroll gesture.
+ await inject_input("down");
+
+ // The scroll should normally bubble and affect this page, but the <iframe>
+ // is allowed to block it.
+ assert_equals(window.scrollY,
+ 0,
+ "Main frame must not scroll vertically");
+ }, "Vertical scrolling through 'touch-action' can normally be blocked if" +
+ " 'pan-y' is not present.");
+
+ // Disable 'vertical-scroll': "touch-action" should not be able to block
+ // scroll.
+ promise_test(async() => {
+ window.scrollTo(0, 0);
+
+ // Disallow vertical scroll and reload the <iframe>.
+ setFeatureState(iframeElement, "vertical-scroll", "'none'");
+ await loadUrlInIframe(iframeElement, url);
+
+ // Apply the scroll gesture. Main frame should scroll vertically.
+ await inject_input("down");
+
+ assert_greater_than(window.scrollY,
+ 0,
+ "Main frame must scroll vertically.");
+ window.scrollTo(0, 0);
+
+ await inject_input("right");
+ assert_equals(
+ window.scrollX,
+ 0,
+ "'pan-x' can be blocked even when 'vertical-scroll' is disabled");
+ }, "Vertical scrolling (and only vertical scrolling) through 'touch-action'" +
+ " cannot be blocked by a disabled frame.");
+</script>
diff --git a/testing/web-platform/tests/permissions-policy/experimental-features/vertical-scroll-touch-block-manual.tentative.html b/testing/web-platform/tests/permissions-policy/experimental-features/vertical-scroll-touch-block-manual.tentative.html
new file mode 100644
index 0000000000..341e543934
--- /dev/null
+++ b/testing/web-platform/tests/permissions-policy/experimental-features/vertical-scroll-touch-block-manual.tentative.html
@@ -0,0 +1,237 @@
+<!doctype html>
+<title>vertical-scroll test for touch-action</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/permissions-policy/experimental-features/resources/common.js"></script>
+<script src="/permissions-policy/experimental-features/resources/vertical-scroll.js"></script>
+<style>
+html, body {
+ height: 100%;
+ width: 100%;
+}
+
+iframe {
+ width: 90%;
+ height: 90%;
+ margin: 0;
+ padding: 0;
+}
+
+.spacer {
+ width: 100%;
+ height: 100%;
+ margin: 100%;
+}
+</style>
+<iframe></iframe>
+<br/>
+<p>Spacers below to make page scrollable</p>
+<br/>
+<div class="spacer"></div>
+<div class="spacer"></div>
+<p> EOF </p>
+
+<script>
+ "use strict";
+
+ let url = url_base + "vertical-scroll-touch-block.html";
+ let iframeElement = document.querySelector("iframe");
+
+ // Wait for the helper scripts to load.
+ promise_test(async() => {
+ if (window.input_api_ready)
+ return Promise.resolve();
+ await new Promise( (r) => {
+ window.resolve_on_input_api_ready = r;
+ });
+
+ }, "Make sure input injection API is ready.");
+
+ // Sanity-check: Verify we can scroll using the test-API. The <iframe> with no
+ // query arguments would not preventDefault() any events.
+ promise_test(async() => {
+ window.scrollTo(0, 0);
+ await loadUrlInIframe(iframeElement, url);
+
+ await inject_scroll("down");
+ assert_greater_than(window.scrollY, 0, "Page must have scrolled down.");
+
+ await inject_scroll("right");
+ assert_greater_than(window.scrollX, 0, "Page must have scrolled right.");
+ }, "Verify that the page normally scrolls.");
+
+ // Enable 'vertical-scroll': Blocking "touchstart" can block scrolling.
+ promise_test(async() => {
+ window.scrollTo(0, 0);
+ setFeatureState(iframeElement, "vertical-scroll", "*");
+ await loadUrlInIframe(iframeElement, url);
+ await sendMessageAndGetResponse(iframeElement.contentWindow,
+ {
+ event_type: "touchstart",
+ query: "#blocker",
+ prevent: true
+ });
+
+ await inject_scroll("down");
+ assert_equals(window.scrollY,
+ 0,
+ "Main frame must not scroll vertically.");
+ }, "Calling 'preventDefault' on 'touchstart' blocks vertical scroll when " +
+ " the feature is enabled.");
+
+ // Disable 'vertical-scroll': Blocking "touchstart" cannot block vertical
+ // scroll.
+ promise_test(async() => {
+ window.scrollTo(0, 0);
+ setFeatureState(iframeElement, "vertical-scroll", "'none'");
+ await loadUrlInIframe(iframeElement, url);
+ await sendMessageAndGetResponse(iframeElement.contentWindow,
+ {
+ event_type: "touchstart",
+ query: "#blocker",
+ prevent: true
+ });
+
+ await inject_scroll("down");
+ assert_greater_than(window.scrollY,
+ 0,
+ "Main frame must scroll vertically.");
+
+ // Horizontal scrolling must still be blocked.
+ window.scrollTo(0, 0);
+ await inject_scroll("right");
+ assert_equals(window.scrollX,
+ 0,
+ "Main frame must not scroll horizontally.");
+ }, "When feature is disabled, calling 'preventDefault' on 'touchstart' does" +
+ " not block vertical scroll, but still bocks horizontal scroll.");
+
+ // Enable 'vertical-scroll': Blocking "touchmove" can block scrolling.
+ promise_test(async() => {
+ // Make sure <window> can scroll both towards right and bottom.
+ window.scrollTo(0, 0);
+ setFeatureState(iframeElement, "vertical-scroll", "*");
+ await loadUrlInIframe(iframeElement, url);
+ await sendMessageAndGetResponse(iframeElement.contentWindow,
+ {
+ event_type: "touchmove",
+ query: "#blocker",
+ prevent: true
+ });
+
+ await inject_scroll("down");
+ assert_equals(window.scrollY,
+ 0,
+ "Main frame must not scroll vertically");
+ }, "Calling 'preventDefault' on 'touchmove' blocks vertical scroll when " +
+ "the feature is enabled.");
+
+ // Disable 'vertical-scroll': Blocking "touchmove" cannot block vertical
+ // scroll.
+ promise_test(async() => {
+ window.scrollTo(0, 0);
+ setFeatureState(iframeElement, "vertical-scroll", "'none'");
+ await loadUrlInIframe(iframeElement, url);
+ await sendMessageAndGetResponse(iframeElement.contentWindow,
+ {
+ event_type: "touchmove",
+ query: "#blocker",
+ prevent: true
+ });
+ await new Promise((r) => {
+ window.setTimeout(r, 100);
+ });
+
+ await inject_scroll("down");
+ assert_greater_than(window.scrollY,
+ 0,
+ "Main frame must scroll vertically.");
+ }, "When feature is disabled, calling 'preventDefault' on 'touchmove' does" +
+ " not block vertical scroll.");
+
+ // Disable 'vertical-scroll': Add a non-preventing handler, and then a
+ // preventing handler and verify vertical scroll is possible and horizontal
+ // is blocked.
+ promise_test(async() => {
+ setFeatureState(iframeElement, "vertical-scroll", "'none'");
+ await loadUrlInIframe(iframeElement, url);
+
+ // Add a non-blocking handler.
+ await sendMessageAndGetResponse(iframeElement.contentWindow,
+ {
+ event_type: "touchstart",
+ query: "#blocker",
+ prevent: false
+ });
+
+ window.scrollTo(0, 0);
+ await inject_scroll("down");
+ assert_greater_than(window.scrollY, 0, "Page must have scrolled down.");
+
+ window.scrollTo(0, 0);
+ await inject_scroll("right");
+ assert_greater_than(window.scrollX, 0, "Page must have scrolled right.");
+
+ // Add a blocking handler.
+ await sendMessageAndGetResponse(iframeElement.contentWindow,
+ {
+ event_type: "touchstart",
+ query: "#blocker",
+ prevent: true
+ });
+ window.scrollTo(0, 0);
+ await inject_scroll("down");
+ assert_greater_than(window.scrollY,
+ 0,
+ "Page must have scrolled down (preventing handler).");
+
+ window.scrollTo(0, 0);
+ await inject_scroll("right");
+ assert_equals(window.scrollX,
+ 0,
+ "Page must not have scrolled horizontally.");
+ }, "Verify that starting with a non-preventing listener and then switching" +
+ " to a preventing one works as intended.");
+
+ // Disable 'vertical-scroll': A preventing 'touchstart' handler does not allow
+ // other multi-touch actions such as 'pinch-zoom'.
+ promise_test(async() => {
+ assert_true(!!window.visualViewport,
+ "'visualViewport' is needed for this test.");
+
+ setFeatureState(iframeElement, "vertical-scroll", "'none'");
+ await loadUrlInIframe(iframeElement, url);
+ await sendMessageAndGetResponse(iframeElement.contentWindow,
+ {
+ event_type: "touchstart",
+ query: "#blocker",
+ prevent: false
+ });
+
+ // Sanity check: Pinch zoom in should work since 'touchstart' is not
+ // preventing.
+ let current_scale_factor = window.visualViewport.scale;
+ await inject_zoom("in");
+ let new_scale_factor = window.visualViewport.scale;
+ assert_greater_than(
+ new_scale_factor,
+ current_scale_factor,
+ "Pinch zoom should work since 'touchstart' is not prevented.");
+
+ // Add a preventing handler and verify pinch zoom is now blocked.
+ await sendMessageAndGetResponse(iframeElement.contentWindow,
+ {
+ event_type: "touchstart",
+ query: "#blocker",
+ prevent: true
+ });
+ current_scale_factor = new_scale_factor;
+ await inject_zoom("out");
+ new_scale_factor = window.visualViewport.scale;
+ assert_equals(
+ new_scale_factor,
+ current_scale_factor,
+ "Pinch zoom must be blocked.");
+ }, "Verify that pinch zoom is blocked in frames with 'vertical-scroll' none'" +
+ " where 'touchstart' is prevent defaulted.");
+</script>
diff --git a/testing/web-platform/tests/permissions-policy/experimental-features/vertical-scroll-wheel-block-manual.tentative.html b/testing/web-platform/tests/permissions-policy/experimental-features/vertical-scroll-wheel-block-manual.tentative.html
new file mode 100644
index 0000000000..2627fd4154
--- /dev/null
+++ b/testing/web-platform/tests/permissions-policy/experimental-features/vertical-scroll-wheel-block-manual.tentative.html
@@ -0,0 +1,145 @@
+<!doctype html>
+<title>vertical-scroll test for 'mousewheel'</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/permissions-policy/experimental-features/resources/common.js"></script>
+<script src="/permissions-policy/experimental-features/resources/vertical-scroll.js"></script>
+<style>
+html, body {
+ height: 100%;
+ width: 100%;
+}
+
+iframe {
+ width: 90%;
+ height: 90%;
+ margin: 0;
+ padding: 0;
+}
+
+.spacer {
+ width: 100%;
+ height: 100%;
+ margin: 100%;
+}
+
+</style>
+<iframe></iframe>
+<br/>
+<p>Spacers below to make page scrollable</p>
+<br/>
+<div class="spacer"></div>
+<div class="spacer"></div>
+<p> EOF </p>
+<script>
+ "use strict";
+
+ let url = url_base + "vertical-scroll-wheel-block.html";
+ function iframeElement() {
+ return document.querySelector("iframe");
+ }
+
+ // Used as the polling interval when waiting for a specific condition.
+ let verify_scroll_offset_delay = 5;
+ let no_scroll_timout = 50;
+
+ function waitUntilSatisfied(testInstance, predicate) {
+ return new Promise((r) => {
+ function testPredicate() {
+ if (predicate()) {
+ r();
+ } else {
+ testInstance.step_timeout(testPredicate, verify_scroll_offset_delay);
+ }
+ }
+ testPredicate();
+ });
+ }
+
+ function resetScroll(testInstance) {
+ window.scrollTo({top: 0, left: 0, behavior: "instant"});
+ return waitUntilSatisfied(testInstance, () => {
+ return window.scrollX === 0 && window.scrollY === 0;
+ });
+ }
+
+ function waitForMinimumScrollOffset(testInstance, minX, minY) {
+ return waitUntilSatisfied(testInstance, () => {
+ return window.scrollX >= minX && window.scrollY >= minY;
+ });
+ }
+
+ function waitFor(testInstance, delay) {
+ let checked_once = false;
+ return waitUntilSatisfied(testInstance, () => {
+ if (checked_once)
+ return true;
+ checked_once = true;
+ return false;
+ });
+ }
+
+ // Wait for the helper scripts to load.
+ promise_test(async() => {
+ if (window.input_api_ready)
+ return Promise.resolve();
+ await new Promise((r) => {
+ window.resolve_on_input_api_ready = r;
+ });
+ }, "Make sure input injection API is ready.");
+
+ // Sanity-check: Test API for scrolling along y-axis works as expected.
+ promise_test(async(testInstance) => {
+ await resetScroll(testInstance);
+ await inject_wheel_scroll("down");
+ await waitForMinimumScrollOffset(testInstance, 0, 1);
+ assert_greater_than(window.scrollY, 0, "Expected vertical scroll.");
+ }, "Sanity-check: the page scrolls vertically in response to vertical wheel.");
+
+ // Sanity-check: Test API for scrolling along x-axis works as expected.
+ promise_test(async(testInstance) => {
+ await resetScroll(testInstance);
+ await inject_wheel_scroll("right");
+ await waitForMinimumScrollOffset(testInstance, 1, 0);
+ assert_greater_than(window.scrollX, 0, "Expected horizontal scroll.");
+ }, "Sanity-check: the page scrolls horizontally in response to horizontal wheel.");
+
+ // Test that when 'vertical-scroll' is enabled, vertical scroll can be
+ // blocked by canceling 'wheel' event.
+ promise_test(async(testInstance) => {
+ setFeatureState(iframeElement(), "vertical-scroll", "*");
+ await loadUrlInIframe(iframeElement(), url);
+
+ await resetScroll(testInstance);
+ await inject_wheel_scroll("down")
+ await waitFor(testInstance, no_scroll_timout);
+ assert_equals(window.scrollY, 0, "Did not expected vertical scroll.");
+ }, "When 'vertical-scroll' is enabled canceling vertical 'wheel' " +
+ "blocks vertical scrolling.");
+
+ // Test that when 'vertical-scroll' is disabled, vertical scroll cannot
+ // be blocked by canceling 'wheel' event.
+ promise_test(async(testInstance) => {
+ setFeatureState(iframeElement(), "vertical-scroll", "'none'");
+ await loadUrlInIframe(iframeElement(), url);
+
+ await resetScroll(testInstance);
+ await inject_wheel_scroll("down");
+ await waitForMinimumScrollOffset(testInstance, 0, 1);
+ assert_greater_than(window.scrollY, 0, "Expected vertical scroll.");
+ }, "When 'vertical-scroll' is disabled canceling vertical 'wheel' " +
+ "does not block vertical scrolling.");
+
+ // Test that when 'vertical-scroll' is disabled, horizontal scroll can be
+ // blocked by canceling 'wheel' event.
+ promise_test(async(testInstance) => {
+ setFeatureState(iframeElement(), "vertical-scroll", "'none'");
+ await loadUrlInIframe(iframeElement(), url);
+
+ await resetScroll(testInstance);
+ await inject_wheel_scroll("right");
+ await waitFor(testInstance, no_scroll_timout);
+ assert_equals(window.scrollX, 0, "Did not expect horizontal scroll.");
+ }, "When 'vertical-scroll' is disabled canceling horizontal 'wheel' " +
+ "blocks horizontal scrolling.");
+</script>