summaryrefslogtreecommitdiffstats
path: root/testing/web-platform/tests/feature-policy
diff options
context:
space:
mode:
Diffstat (limited to 'testing/web-platform/tests/feature-policy')
-rw-r--r--testing/web-platform/tests/feature-policy/META.yml4
-rw-r--r--testing/web-platform/tests/feature-policy/README.md59
-rw-r--r--testing/web-platform/tests/feature-policy/experimental-features/focus-without-user-activation-disabled-tentative.html51
-rw-r--r--testing/web-platform/tests/feature-policy/experimental-features/focus-without-user-activation-enabled-tentative.sub.html51
-rw-r--r--testing/web-platform/tests/feature-policy/experimental-features/resources/common.js94
-rw-r--r--testing/web-platform/tests/feature-policy/experimental-features/resources/feature-policy-trust-token-redemption.html55
-rw-r--r--testing/web-platform/tests/feature-policy/experimental-features/resources/focus-without-user-activation-iframe-tentative.html47
-rw-r--r--testing/web-platform/tests/feature-policy/experimental-features/resources/lazyload-contents.html13
-rw-r--r--testing/web-platform/tests/feature-policy/experimental-features/resources/lazyload.pngbin0 -> 20819 bytes
-rw-r--r--testing/web-platform/tests/feature-policy/experimental-features/resources/vertical-scroll-scrollable-content.html16
-rw-r--r--testing/web-platform/tests/feature-policy/experimental-features/resources/vertical-scroll-scrollbar-ref.html13
-rw-r--r--testing/web-platform/tests/feature-policy/experimental-features/resources/vertical-scroll-scrollintoview.html45
-rw-r--r--testing/web-platform/tests/feature-policy/experimental-features/resources/vertical-scroll-touch-action.html14
-rw-r--r--testing/web-platform/tests/feature-policy/experimental-features/resources/vertical-scroll-touch-block.html42
-rw-r--r--testing/web-platform/tests/feature-policy/experimental-features/resources/vertical-scroll-wheel-block.html22
-rw-r--r--testing/web-platform/tests/feature-policy/experimental-features/resources/vertical-scroll.js25
-rw-r--r--testing/web-platform/tests/feature-policy/experimental-features/trust-token-redemption-default-feature-policy.tentative.https.sub.html62
-rw-r--r--testing/web-platform/tests/feature-policy/experimental-features/trust-token-redemption-supported-by-feature-policy.tentative.html9
-rw-r--r--testing/web-platform/tests/feature-policy/experimental-features/vertical-scroll-disabled-frame-no-scroll-manual.tentative.html113
-rw-r--r--testing/web-platform/tests/feature-policy/experimental-features/vertical-scroll-disabled-scrollbar-tentative.html4
-rw-r--r--testing/web-platform/tests/feature-policy/experimental-features/vertical-scroll-main-frame-manual.tentative.html46
-rw-r--r--testing/web-platform/tests/feature-policy/experimental-features/vertical-scroll-main-frame-manual.tentative.html.headers1
-rw-r--r--testing/web-platform/tests/feature-policy/experimental-features/vertical-scroll-scrollintoview.tentative.html117
-rw-r--r--testing/web-platform/tests/feature-policy/experimental-features/vertical-scroll-touch-action-manual.tentative.html103
-rw-r--r--testing/web-platform/tests/feature-policy/experimental-features/vertical-scroll-touch-block-manual.tentative.html237
-rw-r--r--testing/web-platform/tests/feature-policy/experimental-features/vertical-scroll-wheel-block-manual.tentative.html145
-rw-r--r--testing/web-platform/tests/feature-policy/feature-policy-frame-policy-allowed-for-all.https.sub.html184
-rw-r--r--testing/web-platform/tests/feature-policy/feature-policy-frame-policy-allowed-for-all.https.sub.html.sub.headers1
-rw-r--r--testing/web-platform/tests/feature-policy/feature-policy-frame-policy-allowed-for-self.https.sub.html186
-rw-r--r--testing/web-platform/tests/feature-policy/feature-policy-frame-policy-allowed-for-self.https.sub.html.sub.headers1
-rw-r--r--testing/web-platform/tests/feature-policy/feature-policy-frame-policy-allowed-for-some-override.https.sub.html83
-rw-r--r--testing/web-platform/tests/feature-policy/feature-policy-frame-policy-allowed-for-some-override.https.sub.html.sub.headers1
-rw-r--r--testing/web-platform/tests/feature-policy/feature-policy-frame-policy-allowed-for-some.https.sub.html136
-rw-r--r--testing/web-platform/tests/feature-policy/feature-policy-frame-policy-allowed-for-some.https.sub.html.sub.headers1
-rw-r--r--testing/web-platform/tests/feature-policy/feature-policy-frame-policy-disallowed-for-all.https.sub.html150
-rw-r--r--testing/web-platform/tests/feature-policy/feature-policy-frame-policy-disallowed-for-all.https.sub.html.sub.headers1
-rw-r--r--testing/web-platform/tests/feature-policy/feature-policy-frame-policy-timing-iframe-camera.https.sub.html15
-rw-r--r--testing/web-platform/tests/feature-policy/feature-policy-frame-policy-timing.https.sub.html69
-rw-r--r--testing/web-platform/tests/feature-policy/feature-policy-header-policy-allowed-for-all.https.sub.html61
-rw-r--r--testing/web-platform/tests/feature-policy/feature-policy-header-policy-allowed-for-all.https.sub.html.sub.headers1
-rw-r--r--testing/web-platform/tests/feature-policy/feature-policy-header-policy-allowed-for-self.https.sub.html45
-rw-r--r--testing/web-platform/tests/feature-policy/feature-policy-header-policy-allowed-for-self.https.sub.html.sub.headers1
-rw-r--r--testing/web-platform/tests/feature-policy/feature-policy-header-policy-allowed-for-some.https.sub.html76
-rw-r--r--testing/web-platform/tests/feature-policy/feature-policy-header-policy-allowed-for-some.https.sub.html.sub.headers1
-rw-r--r--testing/web-platform/tests/feature-policy/feature-policy-header-policy-declined.https.sub.html75
-rw-r--r--testing/web-platform/tests/feature-policy/feature-policy-header-policy-declined.https.sub.html.sub.headers1
-rw-r--r--testing/web-platform/tests/feature-policy/feature-policy-header-policy-disallowed-for-all.https.sub.html46
-rw-r--r--testing/web-platform/tests/feature-policy/feature-policy-header-policy-disallowed-for-all.https.sub.html.sub.headers2
-rw-r--r--testing/web-platform/tests/feature-policy/feature-policy-nested-header-policy-allowed-for-all.https.sub.html66
-rw-r--r--testing/web-platform/tests/feature-policy/feature-policy-nested-header-policy-allowed-for-all.https.sub.html.sub.headers1
-rw-r--r--testing/web-platform/tests/feature-policy/feature-policy-nested-header-policy-allowed-for-self.https.sub.html63
-rw-r--r--testing/web-platform/tests/feature-policy/feature-policy-nested-header-policy-allowed-for-self.https.sub.html.sub.headers1
-rw-r--r--testing/web-platform/tests/feature-policy/feature-policy-nested-header-policy-disallowed-for-all.https.sub.html51
-rw-r--r--testing/web-platform/tests/feature-policy/feature-policy-nested-header-policy-disallowed-for-all.https.sub.html.sub.headers1
-rw-r--r--testing/web-platform/tests/feature-policy/payment-allowed-by-feature-policy-attribute-redirect-on-load.https.sub.html28
-rw-r--r--testing/web-platform/tests/feature-policy/payment-allowed-by-feature-policy-attribute.https.sub.html26
-rw-r--r--testing/web-platform/tests/feature-policy/payment-allowed-by-feature-policy.https.sub.html41
-rw-r--r--testing/web-platform/tests/feature-policy/payment-allowed-by-feature-policy.https.sub.html.headers1
-rw-r--r--testing/web-platform/tests/feature-policy/payment-default-feature-policy.https.sub.html36
-rw-r--r--testing/web-platform/tests/feature-policy/payment-disabled-by-feature-policy.https.sub.html34
-rw-r--r--testing/web-platform/tests/feature-policy/payment-disabled-by-feature-policy.https.sub.html.headers1
-rw-r--r--testing/web-platform/tests/feature-policy/payment-supported-by-feature-policy.tentative.html11
-rw-r--r--testing/web-platform/tests/feature-policy/permissions-policy-feature-policy-coexist.https.html25
-rw-r--r--testing/web-platform/tests/feature-policy/permissions-policy-feature-policy-coexist.https.html.headers2
-rw-r--r--testing/web-platform/tests/feature-policy/permissions-policy-header-policy-allowed-for-self.https.sub.html45
-rw-r--r--testing/web-platform/tests/feature-policy/permissions-policy-header-policy-allowed-for-self.https.sub.html.sub.headers1
-rw-r--r--testing/web-platform/tests/feature-policy/picture-in-picture-allowed-by-feature-policy-attribute-redirect-on-load.https.sub.html31
-rw-r--r--testing/web-platform/tests/feature-policy/picture-in-picture-allowed-by-feature-policy-attribute.https.sub.html30
-rw-r--r--testing/web-platform/tests/feature-policy/picture-in-picture-allowed-by-feature-policy.https.sub.html33
-rw-r--r--testing/web-platform/tests/feature-policy/picture-in-picture-allowed-by-feature-policy.https.sub.html.headers1
-rw-r--r--testing/web-platform/tests/feature-policy/picture-in-picture-default-feature-policy.https.sub.html34
-rw-r--r--testing/web-platform/tests/feature-policy/picture-in-picture-disabled-by-feature-policy.https.sub.html33
-rw-r--r--testing/web-platform/tests/feature-policy/picture-in-picture-disabled-by-feature-policy.https.sub.html.headers1
-rw-r--r--testing/web-platform/tests/feature-policy/picture-in-picture-supported-by-feature-policy.html11
-rw-r--r--testing/web-platform/tests/feature-policy/policy-extends-to-sandbox.html26
-rw-r--r--testing/web-platform/tests/feature-policy/reporting/camera-reporting.https.html34
-rw-r--r--testing/web-platform/tests/feature-policy/reporting/camera-reporting.https.html.headers1
-rw-r--r--testing/web-platform/tests/feature-policy/reporting/encrypted-media-reporting.https.html37
-rw-r--r--testing/web-platform/tests/feature-policy/reporting/encrypted-media-reporting.https.html.headers1
-rw-r--r--testing/web-platform/tests/feature-policy/reporting/fullscreen-reporting.html45
-rw-r--r--testing/web-platform/tests/feature-policy/reporting/fullscreen-reporting.html.headers1
-rw-r--r--testing/web-platform/tests/feature-policy/reporting/generic-sensor-reporting.https.html55
-rw-r--r--testing/web-platform/tests/feature-policy/reporting/generic-sensor-reporting.https.html.headers1
-rw-r--r--testing/web-platform/tests/feature-policy/reporting/geolocation-reporting.https.html30
-rw-r--r--testing/web-platform/tests/feature-policy/reporting/geolocation-reporting.https.html.headers1
-rw-r--r--testing/web-platform/tests/feature-policy/reporting/image.bmpbin0 -> 6538 bytes
-rw-r--r--testing/web-platform/tests/feature-policy/reporting/microphone-reporting.https.html34
-rw-r--r--testing/web-platform/tests/feature-policy/reporting/microphone-reporting.https.html.headers1
-rw-r--r--testing/web-platform/tests/feature-policy/reporting/midi-reporting.https.html32
-rw-r--r--testing/web-platform/tests/feature-policy/reporting/midi-reporting.https.html.headers1
-rw-r--r--testing/web-platform/tests/feature-policy/reporting/payment-reporting.https.html37
-rw-r--r--testing/web-platform/tests/feature-policy/reporting/payment-reporting.https.html.headers1
-rw-r--r--testing/web-platform/tests/feature-policy/reporting/picture-in-picture-reporting.html46
-rw-r--r--testing/web-platform/tests/feature-policy/reporting/picture-in-picture-reporting.html.headers1
-rw-r--r--testing/web-platform/tests/feature-policy/reporting/serial-reporting.https.html54
-rw-r--r--testing/web-platform/tests/feature-policy/reporting/serial-reporting.https.html.headers1
-rw-r--r--testing/web-platform/tests/feature-policy/reporting/sync-xhr-reporting.html36
-rw-r--r--testing/web-platform/tests/feature-policy/reporting/sync-xhr-reporting.html.headers1
-rw-r--r--testing/web-platform/tests/feature-policy/reporting/usb-reporting.https.html30
-rw-r--r--testing/web-platform/tests/feature-policy/reporting/usb-reporting.https.html.headers1
-rw-r--r--testing/web-platform/tests/feature-policy/reporting/vr-report-only.https.html30
-rw-r--r--testing/web-platform/tests/feature-policy/reporting/vr-report-only.https.html.headers1
-rw-r--r--testing/web-platform/tests/feature-policy/reporting/vr-reporting.https.html32
-rw-r--r--testing/web-platform/tests/feature-policy/reporting/vr-reporting.https.html.headers1
-rw-r--r--testing/web-platform/tests/feature-policy/reporting/xr-reporting.https.html33
-rw-r--r--testing/web-platform/tests/feature-policy/reporting/xr-reporting.https.html.headers1
-rw-r--r--testing/web-platform/tests/feature-policy/resources/autoplay.js28
-rw-r--r--testing/web-platform/tests/feature-policy/resources/feature-policy-allowedfeatures.html7
-rw-r--r--testing/web-platform/tests/feature-policy/resources/feature-policy-autoplay.html11
-rw-r--r--testing/web-platform/tests/feature-policy/resources/feature-policy-clipboard-read.html20
-rw-r--r--testing/web-platform/tests/feature-policy/resources/feature-policy-clipboard-write.html20
-rw-r--r--testing/web-platform/tests/feature-policy/resources/feature-policy-generic-sensor.html11
-rw-r--r--testing/web-platform/tests/feature-policy/resources/feature-policy-nested-subframe-policy.https.sub.html57
-rw-r--r--testing/web-platform/tests/feature-policy/resources/feature-policy-payment.html16
-rw-r--r--testing/web-platform/tests/feature-policy/resources/feature-policy-picture-in-picture.html11
-rw-r--r--testing/web-platform/tests/feature-policy/resources/feature-policy-report-json.js20
-rw-r--r--testing/web-platform/tests/feature-policy/resources/feature-policy-webvr.html9
-rw-r--r--testing/web-platform/tests/feature-policy/resources/featurepolicy.js454
-rw-r--r--testing/web-platform/tests/feature-policy/resources/nested-sandbox.html8
-rw-r--r--testing/web-platform/tests/feature-policy/resources/picture-in-picture.js30
-rw-r--r--testing/web-platform/tests/feature-policy/resources/redirect-on-load.html11
-rw-r--r--testing/web-platform/tests/feature-policy/resources/sandbox-self.html23
-rw-r--r--testing/web-platform/tests/feature-policy/resources/sandbox-self.html.headers1
123 files changed, 4476 insertions, 0 deletions
diff --git a/testing/web-platform/tests/feature-policy/META.yml b/testing/web-platform/tests/feature-policy/META.yml
new file mode 100644
index 0000000000..5fb6249c67
--- /dev/null
+++ b/testing/web-platform/tests/feature-policy/META.yml
@@ -0,0 +1,4 @@
+spec: https://wicg.github.io/feature-policy/
+suggested_reviewers:
+ - bakulf
+ - clelland
diff --git a/testing/web-platform/tests/feature-policy/README.md b/testing/web-platform/tests/feature-policy/README.md
new file mode 100644
index 0000000000..646fc1e0be
--- /dev/null
+++ b/testing/web-platform/tests/feature-policy/README.md
@@ -0,0 +1,59 @@
+# Feature Policy Guide
+## How to Test a New Feature with Feature Policy
+
+This directory contains a framework to test features with feature policy.
+
+When adding a new feature to feature policy, the following cases should be tested:
+* feature enabled by header policy [HTTP tests]
+ + test when feature is enabled by feature policy HTTP header;
+* feature disabled by header policy [HTTP tests]
+ + test when feature is disabled by feature policy HTTP header;
+* feature enabled on self origin by header policy [HTTP tests]
+ + test when feature is enabled only on self origin by feature policy HTTP
+ header.
+* feature allowed by container policy (iframe "allow" attribute);
+ + test when feature is enabled by iframe "allow" attribute on self and cross
+ origins.
+* feature allowed by container policy, redirect on load.
+ + test when feature is enabled by iframe "allow" attribute when the iframe
+ is being redirected to a new origin upon loading
+
+### How to Use the Test Framework
+Use `test_feature_availability()` defined in
+`/feature-policy/resources/featurepolicy.js`. Please refer to the comments
+in `/feature-policy/resources/featurepolicy.js` for how this function works.
+
+### How to Write Header Policy Tests
+HTTP tests are used to test features with header policy.
+
+* Define the header policy in `<feature-name>-<enabled | disabled | enabled-on-self-origin>-by-feature-policy.https.sub.html.headers`. Example:
+
+ Feature-Policy: feature-name *
+
+
+* In `<feature-name>-<enabled | disabled | enabled-on-self-origin>-by-feature-policy.https.sub.html`:
+* test if feature is enabled / disabled in the main frame;
+* test if feature is enabled / disabled in a same-origin iframe;
+* test if feature is enabled / disabled in a cross-origin iframe.
+
+Examples:
+`/feature-policy/payment-disabled-by-feature-policy.https.sub.html`
+`/feature-policy/payment-disabled-by-feature-policy.https.sub.html.headers`
+
+### How to Write Container Policy Tests
+Simply use `test_feature_availability()` with the optional argument
+`feature_name` specified to test if:
+* feature is enabled / disabled in a same-origin iframe;
+* feature is enabled / disabled in a cross-origin iframe.
+
+Example:
+`/feature-policy/payment-allowed-by-feature-policy-attribute.https.sub.html`
+
+### How to Write Container Policy Tests with Redirect
+Similar to the section above, append
+`/feature-policy/resources/redirect-on-load.html#` to the argument `src`
+passed to `test_feature_availability()`.
+
+Example:
+`/feature-policy/payment-allowed-by-feature-policy-attribute-redirect-on-load.https.sub.html`
+
diff --git a/testing/web-platform/tests/feature-policy/experimental-features/focus-without-user-activation-disabled-tentative.html b/testing/web-platform/tests/feature-policy/experimental-features/focus-without-user-activation-disabled-tentative.html
new file mode 100644
index 0000000000..c4413f7804
--- /dev/null
+++ b/testing/web-platform/tests/feature-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 = "/feature-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/feature-policy/experimental-features/focus-without-user-activation-enabled-tentative.sub.html b/testing/web-platform/tests/feature-policy/experimental-features/focus-without-user-activation-enabled-tentative.sub.html
new file mode 100644
index 0000000000..411e4f2fa0
--- /dev/null
+++ b/testing/web-platform/tests/feature-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]}}/feature-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/feature-policy/experimental-features/resources/common.js b/testing/web-platform/tests/feature-policy/experimental-features/resources/common.js
new file mode 100644
index 0000000000..4266e39495
--- /dev/null
+++ b/testing/web-platform/tests/feature-policy/experimental-features/resources/common.js
@@ -0,0 +1,94 @@
+const url_base = "/feature-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/feature-policy/experimental-features/resources/feature-policy-trust-token-redemption.html b/testing/web-platform/tests/feature-policy/experimental-features/resources/feature-policy-trust-token-redemption.html
new file mode 100644
index 0000000000..fa5f38b62f
--- /dev/null
+++ b/testing/web-platform/tests/feature-policy/experimental-features/resources/feature-policy-trust-token-redemption.html
@@ -0,0 +1,55 @@
+<script>
+ 'use strict';
+
+ window.onload = function() {
+ // When the trust-token-redemption feature 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/", {
+ trustToken: {
+ type: "token-redemption"
+ }
+ });
+ } catch (e) {
+ num_enabled--;
+ }
+ try {
+ new Request("https://destination.example/", {
+ trustToken: {
+ type: "send-redemption-record",
+ issuers: ["https://issuer.example/"]
+ }
+ });
+ } catch (e) {
+ num_enabled--;
+ }
+
+ try {
+ const xhr = new XMLHttpRequest();
+ xhr.open("GET", "https://issuer.example/");
+ xhr.setTrustToken({
+ type: "token-redemption"
+ });
+ } catch (e) {
+ num_enabled--;
+ }
+
+ try {
+ const xhr = new XMLHttpRequest();
+ xhr.open("GET", "https://destination.example/");
+ xhr.setTrustToken({
+ type: "send-redemption-record",
+ issuers: ["https://issuer.example/"]
+ });
+ } catch (e) {
+ num_enabled--;
+ }
+
+ parent.postMessage({
+ num_operations_enabled: num_enabled
+ }, '*');
+ }
+</script>
diff --git a/testing/web-platform/tests/feature-policy/experimental-features/resources/focus-without-user-activation-iframe-tentative.html b/testing/web-platform/tests/feature-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/feature-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/feature-policy/experimental-features/resources/lazyload-contents.html b/testing/web-platform/tests/feature-policy/experimental-features/resources/lazyload-contents.html
new file mode 100644
index 0000000000..a6e98c24e6
--- /dev/null
+++ b/testing/web-platform/tests/feature-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/feature-policy/experimental-features/resources/lazyload.png b/testing/web-platform/tests/feature-policy/experimental-features/resources/lazyload.png
new file mode 100644
index 0000000000..fd3da53a29
--- /dev/null
+++ b/testing/web-platform/tests/feature-policy/experimental-features/resources/lazyload.png
Binary files differ
diff --git a/testing/web-platform/tests/feature-policy/experimental-features/resources/vertical-scroll-scrollable-content.html b/testing/web-platform/tests/feature-policy/experimental-features/resources/vertical-scroll-scrollable-content.html
new file mode 100644
index 0000000000..9f78ea4bc2
--- /dev/null
+++ b/testing/web-platform/tests/feature-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/feature-policy/experimental-features/resources/vertical-scroll-scrollbar-ref.html b/testing/web-platform/tests/feature-policy/experimental-features/resources/vertical-scroll-scrollbar-ref.html
new file mode 100644
index 0000000000..66d0a55104
--- /dev/null
+++ b/testing/web-platform/tests/feature-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="/feature-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/feature-policy/experimental-features/resources/vertical-scroll-scrollintoview.html b/testing/web-platform/tests/feature-policy/experimental-features/resources/vertical-scroll-scrollintoview.html
new file mode 100644
index 0000000000..7bed27c260
--- /dev/null
+++ b/testing/web-platform/tests/feature-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/feature-policy/experimental-features/resources/vertical-scroll-touch-action.html b/testing/web-platform/tests/feature-policy/experimental-features/resources/vertical-scroll-touch-action.html
new file mode 100644
index 0000000000..51b715f30a
--- /dev/null
+++ b/testing/web-platform/tests/feature-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/feature-policy/experimental-features/resources/vertical-scroll-touch-block.html b/testing/web-platform/tests/feature-policy/experimental-features/resources/vertical-scroll-touch-block.html
new file mode 100644
index 0000000000..4c204055af
--- /dev/null
+++ b/testing/web-platform/tests/feature-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/feature-policy/experimental-features/resources/vertical-scroll-wheel-block.html b/testing/web-platform/tests/feature-policy/experimental-features/resources/vertical-scroll-wheel-block.html
new file mode 100644
index 0000000000..21fc2b9b39
--- /dev/null
+++ b/testing/web-platform/tests/feature-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/feature-policy/experimental-features/resources/vertical-scroll.js b/testing/web-platform/tests/feature-policy/experimental-features/resources/vertical-scroll.js
new file mode 100644
index 0000000000..88835cc602
--- /dev/null
+++ b/testing/web-platform/tests/feature-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/feature-policy/experimental-features/trust-token-redemption-default-feature-policy.tentative.https.sub.html b/testing/web-platform/tests/feature-policy/experimental-features/trust-token-redemption-default-feature-policy.tentative.https.sub.html
new file mode 100644
index 0000000000..134ccc084d
--- /dev/null
+++ b/testing/web-platform/tests/feature-policy/experimental-features/trust-token-redemption-default-feature-policy.tentative.https.sub.html
@@ -0,0 +1,62 @@
+<!DOCTYPE html>
+<title>Test that trust token redemption is enabled/disabled according to the feature policy</title>
+
+<body>
+ <script src=/resources/testharness.js></script>
+ <script src=/resources/testharnessreport.js></script>
+ <script src=/feature-policy/resources/featurepolicy.js></script>
+ <script>
+ 'use strict';
+ const same_origin_src = '/feature-policy/experimental-features/resources/feature-policy-trust-token-redemption.html';
+ const cross_origin_src = 'https://{{domains[www]}}:{{ports[https][0]}}' +
+ same_origin_src;
+ const header = 'Default "trust-token-redemption" feature policy ["self"]';
+
+ test(() => {
+ try {
+ // The feature policy gates redemption and signing via both the Fetch
+ // and XHR interfaces.
+ new Request("https://issuer.example/", {
+ trustToken: {
+ type: "token-redemption"
+ }
+ });
+ new Request("https://destination.example/", {
+ trustToken: {
+ type: "send-redemption-record", // signing
+ issuers: ["https://issuer.example/"]
+ }
+ });
+
+ const redemption_xhr = new XMLHttpRequest();
+ redemption_xhr.open("GET", "https://issuer.example/");
+ redemption_xhr.setTrustToken({
+ type: "token-redemption"
+ });
+
+ const signing_xhr = new XMLHttpRequest();
+ signing_xhr.open("GET", "https://destination.example/");
+ signing_xhr.setTrustToken({
+ type: "send-redemption-record", // signing
+ issuers: ["https://issuer.example/"]
+ });
+ } catch (e) {
+ assert_unreached();
+ }
+ }, header + ' allows the top-level document.');
+
+ async_test(t => {
+ test_feature_availability('Trust 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('Trust 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/feature-policy/experimental-features/trust-token-redemption-supported-by-feature-policy.tentative.html b/testing/web-platform/tests/feature-policy/experimental-features/trust-token-redemption-supported-by-feature-policy.tentative.html
new file mode 100644
index 0000000000..e349eadc5d
--- /dev/null
+++ b/testing/web-platform/tests/feature-policy/experimental-features/trust-token-redemption-supported-by-feature-policy.tentative.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<title>Test that trust 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('trust-token-redemption', document.featurePolicy.features());
+ }, 'document.featurePolicy.features should advertise trust token redemption.');
+</script>
diff --git a/testing/web-platform/tests/feature-policy/experimental-features/vertical-scroll-disabled-frame-no-scroll-manual.tentative.html b/testing/web-platform/tests/feature-policy/experimental-features/vertical-scroll-disabled-frame-no-scroll-manual.tentative.html
new file mode 100644
index 0000000000..6f827c919a
--- /dev/null
+++ b/testing/web-platform/tests/feature-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="/feature-policy/experimental-features/resources/common.js"></script>
+<script src="/feature-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/feature-policy/experimental-features/vertical-scroll-disabled-scrollbar-tentative.html b/testing/web-platform/tests/feature-policy/experimental-features/vertical-scroll-disabled-scrollbar-tentative.html
new file mode 100644
index 0000000000..6036234de1
--- /dev/null
+++ b/testing/web-platform/tests/feature-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="/feature-policy/experimental-features/resources/vertical-scroll-scrollbar-ref.html?no-vertical-scrollbar">
+<iframe src="/feature-policy/experimental-features/resources/vertical-scroll-scrollable-content.html" allow="vertical-scroll 'none'"></iframe>
diff --git a/testing/web-platform/tests/feature-policy/experimental-features/vertical-scroll-main-frame-manual.tentative.html b/testing/web-platform/tests/feature-policy/experimental-features/vertical-scroll-main-frame-manual.tentative.html
new file mode 100644
index 0000000000..34cf142d45
--- /dev/null
+++ b/testing/web-platform/tests/feature-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="/feature-policy/experimental-features/resources/common.js"></script>
+<script src="/feature-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/feature-policy/experimental-features/vertical-scroll-main-frame-manual.tentative.html.headers b/testing/web-platform/tests/feature-policy/experimental-features/vertical-scroll-main-frame-manual.tentative.html.headers
new file mode 100644
index 0000000000..44072fce56
--- /dev/null
+++ b/testing/web-platform/tests/feature-policy/experimental-features/vertical-scroll-main-frame-manual.tentative.html.headers
@@ -0,0 +1 @@
+Feature-Policy: vertical-scroll 'none'
diff --git a/testing/web-platform/tests/feature-policy/experimental-features/vertical-scroll-scrollintoview.tentative.html b/testing/web-platform/tests/feature-policy/experimental-features/vertical-scroll-scrollintoview.tentative.html
new file mode 100644
index 0000000000..689685a497
--- /dev/null
+++ b/testing/web-platform/tests/feature-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="/feature-policy/experimental-features/resources/common.js"></script>
+<script src="/feature-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/feature-policy/experimental-features/vertical-scroll-touch-action-manual.tentative.html b/testing/web-platform/tests/feature-policy/experimental-features/vertical-scroll-touch-action-manual.tentative.html
new file mode 100644
index 0000000000..c1b5f1d076
--- /dev/null
+++ b/testing/web-platform/tests/feature-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="/feature-policy/experimental-features/resources/common.js"></script>
+<script src="/feature-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/feature-policy/experimental-features/vertical-scroll-touch-block-manual.tentative.html b/testing/web-platform/tests/feature-policy/experimental-features/vertical-scroll-touch-block-manual.tentative.html
new file mode 100644
index 0000000000..82f6ca527f
--- /dev/null
+++ b/testing/web-platform/tests/feature-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="/feature-policy/experimental-features/resources/common.js"></script>
+<script src="/feature-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/feature-policy/experimental-features/vertical-scroll-wheel-block-manual.tentative.html b/testing/web-platform/tests/feature-policy/experimental-features/vertical-scroll-wheel-block-manual.tentative.html
new file mode 100644
index 0000000000..398aa1f5af
--- /dev/null
+++ b/testing/web-platform/tests/feature-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="/feature-policy/experimental-features/resources/common.js"></script>
+<script src="/feature-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>
diff --git a/testing/web-platform/tests/feature-policy/feature-policy-frame-policy-allowed-for-all.https.sub.html b/testing/web-platform/tests/feature-policy/feature-policy-frame-policy-allowed-for-all.https.sub.html
new file mode 100644
index 0000000000..63cd9853dc
--- /dev/null
+++ b/testing/web-platform/tests/feature-policy/feature-policy-frame-policy-allowed-for-all.https.sub.html
@@ -0,0 +1,184 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta name="timeout" content="long">
+ <script src=/resources/testharness.js></script>
+ <script src=/resources/testharnessreport.js></script>
+</head>
+<body>
+ <script src=/feature-policy/resources/featurepolicy.js></script>
+ <!-- Feature-Policy: fullscreen *; -->
+ <script>
+ 'use strict';
+ var same_origin = 'https://{{domains[]}}:{{ports[https][0]}}';
+ var cross_origin = 'https://{{domains[www]}}:{{ports[https][0]}}';
+ var same_origin_src = '/feature-policy/resources/feature-policy-allowedfeatures.html';
+ var cross_origin_src = cross_origin + same_origin_src;
+ var data_src = 'data:text/html,<h1>data: URL</h1>';
+ var policies = [
+ {allow: "*", sameOriginTestExpect: true, crossOriginTestExpect: true, dataOriginTestExpect: true},
+ {allow: "'self'", sameOriginTestExpect: true, crossOriginTestExpect: false, dataOriginTestExpect: false},
+ {allow: "'none'", sameOriginTestExpect: false, crossOriginTestExpect: false, dataOriginTestExpect: false},
+ {allow: "'self' " + cross_origin + " https://www.example.com", sameOriginTestExpect: true, crossOriginTestExpect: true, dataOriginTestExpect: false}];
+ var pipe_front = '?pipe=sub|header(Feature-Policy,fullscreen ';
+ var pipe_end = ';)';
+ var header_policies = ["*", "'self'", "'none'"];
+
+ // Test that frame.policy inherits from parent's header policy when allow
+ // attribute is not specified.
+ test(function() {
+ test_frame_policy('fullscreen', same_origin_src, undefined, true);
+ }, 'Test frame policy on same origin iframe inherit from header policy.');
+ test(function() {
+ test_frame_policy('fullscreen', cross_origin_src, undefined, false);
+ }, 'Test frame policy on cross origin iframe inherit from header policy.');
+ test(function() {
+ test_frame_policy('fullscreen', undefined, true, true);
+ }, 'Test frame policy on srcdoc iframe inherit from header policy.');
+ test(function() {
+ test_frame_policy('fullscreen', same_origin_src, true, true);
+ }, 'Test frame policy on srcdoc+ same origin iframe inherit from header policy.');
+ test(function() {
+ test_frame_policy('fullscreen', cross_origin_src, true, true);
+ }, 'Test frame policy on srcdoc+ cross origin iframe inherit from header policy.');
+ test(function() {
+ test_frame_policy('fullscreen', data_src, undefined, false);
+ }, 'Test frame policy on data: URL cross origin iframe inherit from header policy.');
+
+ // Test that frame policy can be used for sandboxed frames
+ test(function() {
+ test_frame_policy(
+ 'fullscreen', same_origin_src, undefined, false, undefined, false, true);
+ }, 'Test frame policy on sandboxed iframe with no allow attribute.');
+ test(function() {
+ test_frame_policy(
+ 'fullscreen', same_origin_src, undefined, true, 'fullscreen', false, true);
+ }, 'Test frame policy on sandboxed iframe with allow="fullscreen".');
+ test(function() {
+ test_frame_policy(
+ 'fullscreen', same_origin_src, undefined, true, 'fullscreen \'src\'', false, true);
+ }, 'Test frame policy on sandboxed iframe with allow="fullscreen \'src\'".');
+ test(function() {
+ test_frame_policy(
+ 'fullscreen', cross_origin_src, undefined, false, 'fullscreen ' + cross_origin, false, true);
+ }, 'Test frame policy on sandboxed iframe with allow="fullscreen ' + cross_origin + '".');
+ test(function() {
+ test_frame_policy(
+ 'fullscreen', undefined, true, true, 'fullscreen', false, true);
+ }, 'Test frame policy on srcdoc sandboxed iframe with allow="fullscreen".');
+ test(function() {
+ test_frame_policy(
+ 'fullscreen', same_origin_src, true, true, 'fullscreen', false, true);
+ }, 'Test frame policy on srcdoc + same origin sandboxed iframe with allow="fullscreen".');
+ test(function() {
+ test_frame_policy(
+ 'fullscreen', cross_origin_src, true, true, 'fullscreen', false, true);
+ }, 'Test frame policy on srcdoc + cross origin sandboxed iframe with allow="fullscreen".');
+ test(function() {
+ test_frame_policy(
+ 'fullscreen', data_src, undefined, false, 'fullscreen ' + cross_origin, false, true);
+ }, 'Test frame policy on sandboxed srcdoc iframe with allow="fullscreen ' + cross_origin + '".');
+
+ // Test frame policy with allow attribute set to be one of the policies above.
+ for (var i = 0; i < policies.length; i++) {
+ test(function() {
+ test_frame_policy(
+ 'fullscreen', same_origin_src, undefined,
+ policies[i].sameOriginTestExpect,
+ 'fullscreen ' + policies[i].allow + ';');
+ }, 'Test frame policy on same origin iframe with allow = "' + policies[i].allow + '".');
+ test(function() {
+ test_frame_policy(
+ 'fullscreen', cross_origin_src, undefined,
+ policies[i].crossOriginTestExpect,
+ 'fullscreen ' + policies[i].allow + ';');
+ }, 'Test frame policy on cross origin iframe with allow = "' + policies[i].allow + '".');
+ test(function() {
+ test_frame_policy(
+ 'fullscreen', undefined, true, policies[i].sameOriginTestExpect,
+ 'fullscreen ' + policies[i].allow + ';');
+ }, 'Test frame policy on srcdoc iframe with allow = "' + policies[i].allow + '".');
+ test(function() {
+ test_frame_policy(
+ 'fullscreen', same_origin_src, true, policies[i].sameOriginTestExpect,
+ 'fullscreen ' + policies[i].allow + ';');
+ }, 'Test frame policy on srcdoc + same origin iframe with allow = "' + policies[i].allow + '".');
+ test(function() {
+ test_frame_policy(
+ 'fullscreen', cross_origin_src, true, policies[i].sameOriginTestExpect,
+ 'fullscreen ' + policies[i].allow + ';');
+ }, 'Test frame policy on srcdoc + cross origin iframe with allow = "' + policies[i].allow + '".');
+ test(function() {
+ test_frame_policy(
+ 'fullscreen', data_src, undefined, policies[i].dataOriginTestExpect,
+ 'fullscreen ' + policies[i].allow + ';');
+ }, 'Test frame policy on data: URL cross origin iframe with allow = "' + policies[i].allow + '".');
+ }
+
+ // Test that the header policy of the iframe document does not change the
+ // frame policy.
+ for (var i = 0; i < policies.length; i++) {
+ for (var j = 0; j < header_policies.length; j++) {
+ test(function() {
+ test_frame_policy(
+ 'fullscreen',
+ same_origin_src + pipe_front + header_policies[j] + pipe_end,
+ undefined, policies[i].sameOriginTestExpect,
+ 'fullscreen ' + policies[i].allow + ';');
+ }, 'Test frame policy on same origin iframe with allow = "' + policies[i].allow +
+ '" and header policy = "Feature-Policy: fullscreen ' + header_policies[j] + ';".');
+ test(function() {
+ test_frame_policy(
+ 'fullscreen',
+ cross_origin_src + pipe_front + header_policies[j] + pipe_end,
+ undefined, policies[i].crossOriginTestExpect,
+ 'fullscreen ' + policies[i].allow + ';');
+ }, 'Test frame policy on cross origin iframe with allow = "' + policies[i].allow +
+ '" and header policy = "Feature-Policy: fullscreen ' + header_policies[j] + ';".');
+ }
+ }
+
+ // Test that the allow attribute overrides allowfullscreen.
+ for (var i = 0; i < policies.length; i++) {
+ test(function() {
+ test_frame_policy(
+ 'fullscreen', same_origin_src, undefined,
+ policies[i].sameOriginTestExpect,
+ 'fullscreen ' + policies[i].allow + ';', /*allowfullscreen*/true);
+ }, 'Test frame policy on same origin iframe with allow = "' + policies[i].allow +
+ '" and allowfullscreen.');
+ test(function() {
+ test_frame_policy(
+ 'fullscreen', cross_origin_src, undefined,
+ policies[i].crossOriginTestExpect,
+ 'fullscreen ' + policies[i].allow + ';', /*allowfullscreen*/true);
+ }, 'Test frame policy on cross origin iframe with allow = "' + policies[i].allow +
+ '" and allowfullscreen.');
+ test(function() {
+ test_frame_policy(
+ 'fullscreen', undefined, true, policies[i].sameOriginTestExpect,
+ 'fullscreen ' + policies[i].allow + ';', /*allowfullscreen*/true);
+ }, 'Test frame policy on srcdoc iframe with allow = "' + policies[i].allow +
+ '" and allowfullscreen.');
+ test(function() {
+ test_frame_policy(
+ 'fullscreen', same_origin_src, true, policies[i].sameOriginTestExpect,
+ 'fullscreen ' + policies[i].allow + ';', /*allowfullscreen*/true);
+ }, 'Test frame policy on srcdoc + same origin iframe with allow = "' + policies[i].allow +
+ '" and allowfullscreen.');
+ test(function() {
+ test_frame_policy(
+ 'fullscreen', cross_origin_src, true, policies[i].sameOriginTestExpect,
+ 'fullscreen ' + policies[i].allow + ';', /*allowfullscreen*/true);
+ }, 'Test frame policy on srcdoc + cross origin iframe with allow = "' + policies[i].allow +
+ '" and allowfullscreen.');
+ test(function() {
+ test_frame_policy(
+ 'fullscreen', data_src, undefined, policies[i].dataOriginTestExpect,
+ 'fullscreen ' + policies[i].allow + ';', /*allowfullscreen*/true);
+ }, 'Test frame policy on data: URL cross origin iframe with allow = "' + policies[i].allow +
+ '" and allowfullscreen.');
+ }
+ </script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/feature-policy/feature-policy-frame-policy-allowed-for-all.https.sub.html.sub.headers b/testing/web-platform/tests/feature-policy/feature-policy-frame-policy-allowed-for-all.https.sub.html.sub.headers
new file mode 100644
index 0000000000..111121a52f
--- /dev/null
+++ b/testing/web-platform/tests/feature-policy/feature-policy-frame-policy-allowed-for-all.https.sub.html.sub.headers
@@ -0,0 +1 @@
+Feature-Policy: fullscreen *;
diff --git a/testing/web-platform/tests/feature-policy/feature-policy-frame-policy-allowed-for-self.https.sub.html b/testing/web-platform/tests/feature-policy/feature-policy-frame-policy-allowed-for-self.https.sub.html
new file mode 100644
index 0000000000..9cf56ec798
--- /dev/null
+++ b/testing/web-platform/tests/feature-policy/feature-policy-frame-policy-allowed-for-self.https.sub.html
@@ -0,0 +1,186 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta name="timeout" content="long">
+ <script src=/resources/testharness.js></script>
+ <script src=/resources/testharnessreport.js></script>
+</head>
+<body>
+ <script src=/feature-policy/resources/featurepolicy.js></script>
+ <!-- Feature-Policy: fullscreen 'self'; -->
+ <script>
+ 'use strict';
+ var same_origin = 'https://{{domains[]}}:{{ports[https][0]}}';
+ var cross_origin = 'https://{{domains[www]}}:{{ports[https][0]}}';
+ var same_origin_src = '/feature-policy/resources/feature-policy-allowedfeatures.html';
+ var cross_origin_src = cross_origin + same_origin_src;
+ var data_src = 'data:text/html,<h1>data: URL</h1>';
+ var policies = [
+ {allow: "*", sameOriginTestExpect: true, crossOriginTestExpect: false, dataOriginTestExpect: false},
+ {allow: "'self'", sameOriginTestExpect: true, crossOriginTestExpect: false, dataOriginTestExpect: false},
+ {allow: "'none'", sameOriginTestExpect: false, crossOriginTestExpect: false, dataOriginTestExpect: false},
+ {allow: "'self' " + cross_origin + " https://www.example.com", sameOriginTestExpect: true, crossOriginTestExpect: false, dataOriginTestExpect: false}];
+ var pipe_front = '?pipe=sub|header(Feature-Policy,fullscreen ';
+ var pipe_end = ';)';
+ var header_policies = ["*", "'self'", "'none'"];
+
+ // Test that frame.policy inherits from parent's header policy when allow
+ // attribute is not specified.
+ test(function() {
+ test_frame_policy('fullscreen', same_origin_src, undefined, true);
+ }, 'Test frame policy on same origin iframe inherit from header policy.');
+ test(function() {
+ test_frame_policy('fullscreen', cross_origin_src, undefined, false);
+ }, 'Test frame policy on cross origin iframe inherit from header policy.');
+ test(function() {
+ test_frame_policy('fullscreen', undefined, true, true);
+ }, 'Test frame policy on srcdoc iframe inherit from header policy.');
+ test(function() {
+ test_frame_policy('fullscreen', same_origin_src, true, true);
+ }, 'Test frame policy on srcdoc + same origin iframe inherit from header policy.');
+ test(function() {
+ test_frame_policy('fullscreen', cross_origin_src, true, true);
+ }, 'Test frame policy on srcdoc + cross origin iframe inherit from header policy.');
+ test(function() {
+ test_frame_policy('fullscreen', data_src, undefined, false);
+ }, 'Test frame policy on data: URL cross origin iframe inherit from header policy.');
+
+ // Test that frame policy can be used for sandboxed frames. None of these
+ // frames should be allowed to use fullscreen, as the header prohibits any
+ // cross-origin use.`
+ test(function() {
+ test_frame_policy(
+ 'fullscreen', same_origin_src, undefined, false, undefined, false, true);
+ }, 'Test frame policy on sandboxed iframe with no allow attribute.');
+ test(function() {
+ test_frame_policy(
+ 'fullscreen', same_origin_src, undefined, false, 'fullscreen', false, true);
+ }, 'Test frame policy on sandboxed iframe with allow="fullscreen".');
+ test(function() {
+ test_frame_policy(
+ 'fullscreen', same_origin_src, undefined, false, 'fullscreen \'src\'', false, true);
+ }, 'Test frame policy on sandboxed iframe with allow="fullscreen \'src\'".');
+ test(function() {
+ test_frame_policy(
+ 'fullscreen', cross_origin_src, undefined, false, 'fullscreen ' + cross_origin, false, true);
+ }, 'Test frame policy on sandboxed iframe with allow="fullscreen ' + cross_origin + '".');
+ test(function() {
+ test_frame_policy(
+ 'fullscreen', undefined, true, false, 'fullscreen', false, true);
+ }, 'Test frame policy on srcdoc sandboxed iframe with allow="fullscreen".');
+ test(function() {
+ test_frame_policy(
+ 'fullscreen', same_origin_src, true, false, 'fullscreen', false, true);
+ }, 'Test frame policy on srcdoc + same origin sandboxed iframe with allow="fullscreen".');
+ test(function() {
+ test_frame_policy(
+ 'fullscreen', cross_origin_src, true, false, 'fullscreen', false, true);
+ }, 'Test frame policy on srcdoc + cross origin sandboxed iframe with allow="fullscreen".');
+ test(function() {
+ test_frame_policy(
+ 'fullscreen', data_src, undefined, false, 'fullscreen ' + cross_origin, false, true);
+ }, 'Test frame policy on sandboxed srcdoc iframe with allow="fullscreen ' + cross_origin + '".');
+
+ // Test frame policy with allow attribute set to be one of the policies above.
+ for (var i = 0; i < policies.length; i++) {
+ test(function() {
+ test_frame_policy(
+ 'fullscreen', same_origin_src, undefined,
+ policies[i].sameOriginTestExpect,
+ 'fullscreen ' + policies[i].allow + ';');
+ }, 'Test frame policy on same origin iframe with allow = "' + policies[i].allow + '".');
+ test(function() {
+ test_frame_policy(
+ 'fullscreen', cross_origin_src, undefined,
+ policies[i].crossOriginTestExpect,
+ 'fullscreen ' + policies[i].allow + ';');
+ }, 'Test frame policy on cross origin iframe with allow = "' + policies[i].allow + '".');
+ test(function() {
+ test_frame_policy(
+ 'fullscreen', undefined, true, policies[i].sameOriginTestExpect,
+ 'fullscreen ' + policies[i].allow + ';');
+ }, 'Test frame policy on srcdoc iframe with allow = "' + policies[i].allow + '".');
+ test(function() {
+ test_frame_policy(
+ 'fullscreen', same_origin_src, true, policies[i].sameOriginTestExpect,
+ 'fullscreen ' + policies[i].allow + ';');
+ }, 'Test frame policy on srcdoc + same origin iframe with allow = "' + policies[i].allow + '".');
+ test(function() {
+ test_frame_policy(
+ 'fullscreen', cross_origin_src, true, policies[i].sameOriginTestExpect,
+ 'fullscreen ' + policies[i].allow + ';');
+ }, 'Test frame policy on srcdoc + cross origin iframe with allow = "' + policies[i].allow + '".');
+ test(function() {
+ test_frame_policy(
+ 'fullscreen', data_src, undefined, policies[i].dataOriginTestExpect,
+ 'fullscreen ' + policies[i].allow + ';');
+ }, 'Test frame policy on data: URL origin iframe with allow = "' + policies[i].allow + '".');
+ }
+
+ // Test that the header policy of the iframe document does not change the
+ // frame policy.
+ for (var i = 0; i < policies.length; i++) {
+ for (var j = 0; j < header_policies.length; j++) {
+ test(function() {
+ test_frame_policy(
+ 'fullscreen',
+ same_origin_src + pipe_front + header_policies[j] + pipe_end,
+ undefined, policies[i].sameOriginTestExpect,
+ 'fullscreen ' + policies[i].allow + ';');
+ }, 'Test frame policy on same origin iframe with allow = "' + policies[i].allow +
+ '" and header policy = "Feature-Policy: fullscreen ' + header_policies[j] + ';".');
+ test(function() {
+ test_frame_policy(
+ 'fullscreen',
+ cross_origin_src + pipe_front + header_policies[j] + pipe_end,
+ undefined, policies[i].crossOriginTestExpect,
+ 'fullscreen ' + policies[i].allow + ';');
+ }, 'Test frame policy on cross origin iframe with allow = "' + policies[i].allow +
+ '" and header policy = "Feature-Policy: fullscreen ' + header_policies[j] + ';".');
+ }
+ }
+
+ // Test that the allow attribute overrides allowfullscreen.
+ for (var i = 0; i < policies.length; i++) {
+ test(function() {
+ test_frame_policy(
+ 'fullscreen', same_origin_src, undefined,
+ policies[i].sameOriginTestExpect,
+ 'fullscreen ' + policies[i].allow + ';', /*allowfullscreen*/true);
+ }, 'Test frame policy on same origin iframe with allow = "' + policies[i].allow +
+ '" and allowfullscreen.');
+ test(function() {
+ test_frame_policy(
+ 'fullscreen', cross_origin_src, undefined,
+ policies[i].crossOriginTestExpect,
+ 'fullscreen ' + policies[i].allow + ';', /*allowfullscreen*/true);
+ }, 'Test frame policy on cross origin iframe with allow = "' + policies[i].allow +
+ '" and allowfullscreen.');
+ test(function() {
+ test_frame_policy(
+ 'fullscreen', undefined, true, policies[i].sameOriginTestExpect,
+ 'fullscreen ' + policies[i].allow + ';', /*allowfullscreen*/true);
+ }, 'Test frame policy on srcdoc iframe with allow = "' + policies[i].allow +
+ '" and allowfullscreen.');
+ test(function() {
+ test_frame_policy(
+ 'fullscreen', same_origin_src, true, policies[i].sameOriginTestExpect,
+ 'fullscreen ' + policies[i].allow + ';', /*allowfullscreen*/true);
+ }, 'Test frame policy on srcdoc + same origin iframe with allow = "' + policies[i].allow +
+ '" and allowfullscreen.');
+ test(function() {
+ test_frame_policy(
+ 'fullscreen', cross_origin_src, true, policies[i].sameOriginTestExpect,
+ 'fullscreen ' + policies[i].allow + ';', /*allowfullscreen*/true);
+ }, 'Test frame policy on srcdoc + cross origin iframe with allow = "' + policies[i].allow +
+ '" and allowfullscreen.');
+ test(function() {
+ test_frame_policy(
+ 'fullscreen', data_src, undefined, policies[i].dataOriginTestExpect,
+ 'fullscreen ' + policies[i].allow + ';', /*allowfullscreen*/true);
+ }, 'Test frame policy on data: URL origin iframe with allow = "' + policies[i].allow +
+ '" and allowfullscreen.');
+ }
+ </script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/feature-policy/feature-policy-frame-policy-allowed-for-self.https.sub.html.sub.headers b/testing/web-platform/tests/feature-policy/feature-policy-frame-policy-allowed-for-self.https.sub.html.sub.headers
new file mode 100644
index 0000000000..0cc259b24f
--- /dev/null
+++ b/testing/web-platform/tests/feature-policy/feature-policy-frame-policy-allowed-for-self.https.sub.html.sub.headers
@@ -0,0 +1 @@
+Feature-Policy: fullscreen 'self';
diff --git a/testing/web-platform/tests/feature-policy/feature-policy-frame-policy-allowed-for-some-override.https.sub.html b/testing/web-platform/tests/feature-policy/feature-policy-frame-policy-allowed-for-some-override.https.sub.html
new file mode 100644
index 0000000000..744c950248
--- /dev/null
+++ b/testing/web-platform/tests/feature-policy/feature-policy-frame-policy-allowed-for-some-override.https.sub.html
@@ -0,0 +1,83 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta name="timeout" content="long">
+ <script src=/resources/testharness.js></script>
+ <script src=/resources/testharnessreport.js></script>
+</head>
+<body>
+ <script src=/feature-policy/resources/featurepolicy.js></script>
+ <!-- Feature-Policy: fullscreen 'self' cross_origin https://www.example.com; -->
+ <script>
+ 'use strict';
+ var same_origin = 'https://{{domains[]}}:{{ports[https][0]}}';
+ var cross_origin = 'https://{{domains[www]}}:{{ports[https][0]}}';
+ var cross_origin1 = 'https://{{domains[www1]}}:{{ports[https][0]}}';
+ var same_origin_src = '/feature-policy/resources/feature-policy-allowedfeatures.html';
+ var cross_origin_src = cross_origin + same_origin_src;
+ var cross_origin_src1 = cross_origin1 + same_origin_src;
+ var data_src = 'data:text/html,<h1>data: URL</h1>';
+ // Test feature policy with same_origin_src and cross_origin_src.
+ var policies = [
+ {allow: "*", sameOriginTestExpect: true, crossOriginTestExpect: true, crossOrigin1TestExpect: false, dataOriginTestExpect: false},
+ {allow: "'self'", sameOriginTestExpect: true, crossOriginTestExpect: false, crossOrigin1TestExpect: false, dataOriginTestExpect: false},
+ {allow: "'none'", sameOriginTestExpect: false, crossOriginTestExpect: false, crossOrigin1TestExpect: false, dataOriginTestExpect: false},
+ {allow: "'self' " + cross_origin + " https://www.example.com", sameOriginTestExpect: true, crossOriginTestExpect: true, crossOrigin1TestExpect: false, dataOriginTestExpect: false}];
+
+ // Test that the allow attribute overrides allowfullscreen.
+ for (var i = 0; i < policies.length; i++) {
+ test(function() {
+ test_frame_policy(
+ 'fullscreen', same_origin_src, undefined,
+ policies[i].sameOriginTestExpect,
+ 'fullscreen ' + policies[i].allow + ';', /*allowfullscreen*/true);
+ }, 'Test frame policy on same origin iframe with allow = "' + policies[i].allow +
+ '" and allowfullscreen.');
+ test(function() {
+ test_frame_policy(
+ 'fullscreen', cross_origin_src, undefined,
+ policies[i].crossOriginTestExpect,
+ 'fullscreen ' + policies[i].allow + ';', /*allowfullscreen*/true);
+ }, 'Test frame policy on cross origin iframe with allow = "' + policies[i].allow +
+ '" and allowfullscreen.');
+ test(function() {
+ test_frame_policy(
+ 'fullscreen', cross_origin_src1, undefined,
+ policies[i].crossOrigin1TestExpect,
+ 'fullscreen ' + policies[i].allow + ';', /*allowfullscreen*/true);
+ }, 'Test frame policy on another cross origin iframe with allow = "' + policies[i].allow +
+ '" and allowfullscreen.');
+ test(function() {
+ test_frame_policy(
+ 'fullscreen', undefined, true, policies[i].sameOriginTestExpect,
+ 'fullscreen ' + policies[i].allow + ';', /*allowfullscreen*/true);
+ }, 'Test frame policy on srcdoc iframe with allow = "' + policies[i].allow +
+ '" and allowfullscreen.');
+ test(function() {
+ test_frame_policy(
+ 'fullscreen', same_origin_src, true, policies[i].sameOriginTestExpect,
+ 'fullscreen ' + policies[i].allow + ';', /*allowfullscreen*/true);
+ }, 'Test frame policy on srcdoc + same origin iframe with allow = "' + policies[i].allow +
+ '" and allowfullscreen.');
+ test(function() {
+ test_frame_policy(
+ 'fullscreen', cross_origin_src, true, policies[i].sameOriginTestExpect,
+ 'fullscreen ' + policies[i].allow + ';', /*allowfullscreen*/true);
+ }, 'Test frame policy on srcdoc + cross origin iframe with allow = "' + policies[i].allow +
+ '" and allowfullscreen.');
+ test(function() {
+ test_frame_policy(
+ 'fullscreen', cross_origin_src1, true, policies[i].sameOriginTestExpect,
+ 'fullscreen ' + policies[i].allow + ';', /*allowfullscreen*/true);
+ }, 'Test frame policy on srcdoc + another cross origin iframe with allow = "' + policies[i].allow +
+ '" and allowfullscreen.');
+ test(function() {
+ test_frame_policy(
+ 'fullscreen', data_src, undefined, policies[i].dataOriginTestExpect,
+ 'fullscreen ' + policies[i].allow + ';', /*allowfullscreen*/true);
+ }, 'Test frame policy on data: URL cross origin iframe with allow = "' + policies[i].allow +
+ '" and allowfullscreen.');
+ }
+ </script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/feature-policy/feature-policy-frame-policy-allowed-for-some-override.https.sub.html.sub.headers b/testing/web-platform/tests/feature-policy/feature-policy-frame-policy-allowed-for-some-override.https.sub.html.sub.headers
new file mode 100644
index 0000000000..c2493a0890
--- /dev/null
+++ b/testing/web-platform/tests/feature-policy/feature-policy-frame-policy-allowed-for-some-override.https.sub.html.sub.headers
@@ -0,0 +1 @@
+Feature-Policy: fullscreen 'self' https://{{domains[www]}}:{{ports[https][0]}} https://www.example.com;
diff --git a/testing/web-platform/tests/feature-policy/feature-policy-frame-policy-allowed-for-some.https.sub.html b/testing/web-platform/tests/feature-policy/feature-policy-frame-policy-allowed-for-some.https.sub.html
new file mode 100644
index 0000000000..5dfd8430d2
--- /dev/null
+++ b/testing/web-platform/tests/feature-policy/feature-policy-frame-policy-allowed-for-some.https.sub.html
@@ -0,0 +1,136 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta name="timeout" content="long">
+ <script src=/resources/testharness.js></script>
+ <script src=/resources/testharnessreport.js></script>
+</head>
+<body>
+ <script src=/feature-policy/resources/featurepolicy.js></script>
+ <!-- Feature-Policy: fullscreen 'self' cross_origin https://www.example.com; -->
+ <script>
+ 'use strict';
+ var same_origin = 'https://{{domains[]}}:{{ports[https][0]}}';
+ var cross_origin = 'https://{{domains[www]}}:{{ports[https][0]}}';
+ var cross_origin1 = 'https://{{domains[www1]}}:{{ports[https][0]}}';
+ var same_origin_src = '/feature-policy/resources/feature-policy-allowedfeatures.html';
+ var cross_origin_src = cross_origin + same_origin_src;
+ var cross_origin_src1 = cross_origin1 + same_origin_src;
+ var data_src = 'data:text/html,<h1>data: URL</h1>';
+ // Test feature policy with same_origin_src and cross_origin_src.
+ var policies = [
+ {allow: "*", sameOriginTestExpect: true, crossOriginTestExpect: true, crossOrigin1TestExpect: false, dataOriginTestExpect: false},
+ {allow: "'self'", sameOriginTestExpect: true, crossOriginTestExpect: false, crossOrigin1TestExpect: false, dataOriginTestExpect: false},
+ {allow: "'none'", sameOriginTestExpect: false, crossOriginTestExpect: false, crossOrigin1TestExpect: false, dataOriginTestExpect: false},
+ {allow: "'self' " + cross_origin + " https://www.example.com", sameOriginTestExpect: true, crossOriginTestExpect: true, crossOrigin1TestExpect: false, dataOriginTestExpect: false}];
+ var pipe_front = '?pipe=sub|header(Feature-Policy,fullscreen ';
+ var pipe_end = ';)';
+ var header_policies = ["*", "'self'", "'none'"];
+
+ // Test that frame.policy inherits from parent's header policy when allow
+ // attribute is not specified.
+ test(function() {
+ test_frame_policy('fullscreen', same_origin_src, undefined, true);
+ }, 'Test frame policy on same origin iframe inherit from header policy.');
+ test(function() {
+ test_frame_policy('fullscreen', cross_origin_src, undefined, false);
+ }, 'Test frame policy on cross origin iframe inherit from header policy.');
+ test(function() {
+ test_frame_policy('fullscreen', cross_origin_src1, undefined, false);
+ }, 'Test frame policy on another cross origin iframe inherit from header policy.');
+ test(function() {
+ test_frame_policy('fullscreen', undefined, true, true);
+ }, 'Test frame policy on srcdoc iframe inherit from header policy.');
+ test(function() {
+ test_frame_policy('fullscreen', same_origin_src, true, true);
+ }, 'Test frame policy on srcdoc + same origin iframe inherit from header policy.');
+ test(function() {
+ test_frame_policy('fullscreen', cross_origin_src, true, true);
+ }, 'Test frame policy on srcdoc + cross origin iframe inherit from header policy.');
+ test(function() {
+ test_frame_policy('fullscreen', cross_origin_src1, true, true);
+ }, 'Test frame policy on srcdoc + another cross origin iframe inherit from header policy.');
+ test(function() {
+ test_frame_policy('fullscreen', data_src, undefined, false);
+ }, 'Test frame policy on data: URL cross origin iframe inherit from header policy.');
+
+ // Test frame policy with allow attribute set to be one of the policies above.
+ for (var i = 0; i < policies.length; i++) {
+ test(function() {
+ test_frame_policy(
+ 'fullscreen', same_origin_src, undefined,
+ policies[i].sameOriginTestExpect,
+ 'fullscreen ' + policies[i].allow + ';');
+ }, 'Test frame policy on same origin iframe with allow = "' + policies[i].allow + '".');
+ test(function() {
+ test_frame_policy(
+ 'fullscreen', cross_origin_src, undefined,
+ policies[i].crossOriginTestExpect,
+ 'fullscreen ' + policies[i].allow + ';');
+ }, 'Test frame policy on cross origin iframe with allow = "' + policies[i].allow + '".');
+ test(function() {
+ test_frame_policy(
+ 'fullscreen', cross_origin_src1, undefined,
+ policies[i].crossOrigin1TestExpect,
+ 'fullscreen ' + policies[i].allow + ';');
+ }, 'Test frame policy on another cross origin iframe with allow = "' + policies[i].allow + '".');
+ test(function() {
+ test_frame_policy(
+ 'fullscreen', undefined, true, policies[i].sameOriginTestExpect,
+ 'fullscreen ' + policies[i].allow + ';');
+ }, 'Test frame policy on srcdoc iframe with allow = "' + policies[i].allow + '".');
+ test(function() {
+ test_frame_policy(
+ 'fullscreen', same_origin_src, true, policies[i].sameOriginTestExpect,
+ 'fullscreen ' + policies[i].allow + ';');
+ }, 'Test frame policy on srcdoc + same origin iframe with allow = "' + policies[i].allow + '".');
+ test(function() {
+ test_frame_policy(
+ 'fullscreen', cross_origin_src, true, policies[i].sameOriginTestExpect,
+ 'fullscreen ' + policies[i].allow + ';');
+ }, 'Test frame policy on srcdoc + cross origin iframe with allow = "' + policies[i].allow + '".');
+ test(function() {
+ test_frame_policy(
+ 'fullscreen', cross_origin_src1, true, policies[i].sameOriginTestExpect,
+ 'fullscreen ' + policies[i].allow + ';');
+ }, 'Test frame policy on srcdoc + another cross origin iframe with allow = "' + policies[i].allow + '".');
+ test(function() {
+ test_frame_policy(
+ 'fullscreen', data_src, undefined, policies[i].dataOriginTestExpect,
+ 'fullscreen ' + policies[i].allow + ';');
+ }, 'Test frame policy on data: URL cross origin iframe with allow = "' + policies[i].allow + '".');
+ }
+
+ // Test that the header policy of the iframe document does not change the
+ // frame policy.
+ for (var i = 0; i < policies.length; i++) {
+ for (var j = 0; j < header_policies.length; j++) {
+ test(function() {
+ test_frame_policy(
+ 'fullscreen',
+ same_origin_src + pipe_front + header_policies[j] + pipe_end,
+ undefined, policies[i].sameOriginTestExpect,
+ 'fullscreen ' + policies[i].allow + ';');
+ }, 'Test frame policy on same origin iframe with allow = "' + policies[i].allow +
+ '" and header policy = "Feature-Policy: fullscreen ' + header_policies[j] + ';".');
+ test(function() {
+ test_frame_policy(
+ 'fullscreen',
+ cross_origin_src + pipe_front + header_policies[j] + pipe_end,
+ undefined, policies[i].crossOriginTestExpect,
+ 'fullscreen ' + policies[i].allow + ';');
+ }, 'Test frame policy on cross origin iframe with allow = "' + policies[i].allow +
+ '" and header policy = "Feature-Policy: fullscreen ' + header_policies[j] + ';".');
+ test(function() {
+ test_frame_policy(
+ 'fullscreen',
+ cross_origin_src1 + pipe_front + header_policies[j] + pipe_end,
+ undefined, policies[i].crossOrigin1TestExpect,
+ 'fullscreen ' + policies[i].allow + ';');
+ }, 'Test frame policy on another cross origin iframe with allow = "' + policies[i].allow +
+ '" and header policy = "Feature-Policy: fullscreen ' + header_policies[j] + ';".');
+ }
+ }
+ </script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/feature-policy/feature-policy-frame-policy-allowed-for-some.https.sub.html.sub.headers b/testing/web-platform/tests/feature-policy/feature-policy-frame-policy-allowed-for-some.https.sub.html.sub.headers
new file mode 100644
index 0000000000..c2493a0890
--- /dev/null
+++ b/testing/web-platform/tests/feature-policy/feature-policy-frame-policy-allowed-for-some.https.sub.html.sub.headers
@@ -0,0 +1 @@
+Feature-Policy: fullscreen 'self' https://{{domains[www]}}:{{ports[https][0]}} https://www.example.com;
diff --git a/testing/web-platform/tests/feature-policy/feature-policy-frame-policy-disallowed-for-all.https.sub.html b/testing/web-platform/tests/feature-policy/feature-policy-frame-policy-disallowed-for-all.https.sub.html
new file mode 100644
index 0000000000..7ac821d885
--- /dev/null
+++ b/testing/web-platform/tests/feature-policy/feature-policy-frame-policy-disallowed-for-all.https.sub.html
@@ -0,0 +1,150 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta name="timeout" content="long">
+ <script src=/resources/testharness.js></script>
+ <script src=/resources/testharnessreport.js></script>
+</head>
+<body>
+ <script src=/feature-policy/resources/featurepolicy.js></script>
+ <!-- Feature-Policy: fullscreen 'none'; -->
+ <script>
+ 'use strict';
+ var same_origin = 'https://{{domains[]}}:{{ports[https][0]}}';
+ var cross_origin = 'https://{{domains[www]}}:{{ports[https][0]}}';
+ var same_origin_src = '/feature-policy/resources/feature-policy-allowedfeatures.html';
+ var cross_origin_src = cross_origin + same_origin_src;
+ var data_src = 'data:text/html,<h1>data: URL</h1>';
+ var policies = [
+ {allow: "*", sameOriginTestExpect: false, crossOriginTestExpect: false, dataOriginTestExpect: false},
+ {allow: "'self'", sameOriginTestExpect: false, crossOriginTestExpect: false, dataOriginTestExpect: false},
+ {allow: "'none'", sameOriginTestExpect: false, crossOriginTestExpect: false, dataOriginTestExpect: false},
+ {allow: "'self' " + cross_origin + " https://www.example.com", sameOriginTestExpect: false, crossOriginTestExpect: false, dataOriginTestExpect: false}];
+ var pipe_front = '?pipe=sub|header(Feature-Policy,fullscreen ';
+ var pipe_end = ';)';
+ var header_policies = ["*", "'self'", "'none'"];
+
+ // Test that frame.policy inherits from parent's header policy when allow
+ // attribute is not specified.
+ test(function() {
+ test_frame_policy('fullscreen', same_origin_src, undefined, false);
+ }, 'Test frame policy on same origin iframe inherit from header policy.');
+ test(function() {
+ test_frame_policy('fullscreen', cross_origin_src, undefined, false);
+ }, 'Test frame policy on cross origin iframe inherit from header policy.');
+ test(function() {
+ test_frame_policy('fullscreen', undefined, true, false);
+ }, 'Test frame policy on srcdoc iframe inherit from header policy.');
+ test(function() {
+ test_frame_policy('fullscreen', same_origin_src, true, false);
+ }, 'Test frame policy on srcdoc + same origin iframe inherit from header policy.');
+ test(function() {
+ test_frame_policy('fullscreen', cross_origin_src, true, false);
+ }, 'Test frame policy on srcdoc + cross origin iframe inherit from header policy.');
+ test(function() {
+ test_frame_policy('fullscreen', data_src, undefined, false);
+ }, 'Test frame policy on data: URL cross origin iframe inherit from header policy.');
+
+ // Test frame policy with allow attribute set to be one of the policies above.
+ for (var i = 0; i < policies.length; i++) {
+ test(function() {
+ test_frame_policy(
+ 'fullscreen', same_origin_src, undefined,
+ policies[i].sameOriginTestExpect,
+ 'fullscreen ' + policies[i].allow + ';');
+ }, 'Test frame policy on same origin iframe with allow = "' + policies[i].allow + '".');
+ test(function() {
+ test_frame_policy(
+ 'fullscreen', cross_origin_src, undefined,
+ policies[i].crossOriginTestExpect,
+ 'fullscreen ' + policies[i].allow + ';');
+ }, 'Test frame policy on cross origin iframe with allow = "' + policies[i].allow + '".');
+ test(function() {
+ test_frame_policy(
+ 'fullscreen', undefined, true, policies[i].sameOriginTestExpect,
+ 'fullscreen ' + policies[i].allow + ';');
+ }, 'Test frame policy on srcdoc iframe with allow = "' + policies[i].allow + '".');
+ test(function() {
+ test_frame_policy(
+ 'fullscreen', same_origin_src, true, policies[i].sameOriginTestExpect,
+ 'fullscreen ' + policies[i].allow + ';');
+ }, 'Test frame policy on srcdoc + same origin iframe with allow = "' + policies[i].allow + '".');
+ test(function() {
+ test_frame_policy(
+ 'fullscreen', cross_origin_src, true, policies[i].sameOriginTestExpect,
+ 'fullscreen ' + policies[i].allow + ';');
+ }, 'Test frame policy on srcdoc + cross origin iframe with allow = "' + policies[i].allow + '".');
+ test(function() {
+ test_frame_policy(
+ 'fullscreen', data_src, undefined, policies[i].dataOriginTestExpect,
+ 'fullscreen ' + policies[i].allow + ';');
+ }, 'Test frame policy on data: URL cross origin iframe with allow = "' + policies[i].allow + '".');
+ }
+
+ // Test that the header policy of the iframe document does not change the
+ // frame policy.
+ for (var i = 0; i < policies.length; i++) {
+ for (var j = 0; j < header_policies.length; j++) {
+ test(function() {
+ test_frame_policy(
+ 'fullscreen',
+ same_origin_src + pipe_front + header_policies[j] + pipe_end,
+ undefined, policies[i].sameOriginTestExpect,
+ 'fullscreen ' + policies[i].allow + ';');
+ }, 'Test frame policy on same origin iframe with allow = "' + policies[i].allow +
+ '" and header policy = "Feature-Policy: fullscreen ' + header_policies[j] + ';".');
+ test(function() {
+ test_frame_policy(
+ 'fullscreen',
+ cross_origin_src + pipe_front + header_policies[j] + pipe_end,
+ undefined, policies[i].crossOriginTestExpect,
+ 'fullscreen ' + policies[i].allow + ';');
+ }, 'Test frame policy on cross origin iframe with allow = "' + policies[i].allow +
+ '" and header policy = "Feature-Policy: fullscreen ' + header_policies[j] + ';".');
+ }
+ }
+
+ // Test that the allow attribute overrides allowfullscreen.
+ for (var i = 0; i < policies.length; i++) {
+ test(function() {
+ test_frame_policy(
+ 'fullscreen', same_origin_src, undefined,
+ policies[i].sameOriginTestExpect,
+ 'fullscreen ' + policies[i].allow + ';', /*allowfullscreen*/true);
+ }, 'Test frame policy on same origin iframe with allow = "' + policies[i].allow +
+ '" and allowfullscreen.');
+ test(function() {
+ test_frame_policy(
+ 'fullscreen', cross_origin_src, undefined,
+ policies[i].crossOriginTestExpect,
+ 'fullscreen ' + policies[i].allow + ';', /*allowfullscreen*/true);
+ }, 'Test frame policy on cross origin iframe with allow = "' + policies[i].allow +
+ '" and allowfullscreen.');
+ test(function() {
+ test_frame_policy(
+ 'fullscreen', undefined, true, policies[i].sameOriginTestExpect,
+ 'fullscreen ' + policies[i].allow + ';', /*allowfullscreen*/true);
+ }, 'Test frame policy on srcdoc iframe with allow = "' + policies[i].allow +
+ '" and allowfullscreen.');
+ test(function() {
+ test_frame_policy(
+ 'fullscreen', same_origin_src, true, policies[i].sameOriginTestExpect,
+ 'fullscreen ' + policies[i].allow + ';', /*allowfullscreen*/true);
+ }, 'Test frame policy on srcdoc + same origin iframe with allow = "' + policies[i].allow +
+ '" and allowfullscreen.');
+ test(function() {
+ test_frame_policy(
+ 'fullscreen', cross_origin_src, true, policies[i].sameOriginTestExpect,
+ 'fullscreen ' + policies[i].allow + ';', /*allowfullscreen*/true);
+ }, 'Test frame policy on srcdoc + cross origin iframe with allow = "' + policies[i].allow +
+ '" and allowfullscreen.');
+ test(function() {
+ test_frame_policy(
+ 'fullscreen', data_src, undefined, policies[i].dataOriginTestExpect,
+ 'fullscreen ' + policies[i].allow + ';', /*allowfullscreen*/true);
+ }, 'Test frame policy on data: URL cross origin iframe with allow = "' + policies[i].allow +
+ '" and allowfullscreen.');
+ }
+ </script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/feature-policy/feature-policy-frame-policy-disallowed-for-all.https.sub.html.sub.headers b/testing/web-platform/tests/feature-policy/feature-policy-frame-policy-disallowed-for-all.https.sub.html.sub.headers
new file mode 100644
index 0000000000..961d40336a
--- /dev/null
+++ b/testing/web-platform/tests/feature-policy/feature-policy-frame-policy-disallowed-for-all.https.sub.html.sub.headers
@@ -0,0 +1 @@
+Feature-Policy: fullscreen 'none';
diff --git a/testing/web-platform/tests/feature-policy/feature-policy-frame-policy-timing-iframe-camera.https.sub.html b/testing/web-platform/tests/feature-policy/feature-policy-frame-policy-timing-iframe-camera.https.sub.html
new file mode 100644
index 0000000000..995ac2134e
--- /dev/null
+++ b/testing/web-platform/tests/feature-policy/feature-policy-frame-policy-timing-iframe-camera.https.sub.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<script>
+window.onmessage = m => {
+ navigator.mediaDevices.getUserMedia({video: true})
+ .then(() => 'ok')
+ .catch(e => e.name)
+ .then(r => {
+ window.parent.postMessage({
+ id: m.data.id,
+ result: r
+ }, '*');
+ });
+}
+</script>
diff --git a/testing/web-platform/tests/feature-policy/feature-policy-frame-policy-timing.https.sub.html b/testing/web-platform/tests/feature-policy/feature-policy-frame-policy-timing.https.sub.html
new file mode 100644
index 0000000000..c78d16132b
--- /dev/null
+++ b/testing/web-platform/tests/feature-policy/feature-policy-frame-policy-timing.https.sub.html
@@ -0,0 +1,69 @@
+<!doctype html>
+<html>
+ <header>
+ <title>allow/sandbox attr changed after document creation, before response</title>
+ <script src=/resources/testharness.js></script>
+ <script src=/resources/testharnessreport.js></script>
+ <script>
+ let lastCallbackId = 0;
+ const callbacks = {};
+
+ function postMessageToFrame(frame, cb) {
+ var id = ++lastCallbackId;
+ callbacks[id] = cb;
+ frame.contentWindow.postMessage({id:id}, '*');
+ step_timeout(() => {
+ if (id in callbacks) {
+ callbacks[id]('timeout');
+ delete callbacks[id];
+ }
+ }, 1000);
+ }
+
+ window.onmessage = function(e) {
+ const message = e.data;
+ const id = message['id'];
+ const callback = callbacks[id];
+ delete callbacks[id];
+ callback(message.result);
+ };
+ // @param {string} url
+ // @param {Function} iframe_pre_nav_callback - a callback with signature (iframe) => () which gets
+ // triggered before setting src attribute.
+ // @param {Function} iframe_post_nav_callback - a callback with signature (iframe) => () which gets
+ // triggered after setting src attribute but before commit
+ // of navigation.
+ // @param {Function} result_handler - a callback that handles the result posted back from iframe.
+ // @param {string} test_name
+ function timing_test(url,
+ iframe_pre_nav_callback,
+ iframe_post_nav_callback,
+ result_handler, test_name) {
+ async_test((t) => {
+ const iframe = document.createElement('iframe');
+ document.body.appendChild(iframe);
+ iframe_pre_nav_callback(iframe);
+ iframe.src = url;
+ iframe_post_nav_callback(iframe);
+ iframe.onload = t.step_func(() => {
+ postMessageToFrame(iframe, t.step_func_done(result_handler));
+ });
+ }, test_name);
+ }
+
+ const path = location.pathname.substring(0, location.pathname.lastIndexOf('/') + 1);
+ const same_origin = path;
+ const cross_origin = "https://{{domains[www1]}}:{{ports[https][0]}}" + path;
+
+ const cameraUrl = 'feature-policy-frame-policy-timing-iframe-camera.https.sub.html';
+ function disallowCamera(iframe) { iframe.allow = "camera 'none'"; }
+ function allowCamera(iframe) { iframe.allow = 'camera *'; }
+ function verifyCamera(result) { assert_equals(result, 'NotAllowedError'); }
+ timing_test(same_origin + cameraUrl, disallowCamera, allowCamera, verifyCamera, 'allow attr timing test same origin');
+ timing_test(cross_origin + cameraUrl, disallowCamera, allowCamera, verifyCamera, 'allow attr timing test diff origin');
+ </script>
+ </header>
+ <body>
+ <div id="log"></div>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/feature-policy/feature-policy-header-policy-allowed-for-all.https.sub.html b/testing/web-platform/tests/feature-policy/feature-policy-header-policy-allowed-for-all.https.sub.html
new file mode 100644
index 0000000000..a036859451
--- /dev/null
+++ b/testing/web-platform/tests/feature-policy/feature-policy-header-policy-allowed-for-all.https.sub.html
@@ -0,0 +1,61 @@
+<!DOCTYPE html>
+<body>
+ <script src=/resources/testharness.js></script>
+ <script src=/resources/testharnessreport.js></script>
+ <script src=/feature-policy/resources/featurepolicy.js></script>
+ <!-- Feature-Policy: fullscreen *; -->
+ <script>
+ 'use strict';
+ var same_origin = 'https://{{domains[]}}:{{ports[https][0]}}';
+ var cross_origin = 'https://{{domains[www]}}:{{ports[https][0]}}';
+ var same_origin_src = '/feature-policy/resources/feature-policy-allowedfeatures.html';
+ var cross_origin_src = cross_origin + same_origin_src;
+ var header_policy = 'Feature-Policy: fullscreen *';
+
+ // Test that fullscreen's allowlist is ['*']
+ test(function() {
+ assert_array_equals(
+ document.featurePolicy.getAllowlistForFeature('fullscreen'),
+ ['*']);
+ }, header_policy + ' -- test allowlist is ['*']');
+
+ // Test that fullscreen is allowed on same-origin subframes.
+ test_allowed_feature_for_subframe(
+ header_policy + ' -- test fullscreen is allowed on same-origin subframe',
+ 'fullscreen',
+ same_origin_src);
+
+ // Test that fullscreen is not allowed on cross-origin subframes without an
+ // allow attribute.
+ test_disallowed_feature_for_subframe(
+ header_policy + ' -- test fullscreen is disallowed on cross-origin subframe',
+ 'fullscreen',
+ cross_origin_src);
+
+ // Dynamically update sub frame's container policy to self
+ var allow = "fullscreen 'self';"
+ test_allowed_feature_for_subframe(
+ header_policy + ', iframe.allow = ' + allow + ' -- test fullscreen is allowed on same-origin subframe',
+ 'fullscreen',
+ same_origin_src,
+ allow);
+ test_disallowed_feature_for_subframe(
+ header_policy + ', iframe.allow = ' + allow + ' -- test fullscreen is disallowed on cross-origin subframe',
+ 'fullscreen',
+ cross_origin_src,
+ allow);
+
+ // Dynamically update sub frame's container policy to src
+ var allow = "fullscreen 'src';"
+ test_allowed_feature_for_subframe(
+ header_policy + ', iframe.allow = ' + allow + ' -- test fullscreen is allowed on same-origin subframe',
+ 'fullscreen',
+ same_origin_src,
+ allow);
+ test_allowed_feature_for_subframe(
+ header_policy + ', iframe.allow = ' + allow + ' -- test fullscreen is allowed on cross-origin subframe',
+ 'fullscreen',
+ cross_origin_src,
+ allow);
+ </script>
+</body>
diff --git a/testing/web-platform/tests/feature-policy/feature-policy-header-policy-allowed-for-all.https.sub.html.sub.headers b/testing/web-platform/tests/feature-policy/feature-policy-header-policy-allowed-for-all.https.sub.html.sub.headers
new file mode 100644
index 0000000000..111121a52f
--- /dev/null
+++ b/testing/web-platform/tests/feature-policy/feature-policy-header-policy-allowed-for-all.https.sub.html.sub.headers
@@ -0,0 +1 @@
+Feature-Policy: fullscreen *;
diff --git a/testing/web-platform/tests/feature-policy/feature-policy-header-policy-allowed-for-self.https.sub.html b/testing/web-platform/tests/feature-policy/feature-policy-header-policy-allowed-for-self.https.sub.html
new file mode 100644
index 0000000000..b6fbdd2cc2
--- /dev/null
+++ b/testing/web-platform/tests/feature-policy/feature-policy-header-policy-allowed-for-self.https.sub.html
@@ -0,0 +1,45 @@
+<!DOCTYPE html>
+<body>
+ <script src=/resources/testharness.js></script>
+ <script src=/resources/testharnessreport.js></script>
+ <script src=/feature-policy/resources/featurepolicy.js></script>
+ <!-- Feature-Policy: fullscreen 'self'; -->
+ <script>
+ 'use strict';
+ var same_origin = 'https://{{domains[]}}:{{ports[https][0]}}';
+ var cross_origin = 'https://{{domains[www]}}:{{ports[https][0]}}';
+ var same_origin_src = '/feature-policy/resources/feature-policy-allowedfeatures.html';
+ var cross_origin_src = cross_origin + same_origin_src;
+ var header_policy = 'Feature-Policy: fullscreen \'self\'';
+
+ // Test that fullscreen's allowlist is ['same_origin']
+ test(function() {
+ assert_array_equals(
+ document.featurePolicy.getAllowlistForFeature('fullscreen'),
+ [same_origin]);
+ }, header_policy + ' -- test allowlist is [same_origin]');
+
+ // Test that fullscreen is only allowed on same-origin subframe.
+ test_allowed_feature_for_subframe(
+ header_policy + ' -- test fullscreen is allowed on same-origin subframe',
+ 'fullscreen',
+ same_origin_src);
+ test_disallowed_feature_for_subframe(
+ header_policy + ' -- test fullscreen is disallowed on cross-origin subframe',
+ 'fullscreen',
+ cross_origin_src);
+
+ // Dynamically update sub frame's container policy
+ var allow = "fullscreen 'src';"
+ test_allowed_feature_for_subframe(
+ header_policy + ', iframe.allow = ' + allow + ' -- test fullscreen is allowed on same-origin subframe',
+ 'fullscreen',
+ same_origin_src,
+ allow);
+ test_disallowed_feature_for_subframe(
+ header_policy + ', iframe.allow = ' + allow + ' -- test fullscreen is disallowed on cross-origin subframe',
+ 'fullscreen',
+ cross_origin_src,
+ allow);
+ </script>
+</body>
diff --git a/testing/web-platform/tests/feature-policy/feature-policy-header-policy-allowed-for-self.https.sub.html.sub.headers b/testing/web-platform/tests/feature-policy/feature-policy-header-policy-allowed-for-self.https.sub.html.sub.headers
new file mode 100644
index 0000000000..0cc259b24f
--- /dev/null
+++ b/testing/web-platform/tests/feature-policy/feature-policy-header-policy-allowed-for-self.https.sub.html.sub.headers
@@ -0,0 +1 @@
+Feature-Policy: fullscreen 'self';
diff --git a/testing/web-platform/tests/feature-policy/feature-policy-header-policy-allowed-for-some.https.sub.html b/testing/web-platform/tests/feature-policy/feature-policy-header-policy-allowed-for-some.https.sub.html
new file mode 100644
index 0000000000..5eda8baa08
--- /dev/null
+++ b/testing/web-platform/tests/feature-policy/feature-policy-header-policy-allowed-for-some.https.sub.html
@@ -0,0 +1,76 @@
+<!DOCTYPE html>
+<body>
+ <script src=/resources/testharness.js></script>
+ <script src=/resources/testharnessreport.js></script>
+ <script src=/feature-policy/resources/featurepolicy.js></script>
+ <!-- Feature-Policy: fullscreen 'self' cross_origin https://www.example.com; -->
+ <script>
+ 'use strict';
+ var same_origin = 'https://{{domains[]}}:{{ports[https][0]}}';
+ var cross_origin = 'https://{{domains[www]}}:{{ports[https][0]}}';
+ var cross_origin1 = 'https://{{domains[www1]}}:{{ports[https][0]}}';
+ var same_origin_src = '/feature-policy/resources/feature-policy-allowedfeatures.html';
+ var cross_origin_src = cross_origin + same_origin_src;
+ var cross_origin_src1 = cross_origin1 + same_origin_src;
+ var header_policy = 'Feature-Policy: fullscreen \'self\' ' + cross_origin +
+ ' https://www.example.com;';
+
+ // Test that fullscreen's allowlist is [same_origin, cross_origin, 'https://www.example.com']
+ test(function() {
+ assert_array_equals(
+ document.featurePolicy.getAllowlistForFeature('fullscreen').sort(),
+ [same_origin, cross_origin, 'https://www.example.com'].sort());
+ }, header_policy + ' -- test allowlist is [same_origin, cross_origin, https://www.example.com]');
+
+ // Test that fullscreen is allowed on same-origin, but disallowed on cross-
+ // origin subframes, without an allow attribute.
+ test_allowed_feature_for_subframe(
+ header_policy + ' -- test fullscreen is allowed on same-origin subframe',
+ 'fullscreen',
+ same_origin_src);
+ test_disallowed_feature_for_subframe(
+ header_policy + ' -- test fullscreen is disallowed on cross-origin ' + cross_origin_src + ' subframe',
+ 'fullscreen',
+ cross_origin_src);
+ test_disallowed_feature_for_subframe(
+ header_policy + ' -- test fullscreen is disallowed on cross-origin ' + cross_origin_src1 + ' subframe',
+ 'fullscreen',
+ cross_origin_src1);
+
+ // dynamically update sub frame's container policy to none
+ var allow = "fullscreen 'none';"
+ test_disallowed_feature_for_subframe(
+ header_policy + ', iframe.allow = ' + allow + ' -- test fullscreen is disallowed on same-origin subframe',
+ 'fullscreen',
+ same_origin_src,
+ allow);
+ test_disallowed_feature_for_subframe(
+ header_policy + 'iframe.allow = ' + allow + ' -- test fullscreen is disallowed on cross-origin subframe',
+ 'fullscreen',
+ cross_origin_src,
+ allow);
+ test_disallowed_feature_for_subframe(
+ header_policy + 'iframe.allow = ' + allow + ' -- test fullscreen is disallowed on another cross-origin subframe',
+ 'fullscreen',
+ cross_origin_src1,
+ allow);
+
+ // dynamically update sub frame's container policy to src
+ var allow = "fullscreen 'src';"
+ test_allowed_feature_for_subframe(
+ header_policy + ', iframe.allow = ' + allow + ' -- test fullscreen is allowed on same-origin subframe',
+ 'fullscreen',
+ same_origin_src,
+ allow);
+ test_allowed_feature_for_subframe(
+ header_policy + 'iframe.allow = ' + allow + ' -- test fullscreen is allowed on cross-origin subframe',
+ 'fullscreen',
+ cross_origin_src,
+ allow);
+ test_disallowed_feature_for_subframe(
+ header_policy + 'iframe.allow = ' + allow + ' -- test fullscreen is disallowed on another cross-origin subframe',
+ 'fullscreen',
+ cross_origin_src1,
+ allow);
+ </script>
+</body>
diff --git a/testing/web-platform/tests/feature-policy/feature-policy-header-policy-allowed-for-some.https.sub.html.sub.headers b/testing/web-platform/tests/feature-policy/feature-policy-header-policy-allowed-for-some.https.sub.html.sub.headers
new file mode 100644
index 0000000000..c2493a0890
--- /dev/null
+++ b/testing/web-platform/tests/feature-policy/feature-policy-header-policy-allowed-for-some.https.sub.html.sub.headers
@@ -0,0 +1 @@
+Feature-Policy: fullscreen 'self' https://{{domains[www]}}:{{ports[https][0]}} https://www.example.com;
diff --git a/testing/web-platform/tests/feature-policy/feature-policy-header-policy-declined.https.sub.html b/testing/web-platform/tests/feature-policy/feature-policy-header-policy-declined.https.sub.html
new file mode 100644
index 0000000000..17378e3e13
--- /dev/null
+++ b/testing/web-platform/tests/feature-policy/feature-policy-header-policy-declined.https.sub.html
@@ -0,0 +1,75 @@
+<!DOCTYPE html>
+<body>
+ <script src=/resources/testharness.js></script>
+ <script src=/resources/testharnessreport.js></script>
+ <script src=/feature-policy/resources/featurepolicy.js></script>
+ <!-- Feature-Policy: fullscreen cross_origin https://www.example.com; -->
+ <script>
+ 'use strict';
+ var same_origin = 'https://{{domains[]}}:{{ports[https][0]}}';
+ var cross_origin = 'https://{{domains[www]}}:{{ports[https][0]}}';
+ var cross_origin1 = 'https://{{domains[www1]}}:{{ports[https][0]}}';
+ var same_origin_src = '/feature-policy/resources/feature-policy-allowedfeatures.html';
+ var cross_origin_src = cross_origin + same_origin_src;
+ var cross_origin_src1 = cross_origin1 + same_origin_src;
+ var header_policy = 'Feature-Policy: fullscreen \'self\' ' + cross_origin +
+ ' https://www.example.com;';
+
+ // Test that fullscreen's allowlist is [same_origin, cross_origin, 'https://www.example.com']
+ test(function() {
+ assert_array_equals(
+ document.featurePolicy.getAllowlistForFeature('fullscreen'),
+ [cross_origin, 'https://www.example.com'].sort());
+ }, header_policy + ' -- test allowlist is [cross_origin, https://www.example.com]');
+
+ // Test that fullscreen is disallowed everywhere.
+ test_disallowed_feature_for_subframe(
+ header_policy + ' -- test fullscreen is disallowed on same-origin subframe',
+ 'fullscreen',
+ same_origin_src);
+ test_disallowed_feature_for_subframe(
+ header_policy + ' -- test fullscreen is disallowed on cross-origin ' + cross_origin_src + ' subframe',
+ 'fullscreen',
+ cross_origin_src);
+ test_disallowed_feature_for_subframe(
+ header_policy + ' -- test fullscreen is disallowed on cross-origin ' + cross_origin_src1 + ' subframe',
+ 'fullscreen',
+ cross_origin_src1);
+
+ // dynamically update sub frame's container policy to none
+ var disallow = "fullscreen 'none';"
+ test_disallowed_feature_for_subframe(
+ header_policy + ', iframe.allow = ' + disallow + ' -- test fullscreen is disallowed on same-origin subframe',
+ 'fullscreen',
+ same_origin_src,
+ disallow);
+ test_disallowed_feature_for_subframe(
+ header_policy + 'iframe.allow = ' + disallow + ' -- test fullscreen is disallowed on specific cross-origin subframe',
+ 'fullscreen',
+ cross_origin_src,
+ disallow);
+ test_disallowed_feature_for_subframe(
+ header_policy + 'iframe.allow = ' + disallow + ' -- test fullscreen is disallowed on another cross-origin subframe',
+ 'fullscreen',
+ cross_origin_src1,
+ disallow);
+
+ // dynamically update sub frame's container policy to cross_origin
+ var allow = "fullscreen " + cross_origin;
+ test_disallowed_feature_for_subframe(
+ header_policy + ', iframe.allow = ' + allow + ' -- test fullscreen is disallowed on same-origin subframe',
+ 'fullscreen',
+ same_origin_src,
+ allow);
+ test_disallowed_feature_for_subframe(
+ header_policy + 'iframe.allow = ' + allow + ' -- test fullscreen is disallowed on specific cross-origin subframe',
+ 'fullscreen',
+ cross_origin_src,
+ allow);
+ test_disallowed_feature_for_subframe(
+ header_policy + 'iframe.allow = ' + allow + ' -- test fullscreen is disallowed on another cross-origin subframe',
+ 'fullscreen',
+ cross_origin_src1,
+ allow);
+ </script>
+</body>
diff --git a/testing/web-platform/tests/feature-policy/feature-policy-header-policy-declined.https.sub.html.sub.headers b/testing/web-platform/tests/feature-policy/feature-policy-header-policy-declined.https.sub.html.sub.headers
new file mode 100644
index 0000000000..4ac2a4a588
--- /dev/null
+++ b/testing/web-platform/tests/feature-policy/feature-policy-header-policy-declined.https.sub.html.sub.headers
@@ -0,0 +1 @@
+Feature-Policy: fullscreen https://{{domains[www]}}:{{ports[https][0]}} https://www.example.com;
diff --git a/testing/web-platform/tests/feature-policy/feature-policy-header-policy-disallowed-for-all.https.sub.html b/testing/web-platform/tests/feature-policy/feature-policy-header-policy-disallowed-for-all.https.sub.html
new file mode 100644
index 0000000000..936f261937
--- /dev/null
+++ b/testing/web-platform/tests/feature-policy/feature-policy-header-policy-disallowed-for-all.https.sub.html
@@ -0,0 +1,46 @@
+<!DOCTYPE html>
+<body>
+ <script src=/resources/testharness.js></script>
+ <script src=/resources/testharnessreport.js></script>
+ <script src=/feature-policy/resources/featurepolicy.js></script>
+ <!-- Feature-Policy: fullscreen 'none'; -->
+ <script>
+ 'use strict';
+ var same_origin = 'https://{{domains[]}}:{{ports[https][0]}}';
+ var cross_origin = 'https://{{domains[www]}}:{{ports[https][0]}}';
+ var same_origin_src = '/feature-policy/resources/feature-policy-allowedfeatures.html';
+ var cross_origin_src = cross_origin + same_origin_src;
+ var header_policy = 'Feature-Policy: fullscreen \'none\'';
+
+ // Test that fullscreen's allowlist is []
+ test(function() {
+ assert_array_equals(
+ document.featurePolicy.getAllowlistForFeature('fullscreen'),
+ []);
+ assert_false(document.fullscreenEnabled, "fullscreenEnabled should reflect feature policy properly");
+ }, header_policy + ' -- test allowlist is []');
+
+ // Test that fullscreen is disallowed on all subframes.
+ test_disallowed_feature_for_subframe(
+ header_policy + ' -- test fullscreen is disallowed on same-origin subframe',
+ 'fullscreen',
+ same_origin_src);
+ test_disallowed_feature_for_subframe(
+ header_policy + ' -- test fullscreen is disallowed on cross-origin subframe',
+ 'fullscreen',
+ cross_origin_src);
+
+ // Dynamically update sub frame's container policy
+ var allow = "fullscreen 'src';"
+ test_disallowed_feature_for_subframe(
+ header_policy + ', iframe.allow = ' + allow + ' -- test fullscreen is disallowed on same-origin subframe',
+ 'fullscreen',
+ same_origin_src,
+ allow);
+ test_disallowed_feature_for_subframe(
+ header_policy + ', iframe.allow = ' + allow + ' -- test fullscreen is disallowed on cross-origin subframe',
+ 'fullscreen',
+ cross_origin_src,
+ allow);
+ </script>
+</body>
diff --git a/testing/web-platform/tests/feature-policy/feature-policy-header-policy-disallowed-for-all.https.sub.html.sub.headers b/testing/web-platform/tests/feature-policy/feature-policy-header-policy-disallowed-for-all.https.sub.html.sub.headers
new file mode 100644
index 0000000000..04d160ae10
--- /dev/null
+++ b/testing/web-platform/tests/feature-policy/feature-policy-header-policy-disallowed-for-all.https.sub.html.sub.headers
@@ -0,0 +1,2 @@
+Feature-Policy: fullscreen 'none';
+
diff --git a/testing/web-platform/tests/feature-policy/feature-policy-nested-header-policy-allowed-for-all.https.sub.html b/testing/web-platform/tests/feature-policy/feature-policy-nested-header-policy-allowed-for-all.https.sub.html
new file mode 100644
index 0000000000..184cd01342
--- /dev/null
+++ b/testing/web-platform/tests/feature-policy/feature-policy-nested-header-policy-allowed-for-all.https.sub.html
@@ -0,0 +1,66 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta name="timeout" content="long">
+</head>
+<body>
+ <script src=/resources/testharness.js></script>
+ <script src=/resources/testharnessreport.js></script>
+ <script src=/feature-policy/resources/featurepolicy.js></script>
+ <script>
+ /*
+ fullscreen is allowed for all at the top-level document. It can be disabled by
+ subframes.
+ */
+ 'use strict';
+ const same_origin = 'https://{{domains[]}}:{{ports[https][0]}}';
+ const cross_origin = 'https://{{domains[www]}}:{{ports[https][0]}}';
+ const same_origin_src = '/feature-policy/resources/feature-policy-nested-subframe-policy.https.sub.html';
+ const cross_origin_src = cross_origin + same_origin_src;
+
+ /* ------------------------------------------
+ | top-level document |
+ | ------------------------------------ |
+ | | same-origin iframe | |
+ | | ------------------------------ | |
+ | | | local and remote iframes | | |
+ | | ------------------------------ | |
+ | ------------------------------------ |
+ ------------------------------------------ */
+ test_subframe_header_policy('fullscreen', '*', same_origin_src,
+ {local_all: true, local_self: true, local_none: false,
+ remote_all: false, remote_self: false, remote_none: false},
+ 'Test nested header policy with local iframe on policy "fullscreen *"');
+ test_subframe_header_policy('fullscreen', '\'self\'', same_origin_src,
+ {local_all: true, local_self: true, local_none: false,
+ remote_all: false, remote_self: false, remote_none: false},
+ 'Test nested header policy with local iframe on policy "fullscreen \'self\'"');
+ test_subframe_header_policy('fullscreen', '\'none\'', same_origin_src,
+ {local_all: false, local_self: false, local_none: false,
+ remote_all: false, remote_self: false, remote_none: false},
+ 'Test nested header policy with local iframe on policy "fullscreen \'none\'"');
+
+ /* -------------------------------------------
+ | top-level document |
+ | ------------------------------------- |
+ | | cross-origin iframe | |
+ | | ------------------------------- | |
+ | | | local and remote iframes | | |
+ | | ------------------------------- | |
+ | ------------------------------------- |
+ ------------------------------------------- */
+ test_subframe_header_policy('fullscreen', '*', cross_origin_src,
+ {local_all: false, local_self: false, local_none: false,
+ remote_all: false, remote_self: false, remote_none: false},
+ 'Test nested header policy with remote iframe on policy "fullscreen *"');
+ test_subframe_header_policy('fullscreen', '\'self\'', cross_origin_src,
+ {local_all: false, local_self: false, local_none: false,
+ remote_all: false, remote_self: false, remote_none: false},
+ 'Test nested header policy with remote iframe on policy "fullscreen \'self\'"');
+ test_subframe_header_policy('fullscreen', '\'none\'', cross_origin_src,
+ {local_all: false, local_self: false, local_none: false,
+ remote_all: false, remote_self: false, remote_none: false},
+ 'Test nested header policy with remote iframe on policy "fullscreen \'none\'"');
+ </script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/feature-policy/feature-policy-nested-header-policy-allowed-for-all.https.sub.html.sub.headers b/testing/web-platform/tests/feature-policy/feature-policy-nested-header-policy-allowed-for-all.https.sub.html.sub.headers
new file mode 100644
index 0000000000..111121a52f
--- /dev/null
+++ b/testing/web-platform/tests/feature-policy/feature-policy-nested-header-policy-allowed-for-all.https.sub.html.sub.headers
@@ -0,0 +1 @@
+Feature-Policy: fullscreen *;
diff --git a/testing/web-platform/tests/feature-policy/feature-policy-nested-header-policy-allowed-for-self.https.sub.html b/testing/web-platform/tests/feature-policy/feature-policy-nested-header-policy-allowed-for-self.https.sub.html
new file mode 100644
index 0000000000..fe857c916c
--- /dev/null
+++ b/testing/web-platform/tests/feature-policy/feature-policy-nested-header-policy-allowed-for-self.https.sub.html
@@ -0,0 +1,63 @@
+<!DOCTYPE html>
+<meta name="timeout" content="long">
+<body>
+ <script src=/resources/testharness.js></script>
+ <script src=/resources/testharnessreport.js></script>
+ <script src=/feature-policy/resources/featurepolicy.js></script>
+ <script>
+ /*
+ fullscreen is allowed for 'self' at the top-level document and through the
+ chain of same-origin iframes. It can be enabled by subframes, but otherwise
+ is disallowed everywhere else.
+ */
+ 'use strict';
+ const same_origin = 'https://{{domains[]}}:{{ports[https][0]}}';
+ const cross_origin = 'https://{{domains[www]}}:{{ports[https][0]}}';
+ const same_origin_src = '/feature-policy/resources/feature-policy-nested-subframe-policy.https.sub.html';
+ const cross_origin_src = cross_origin + same_origin_src;
+
+ /* ------------------------------------------
+ | top-level document |
+ | ------------------------------------ |
+ | | same-origin iframe | |
+ | | ------------------------------ | |
+ | | | local and remote iframes | | |
+ | | ------------------------------ | |
+ | ------------------------------------ |
+ ------------------------------------------ */
+ test_subframe_header_policy('fullscreen', '*', same_origin_src,
+ {local_all: true, local_self: true, local_none: false,
+ remote_all: false, remote_self: false, remote_none: false},
+ 'Test nested header policy with local iframe on policy "fullscreen *"');
+ test_subframe_header_policy('fullscreen', '\'self\'', same_origin_src,
+ {local_all: true, local_self: true, local_none: false,
+ remote_all: false, remote_self: false, remote_none: false},
+ 'Test nested header policy with local iframe on policy "fullscreen \'self\'"');
+ test_subframe_header_policy('fullscreen', '\'none\'', same_origin_src,
+ {local_all: false, local_self: false, local_none: false,
+ remote_all: false, remote_self: false, remote_none: false},
+ 'Test nested header policy with local iframe on policy "fullscreen \'none\'"');
+
+ /* -------------------------------------------
+ | top-level document |
+ | ------------------------------------- |
+ | | cross-origin iframe | |
+ | | ------------------------------- | |
+ | | | local and remote iframes | | |
+ | | ------------------------------- | |
+ | ------------------------------------- |
+ ------------------------------------------- */
+ test_subframe_header_policy('fullscreen', '*', cross_origin_src,
+ {local_all: false, local_self: false, local_none: false,
+ remote_all: false, remote_self: false, remote_none: false},
+ 'Test nested header policy with remote iframe on policy "fullscreen *"');
+ test_subframe_header_policy('fullscreen', '\'self\'', cross_origin_src,
+ {local_all: false, local_self: false, local_none: false,
+ remote_all: false, remote_self: false, remote_none: false},
+ 'Test nested header policy with remote iframe on policy "fullscreen \'self\'"');
+ test_subframe_header_policy('fullscreen', '\'none\'', cross_origin_src,
+ {local_all: false, local_self: false, local_none: false,
+ remote_all: false, remote_self: false, remote_none: false},
+ 'Test nested header policy with remote iframe on policy "fullscreen \'none\'"');
+ </script>
+</body>
diff --git a/testing/web-platform/tests/feature-policy/feature-policy-nested-header-policy-allowed-for-self.https.sub.html.sub.headers b/testing/web-platform/tests/feature-policy/feature-policy-nested-header-policy-allowed-for-self.https.sub.html.sub.headers
new file mode 100644
index 0000000000..0cc259b24f
--- /dev/null
+++ b/testing/web-platform/tests/feature-policy/feature-policy-nested-header-policy-allowed-for-self.https.sub.html.sub.headers
@@ -0,0 +1 @@
+Feature-Policy: fullscreen 'self';
diff --git a/testing/web-platform/tests/feature-policy/feature-policy-nested-header-policy-disallowed-for-all.https.sub.html b/testing/web-platform/tests/feature-policy/feature-policy-nested-header-policy-disallowed-for-all.https.sub.html
new file mode 100644
index 0000000000..6194a33b1d
--- /dev/null
+++ b/testing/web-platform/tests/feature-policy/feature-policy-nested-header-policy-disallowed-for-all.https.sub.html
@@ -0,0 +1,51 @@
+<!DOCTYPE html>
+<meta name="timeout" content="long">
+<body>
+ <script src=/resources/testharness.js></script>
+ <script src=/resources/testharnessreport.js></script>
+ <script src=/feature-policy/resources/featurepolicy.js></script>
+ <script>
+ /*
+ fullscreen is disabled at the top-level document, therefore disabled
+ everywhere throughout inheritance.
+ */
+ 'use strict';
+ const same_origin = 'https://{{domains[]}}:{{ports[https][0]}}';
+ const cross_origin = 'https://{{domains[www]}}:{{ports[https][0]}}';
+ const same_origin_src = '/feature-policy/resources/feature-policy-nested-subframe-policy.https.sub.html';
+ const cross_origin_src = cross_origin + same_origin_src;
+ const policies = ['*', '\'self\'', '\'none\''];
+
+ for (var i = 0; i < policies.length; i++) {
+ /* ------------------------------------------
+ | top-level document |
+ | ------------------------------------ |
+ | | same-origin iframe | |
+ | | ------------------------------ | |
+ | | | local and remote iframes | | |
+ | | ------------------------------ | |
+ | ------------------------------------ |
+ ------------------------------------------ */
+ test_subframe_header_policy('fullscreen', policies[i], same_origin_src,
+ {local_all: false, local_self: false, local_none: false,
+ remote_all: false, remote_self: false, remote_none: false},
+ 'Test nested header policy with local iframe on policy "fullscreen '
+ + policies[i] + '".');
+
+ /* -------------------------------------------
+ | top-level document |
+ | ------------------------------------- |
+ | | cross-origin iframe | |
+ | | ------------------------------- | |
+ | | | local and remote iframes | | |
+ | | ------------------------------- | |
+ | ------------------------------------- |
+ ------------------------------------------- */
+ test_subframe_header_policy('fullscreen', policies[i], cross_origin_src,
+ {local_all: false, local_self: false, local_none: false,
+ remote_all: false, remote_self: false, remote_none: false},
+ 'Test nested header policy with remote iframe on policy "fullscreen '
+ + policies[i] + '".');
+}
+ </script>
+</body>
diff --git a/testing/web-platform/tests/feature-policy/feature-policy-nested-header-policy-disallowed-for-all.https.sub.html.sub.headers b/testing/web-platform/tests/feature-policy/feature-policy-nested-header-policy-disallowed-for-all.https.sub.html.sub.headers
new file mode 100644
index 0000000000..961d40336a
--- /dev/null
+++ b/testing/web-platform/tests/feature-policy/feature-policy-nested-header-policy-disallowed-for-all.https.sub.html.sub.headers
@@ -0,0 +1 @@
+Feature-Policy: fullscreen 'none';
diff --git a/testing/web-platform/tests/feature-policy/payment-allowed-by-feature-policy-attribute-redirect-on-load.https.sub.html b/testing/web-platform/tests/feature-policy/payment-allowed-by-feature-policy-attribute-redirect-on-load.https.sub.html
new file mode 100644
index 0000000000..df53af2236
--- /dev/null
+++ b/testing/web-platform/tests/feature-policy/payment-allowed-by-feature-policy-attribute-redirect-on-load.https.sub.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<body>
+ <script src=/resources/testharness.js></script>
+ <script src=/resources/testharnessreport.js></script>
+ <script src=/feature-policy/resources/featurepolicy.js></script>
+ <script>
+ 'use strict';
+ var relative_path = '/feature-policy/resources/feature-policy-payment.html';
+ var base_src = '/feature-policy/resources/redirect-on-load.html#';
+ var same_origin_src = base_src + relative_path;
+ var cross_origin_src = base_src + 'https://{{domains[www]}}:{{ports[https][0]}}' +
+ relative_path;
+ var header = 'Feature-Policy allow="payment"';
+
+ async_test(t => {
+ test_feature_availability(
+ 'PaymentRequest()', t, same_origin_src,
+ expect_feature_available_default, 'payment');
+ }, header + ' allows same-origin navigation in an iframe.');
+
+ async_test(t => {
+ test_feature_availability(
+ 'PaymentRequest()', t, cross_origin_src,
+ expect_feature_unavailable_default, 'payment');
+ }, header + ' disallows cross-origin navigation in an iframe.');
+
+ </script>
+</body>
diff --git a/testing/web-platform/tests/feature-policy/payment-allowed-by-feature-policy-attribute.https.sub.html b/testing/web-platform/tests/feature-policy/payment-allowed-by-feature-policy-attribute.https.sub.html
new file mode 100644
index 0000000000..eeebe399ac
--- /dev/null
+++ b/testing/web-platform/tests/feature-policy/payment-allowed-by-feature-policy-attribute.https.sub.html
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<body>
+ <script src=/resources/testharness.js></script>
+ <script src=/resources/testharnessreport.js></script>
+ <script src=/feature-policy/resources/featurepolicy.js></script>
+ <script>
+ 'use strict';
+ var same_origin_src = '/feature-policy/resources/feature-policy-payment.html';
+ var cross_origin_src = 'https://{{domains[www]}}:{{ports[https][0]}}' +
+ same_origin_src;
+ var feature_name = 'Feature policy "payment"';
+ var header = 'allow="payment" attribute';
+
+ async_test(t => {
+ test_feature_availability(
+ 'PaymentRequest()', t, same_origin_src,
+ expect_feature_available_default, 'payment');
+ }, feature_name + ' can be enabled in same-origin iframe using ' + header);
+
+ async_test(t => {
+ test_feature_availability(
+ 'PaymentRequest()', t, cross_origin_src,
+ expect_feature_available_default, 'payment');
+ }, feature_name + ' can be enabled in cross-origin iframe using ' + header);
+ </script>
+</body>
diff --git a/testing/web-platform/tests/feature-policy/payment-allowed-by-feature-policy.https.sub.html b/testing/web-platform/tests/feature-policy/payment-allowed-by-feature-policy.https.sub.html
new file mode 100644
index 0000000000..b69fa62c05
--- /dev/null
+++ b/testing/web-platform/tests/feature-policy/payment-allowed-by-feature-policy.https.sub.html
@@ -0,0 +1,41 @@
+<!DOCTYPE html>
+<body>
+ <script src=/resources/testharness.js></script>
+ <script src=/resources/testharnessreport.js></script>
+ <script src=/feature-policy/resources/featurepolicy.js></script>
+ <script>
+ 'use strict';
+ var same_origin_src = '/feature-policy/resources/feature-policy-payment.html';
+ var cross_origin_src = 'https://{{domains[www]}}:{{ports[https][0]}}' +
+ same_origin_src;
+ var header = 'Feature-Policy header {"payment" : ["*"]}';
+
+ test(() => {
+ var supportedMethods = [ { supportedMethods: 'https://{{domains[nonexistent]}}/payment-request' } ];
+ var details = {
+ total: { label: 'Test', amount: { currency: 'USD', value: '5.00' } }
+ };
+ try {
+ new PaymentRequest(supportedMethods, details);
+ } catch (e) {
+ assert_unreached();
+ }
+ }, header + ' allows the top-level document.');
+
+ async_test(t => {
+ test_feature_availability('PaymentRequest()', t, same_origin_src,
+ expect_feature_available_default);
+ }, header + ' allows same-origin iframes.');
+
+ async_test(t => {
+ test_feature_availability('PaymentRequest()', t, cross_origin_src,
+ expect_feature_unavailable_default);
+ }, header + ' disallows cross-origin iframes.');
+
+ async_test(t => {
+ test_feature_availability('PaymentRequest()', t, cross_origin_src,
+ expect_feature_available_default, 'payment');
+ }, header + ' allow="payment" allows cross-origin iframes.');
+
+ </script>
+</body>
diff --git a/testing/web-platform/tests/feature-policy/payment-allowed-by-feature-policy.https.sub.html.headers b/testing/web-platform/tests/feature-policy/payment-allowed-by-feature-policy.https.sub.html.headers
new file mode 100644
index 0000000000..a5c010bd68
--- /dev/null
+++ b/testing/web-platform/tests/feature-policy/payment-allowed-by-feature-policy.https.sub.html.headers
@@ -0,0 +1 @@
+Feature-Policy: payment *
diff --git a/testing/web-platform/tests/feature-policy/payment-default-feature-policy.https.sub.html b/testing/web-platform/tests/feature-policy/payment-default-feature-policy.https.sub.html
new file mode 100644
index 0000000000..f73ac82ab7
--- /dev/null
+++ b/testing/web-platform/tests/feature-policy/payment-default-feature-policy.https.sub.html
@@ -0,0 +1,36 @@
+<!DOCTYPE html>
+<body>
+ <script src=/resources/testharness.js></script>
+ <script src=/resources/testharnessreport.js></script>
+ <script src=/feature-policy/resources/featurepolicy.js></script>
+ <script>
+ 'use strict';
+ var same_origin_src = '/feature-policy/resources/feature-policy-payment.html';
+ var cross_origin_src = 'https://{{domains[www]}}:{{ports[https][0]}}' +
+ same_origin_src;
+ var header = 'Default "payment" feature policy ["self"]';
+
+ test(() => {
+ var supportedInstruments = [ { supportedMethods: 'visa' } ];
+ var details = {
+ total: { label: 'Test', amount: { currency: 'USD', value: '5.00' } }
+ };
+ try {
+ new PaymentRequest(supportedInstruments, details);
+ } catch (e) {
+ assert_unreached();
+ }
+ }, header + ' allows the top-level document.');
+
+ async_test(t => {
+ test_feature_availability('PaymentRequest()', t, same_origin_src,
+ expect_feature_available_default);
+ }, header + ' allows same-origin iframes.');
+
+ async_test(t => {
+ test_feature_availability('PaymentRequest()', t, cross_origin_src,
+ expect_feature_unavailable_default);
+ }, header + ' disallows cross-origin iframes.');
+
+ </script>
+</body>
diff --git a/testing/web-platform/tests/feature-policy/payment-disabled-by-feature-policy.https.sub.html b/testing/web-platform/tests/feature-policy/payment-disabled-by-feature-policy.https.sub.html
new file mode 100644
index 0000000000..0e479fd2a3
--- /dev/null
+++ b/testing/web-platform/tests/feature-policy/payment-disabled-by-feature-policy.https.sub.html
@@ -0,0 +1,34 @@
+<!DOCTYPE html>
+<body>
+ <script src=/resources/testharness.js></script>
+ <script src=/resources/testharnessreport.js></script>
+ <script src=/feature-policy/resources/featurepolicy.js></script>
+ <script>
+ 'use strict';
+ var same_origin_src = '/feature-policy/resources/feature-policy-payment.html';
+ var cross_origin_src = 'https://{{domains[www]}}:{{ports[https][0]}}' +
+ same_origin_src;
+ var header = 'Feature-Policy header {"payment" : []}';
+
+ test(() => {
+ var supportedInstruments = [ { supportedMethods: 'visa' } ];
+ var details = {
+ total: { label: 'Test', amount: { currency: 'USD', value: '5.00' } }
+ };
+ assert_throws_dom('SecurityError', () => {
+ new PaymentRequest(supportedInstruments, details);
+ });
+ }, header + ' disallows the top-level document.');
+
+ async_test(t => {
+ test_feature_availability('PaymentRequest()', t, same_origin_src,
+ expect_feature_unavailable_default);
+ }, header + ' disallows same-origin iframes.');
+
+ async_test(t => {
+ test_feature_availability('PaymentRequest()', t, cross_origin_src,
+ expect_feature_unavailable_default,);
+ }, header + ' disallows cross-origin iframes.');
+
+ </script>
+</body>
diff --git a/testing/web-platform/tests/feature-policy/payment-disabled-by-feature-policy.https.sub.html.headers b/testing/web-platform/tests/feature-policy/payment-disabled-by-feature-policy.https.sub.html.headers
new file mode 100644
index 0000000000..a2836778bc
--- /dev/null
+++ b/testing/web-platform/tests/feature-policy/payment-disabled-by-feature-policy.https.sub.html.headers
@@ -0,0 +1 @@
+Feature-Policy: payment 'none'
diff --git a/testing/web-platform/tests/feature-policy/payment-supported-by-feature-policy.tentative.html b/testing/web-platform/tests/feature-policy/payment-supported-by-feature-policy.tentative.html
new file mode 100644
index 0000000000..07dec70176
--- /dev/null
+++ b/testing/web-platform/tests/feature-policy/payment-supported-by-feature-policy.tentative.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<title>Test that payment is advertised in the feature list</title>
+<link rel="help" href="https://w3c.github.io/webappsec-feature-policy/#dom-featurepolicy-features">
+<link rel="help" href="https://github.com/w3c/payment-request/issues/600">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+test(() => {
+ assert_in_array('payment', document.featurePolicy.features());
+}, 'document.featurePolicy.features should advertise payment.');
+</script>
diff --git a/testing/web-platform/tests/feature-policy/permissions-policy-feature-policy-coexist.https.html b/testing/web-platform/tests/feature-policy/permissions-policy-feature-policy-coexist.https.html
new file mode 100644
index 0000000000..72836b0d1f
--- /dev/null
+++ b/testing/web-platform/tests/feature-policy/permissions-policy-feature-policy-coexist.https.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<script src=/resources/testharness.js> </script>
+<script src=/resources/testharnessreport.js> </script>
+<div>
+ This is a page with following headers: <br>
+ <div>
+ Feature-Policy: geolocation 'none', fullscreen 'none'<br>
+ Permissions-Policy: geolocation=self, payment=()
+ </div>
+</div>
+<script>
+ const policy = document.featurePolicy;
+ test(() => {
+ assert_true(policy.allowsFeature('geolocation'));
+ }, "When there is conflict in Feature Policy header and Permissions Policy" +
+ "header, Permissions Policy wins.");
+
+ test(() => {
+ assert_false(policy.allowsFeature('fullscreen'));
+ assert_false(policy.allowsFeature('payment'));
+ }, "When there is no conflict, Feature Policy and Permissions Policy should " +
+ "all be able to control each feature by themselves.");
+</script>
+
diff --git a/testing/web-platform/tests/feature-policy/permissions-policy-feature-policy-coexist.https.html.headers b/testing/web-platform/tests/feature-policy/permissions-policy-feature-policy-coexist.https.html.headers
new file mode 100644
index 0000000000..100e68e9ad
--- /dev/null
+++ b/testing/web-platform/tests/feature-policy/permissions-policy-feature-policy-coexist.https.html.headers
@@ -0,0 +1,2 @@
+Feature-Policy: geolocation 'none', fullscreen 'none'
+Permissions-Policy: geolocation=self, payment=()
diff --git a/testing/web-platform/tests/feature-policy/permissions-policy-header-policy-allowed-for-self.https.sub.html b/testing/web-platform/tests/feature-policy/permissions-policy-header-policy-allowed-for-self.https.sub.html
new file mode 100644
index 0000000000..32df2b71c3
--- /dev/null
+++ b/testing/web-platform/tests/feature-policy/permissions-policy-header-policy-allowed-for-self.https.sub.html
@@ -0,0 +1,45 @@
+<!DOCTYPE html>
+<body>
+ <script src=/resources/testharness.js></script>
+ <script src=/resources/testharnessreport.js></script>
+ <script src=/feature-policy/resources/featurepolicy.js></script>
+ <!-- Permissions-Policy: fullscreen=self -->
+ <script>
+ 'use strict';
+ var same_origin = 'https://{{domains[]}}:{{ports[https][0]}}';
+ var cross_origin = 'https://{{domains[www]}}:{{ports[https][0]}}';
+ var same_origin_src = '/feature-policy/resources/feature-policy-allowedfeatures.html';
+ var cross_origin_src = cross_origin + same_origin_src;
+ var header_policy = 'Permissions-Policy: fullscreen=self';
+
+ // Test that fullscreen's allowlist is ['same_origin']
+ test(function() {
+ assert_array_equals(
+ document.featurePolicy.getAllowlistForFeature('fullscreen'),
+ [same_origin]);
+ }, header_policy + ' -- test allowlist is [same_origin]');
+
+ // Test that fullscreen is only allowed on same-origin subframe.
+ test_allowed_feature_for_subframe(
+ header_policy + ' -- test fullscreen is allowed on same-origin subframe',
+ 'fullscreen',
+ same_origin_src);
+ test_disallowed_feature_for_subframe(
+ header_policy + ' -- test fullscreen is disallowed on cross-origin subframe',
+ 'fullscreen',
+ cross_origin_src);
+
+ // Dynamically update sub frame's container policy
+ var allow = "fullscreen 'src';"
+ test_allowed_feature_for_subframe(
+ header_policy + ', iframe.allow = ' + allow + ' -- test fullscreen is allowed on same-origin subframe',
+ 'fullscreen',
+ same_origin_src,
+ allow);
+ test_disallowed_feature_for_subframe(
+ header_policy + ', iframe.allow = ' + allow + ' -- test fullscreen is disallowed on cross-origin subframe',
+ 'fullscreen',
+ cross_origin_src,
+ allow);
+ </script>
+</body>
diff --git a/testing/web-platform/tests/feature-policy/permissions-policy-header-policy-allowed-for-self.https.sub.html.sub.headers b/testing/web-platform/tests/feature-policy/permissions-policy-header-policy-allowed-for-self.https.sub.html.sub.headers
new file mode 100644
index 0000000000..ff7ae41353
--- /dev/null
+++ b/testing/web-platform/tests/feature-policy/permissions-policy-header-policy-allowed-for-self.https.sub.html.sub.headers
@@ -0,0 +1 @@
+Permissions-Policy: fullscreen=self
diff --git a/testing/web-platform/tests/feature-policy/picture-in-picture-allowed-by-feature-policy-attribute-redirect-on-load.https.sub.html b/testing/web-platform/tests/feature-policy/picture-in-picture-allowed-by-feature-policy-attribute-redirect-on-load.https.sub.html
new file mode 100644
index 0000000000..a5ea5139a9
--- /dev/null
+++ b/testing/web-platform/tests/feature-policy/picture-in-picture-allowed-by-feature-policy-attribute-redirect-on-load.https.sub.html
@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<body>
+ <script src=/common/media.js></script>
+ <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=/feature-policy/resources/featurepolicy.js></script>
+ <script src=/feature-policy/resources/picture-in-picture.js></script>
+ <script>
+ 'use strict';
+ const relative_path = '/feature-policy/resources/feature-policy-picture-in-picture.html';
+ const base_src = '/feature-policy/resources/redirect-on-load.html#';
+ const same_origin_src = base_src + relative_path;
+ const cross_origin_src = base_src + 'https://{{domains[www]}}:{{ports[https][0]}}' +
+ relative_path;
+ const header = 'Feature-Policy allow="picture-in-picture"';
+
+ async_pip_test(t => {
+ test_feature_availability(
+ 'picture-in-picture', t, same_origin_src,
+ expect_feature_available_default, 'picture-in-picture');
+ }, header + ' allows same-origin navigation in an iframe.');
+
+ async_pip_test(t => {
+ test_feature_availability(
+ 'picture-in-picture', t, cross_origin_src,
+ expect_feature_unavailable_default, 'picture-in-picture');
+ }, header + ' disallows cross-origin navigation in an iframe.');
+ </script>
+</body>
diff --git a/testing/web-platform/tests/feature-policy/picture-in-picture-allowed-by-feature-policy-attribute.https.sub.html b/testing/web-platform/tests/feature-policy/picture-in-picture-allowed-by-feature-policy-attribute.https.sub.html
new file mode 100644
index 0000000000..b0e160e938
--- /dev/null
+++ b/testing/web-platform/tests/feature-policy/picture-in-picture-allowed-by-feature-policy-attribute.https.sub.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<body>
+ <script src=/common/media.js></script>
+ <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=/feature-policy/resources/featurepolicy.js></script>
+ <script src=/feature-policy/resources/picture-in-picture.js></script>
+ <script>
+ 'use strict';
+ const same_origin_src = '/feature-policy/resources/feature-policy-picture-in-picture.html';
+ const cross_origin_src = 'https://{{domains[www]}}:{{ports[https][0]}}' +
+ same_origin_src;
+ const feature_name = 'Feature policy "picture-in-picture"';
+ const header = 'allow="picture-in-picture" attribute';
+
+ async_pip_test(t => {
+ test_feature_availability(
+ 'picture-in-picture', t, same_origin_src,
+ expect_feature_available_default, 'picture-in-picture');
+ }, feature_name + ' can be enabled in same-origin iframe using ' + header);
+
+ async_pip_test(t => {
+ test_feature_availability(
+ 'picture-in-picture', t, cross_origin_src,
+ expect_feature_available_default, 'picture-in-picture');
+ }, feature_name + ' can be enabled in cross-origin iframe using ' + header);
+ </script>
+</body>
diff --git a/testing/web-platform/tests/feature-policy/picture-in-picture-allowed-by-feature-policy.https.sub.html b/testing/web-platform/tests/feature-policy/picture-in-picture-allowed-by-feature-policy.https.sub.html
new file mode 100644
index 0000000000..b09335a6de
--- /dev/null
+++ b/testing/web-platform/tests/feature-policy/picture-in-picture-allowed-by-feature-policy.https.sub.html
@@ -0,0 +1,33 @@
+<!DOCTYPE html>
+<body>
+ <script src=/common/media.js></script>
+ <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=/feature-policy/resources/featurepolicy.js></script>
+ <script src=/feature-policy/resources/picture-in-picture.js></script>
+ <script>
+ 'use strict';
+ const same_origin_src = '/feature-policy/resources/feature-policy-picture-in-picture.html';
+ const cross_origin_src = 'https://{{domains[www]}}:{{ports[https][0]}}' +
+ same_origin_src;
+ const header = 'Feature-Policy header: picture-in-picture *';
+
+ async_pip_test(t => {
+ isPictureInPictureAllowed().then(t.step_func_done((result) => {
+ assert_true(result);
+ }));
+ }, header + ' allows the top-level document.');
+
+ async_pip_test(t => {
+ test_feature_availability('picture-in-picture', t, same_origin_src,
+ expect_feature_available_default);
+ }, header + ' allows same-origin iframes.');
+
+ async_pip_test(t => {
+ test_feature_availability('picture-in-picture', t, cross_origin_src,
+ expect_feature_available_default);
+ }, header + ' allows cross-origin iframes.');
+ </script>
+</body>
diff --git a/testing/web-platform/tests/feature-policy/picture-in-picture-allowed-by-feature-policy.https.sub.html.headers b/testing/web-platform/tests/feature-policy/picture-in-picture-allowed-by-feature-policy.https.sub.html.headers
new file mode 100644
index 0000000000..0204b73b18
--- /dev/null
+++ b/testing/web-platform/tests/feature-policy/picture-in-picture-allowed-by-feature-policy.https.sub.html.headers
@@ -0,0 +1 @@
+Feature-Policy: picture-in-picture *
diff --git a/testing/web-platform/tests/feature-policy/picture-in-picture-default-feature-policy.https.sub.html b/testing/web-platform/tests/feature-policy/picture-in-picture-default-feature-policy.https.sub.html
new file mode 100644
index 0000000000..477cf7ba51
--- /dev/null
+++ b/testing/web-platform/tests/feature-policy/picture-in-picture-default-feature-policy.https.sub.html
@@ -0,0 +1,34 @@
+<!DOCTYPE html>
+<body>
+ <script src=/common/media.js></script>
+ <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=/feature-policy/resources/featurepolicy.js></script>
+ <script src=/feature-policy/resources/picture-in-picture.js></script>
+ <script>
+ 'use strict';
+ const same_origin_src = '/feature-policy/resources/feature-policy-picture-in-picture.html';
+ const cross_origin_src = 'https://{{domains[www]}}:{{ports[https][0]}}' +
+ same_origin_src;
+ const header = 'Default "picture-in-picture" feature policy [*]';
+
+ async_pip_test(t => {
+ isPictureInPictureAllowed().then(t.step_func_done((result) => {
+ assert_true(result);
+ }));
+ }, header + ' allows the top-level document.');
+
+ async_pip_test(t => {
+ test_feature_availability('picture-in-picture', t, same_origin_src,
+ expect_feature_available_default);
+ }, header + ' allows same-origin iframes.');
+
+ async_pip_test(t => {
+ test_feature_availability('picture-in-picture', t, cross_origin_src,
+ expect_feature_available_default);
+ }, header + ' allows cross-origin iframes.');
+
+ </script>
+</body>
diff --git a/testing/web-platform/tests/feature-policy/picture-in-picture-disabled-by-feature-policy.https.sub.html b/testing/web-platform/tests/feature-policy/picture-in-picture-disabled-by-feature-policy.https.sub.html
new file mode 100644
index 0000000000..513d04c2fa
--- /dev/null
+++ b/testing/web-platform/tests/feature-policy/picture-in-picture-disabled-by-feature-policy.https.sub.html
@@ -0,0 +1,33 @@
+<!DOCTYPE html>
+<body>
+ <script src=/common/media.js></script>
+ <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=/feature-policy/resources/featurepolicy.js></script>
+ <script src=/feature-policy/resources/picture-in-picture.js></script>
+ <script>
+ 'use strict';
+ const same_origin_src = '/feature-policy/resources/feature-policy-picture-in-picture.html';
+ const cross_origin_src = 'https://{{domains[www]}}:{{ports[https][0]}}' +
+ same_origin_src;
+ const header = 'Feature-Policy header: picture-in-picture "none"';
+
+ async_pip_test(t => {
+ isPictureInPictureAllowed().then(t.step_func_done((result) => {
+ assert_false(result);
+ }));
+ }, header + ' disallows the top-level document.');
+
+ async_pip_test(t => {
+ test_feature_availability('picture-in-picture', t, same_origin_src,
+ expect_feature_unavailable_default);
+ }, header + ' disallows same-origin iframes.');
+
+ async_pip_test(t => {
+ test_feature_availability('picture-in-picture', t, cross_origin_src,
+ expect_feature_unavailable_default);
+ }, header + ' disallows cross-origin iframes.');
+ </script>
+</body>
diff --git a/testing/web-platform/tests/feature-policy/picture-in-picture-disabled-by-feature-policy.https.sub.html.headers b/testing/web-platform/tests/feature-policy/picture-in-picture-disabled-by-feature-policy.https.sub.html.headers
new file mode 100644
index 0000000000..1759381fdc
--- /dev/null
+++ b/testing/web-platform/tests/feature-policy/picture-in-picture-disabled-by-feature-policy.https.sub.html.headers
@@ -0,0 +1 @@
+Feature-Policy: picture-in-picture 'none'
diff --git a/testing/web-platform/tests/feature-policy/picture-in-picture-supported-by-feature-policy.html b/testing/web-platform/tests/feature-policy/picture-in-picture-supported-by-feature-policy.html
new file mode 100644
index 0000000000..a65c682a6a
--- /dev/null
+++ b/testing/web-platform/tests/feature-policy/picture-in-picture-supported-by-feature-policy.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<title>Test that picture-in-picture is advertised in the feature list</title>
+<link rel="help" href="https://w3c.github.io/webappsec-feature-policy/#dom-featurepolicy-features">
+<link rel="help" href="https://wicg.github.io/picture-in-picture/#feature-policy">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+test(() => {
+ assert_in_array('picture-in-picture', document.featurePolicy.features());
+}, 'document.featurePolicy.features should advertise picture-in-picture.');
+</script>
diff --git a/testing/web-platform/tests/feature-policy/policy-extends-to-sandbox.html b/testing/web-platform/tests/feature-policy/policy-extends-to-sandbox.html
new file mode 100644
index 0000000000..d45e64f49b
--- /dev/null
+++ b/testing/web-platform/tests/feature-policy/policy-extends-to-sandbox.html
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<title>Feature policy treats opaque origins correctly</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<body>
+<script>
+ "use strict";
+ async_test(t => {
+ let frame = document.createElement('iframe');
+ frame.src = "/feature-policy/resources/sandbox-self.html";
+ frame.allow = "fullscreen";
+ frame.sandbox = "allow-scripts";
+
+ var handle_message = t.step_func(evt => {
+ if (evt.source === frame.contentWindow) {
+ assert_equals(evt.data.child, true, "'self' in header should match origin of sandboxed frame.");
+ assert_equals(evt.data.grandchild, false, "Opaque origins should not match each other.");
+ document.body.removeChild(frame);
+ window.removeEventListener('message', handle_message);
+ t.done();
+ }
+ });
+ window.addEventListener('message', handle_message);
+ document.body.appendChild(frame);
+ });
+</script>
diff --git a/testing/web-platform/tests/feature-policy/reporting/camera-reporting.https.html b/testing/web-platform/tests/feature-policy/reporting/camera-reporting.https.html
new file mode 100644
index 0000000000..708d3fa525
--- /dev/null
+++ b/testing/web-platform/tests/feature-policy/reporting/camera-reporting.https.html
@@ -0,0 +1,34 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <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='../../mediacapture-streams/permission-helper.js'></script>
+ </head>
+ <body>
+ <script>
+var t = async_test("Camera Report Format");
+
+var check_report_format = (reports, observer) => {
+ let report = reports[0];
+ assert_equals(report.type, "permissions-policy-violation");
+ assert_equals(report.url, document.location.href);
+ assert_equals(report.body.featureId, "camera");
+ assert_equals(report.body.sourceFile, document.location.href);
+ assert_equals(typeof report.body.lineNumber, "number");
+ assert_equals(typeof report.body.columnNumber, "number");
+ assert_equals(report.body.disposition, "enforce");
+};
+
+new ReportingObserver(t.step_func_done(check_report_format),
+ {types: ['permissions-policy-violation']}).observe();
+
+ setMediaPermission("granted", ["camera"]).then(() => navigator.mediaDevices.getUserMedia({video: true}))
+ .then(
+ t.unreached_func("UserMedia camera access should not be allowed in this document.")
+).catch(() => {});
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/feature-policy/reporting/camera-reporting.https.html.headers b/testing/web-platform/tests/feature-policy/reporting/camera-reporting.https.html.headers
new file mode 100644
index 0000000000..2adc5e237f
--- /dev/null
+++ b/testing/web-platform/tests/feature-policy/reporting/camera-reporting.https.html.headers
@@ -0,0 +1 @@
+Feature-Policy: camera 'none'
diff --git a/testing/web-platform/tests/feature-policy/reporting/encrypted-media-reporting.https.html b/testing/web-platform/tests/feature-policy/reporting/encrypted-media-reporting.https.html
new file mode 100644
index 0000000000..32a5a2cc48
--- /dev/null
+++ b/testing/web-platform/tests/feature-policy/reporting/encrypted-media-reporting.https.html
@@ -0,0 +1,37 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <script src='/resources/testharness.js'></script>
+ <script src='/resources/testharnessreport.js'></script>
+ </head>
+ <body>
+ <script>
+var check_report_format = (reports, observer) => {
+ let report = reports[0];
+ assert_equals(report.type, "permissions-policy-violation");
+ assert_equals(report.url, document.location.href);
+ assert_equals(report.body.featureId, "encrypted-media");
+ assert_equals(report.body.disposition, "enforce");
+ assert_equals(report.body.sourceFile, document.location.href);
+ assert_equals(typeof report.body.lineNumber, "number");
+ assert_equals(typeof report.body.columnNumber, "number");
+};
+
+promise_test(async t => {
+ const report = new Promise(resolve => {
+ new ReportingObserver((reports, observer) => resolve([reports, observer]),
+ {types: ['permissions-policy-violation']}).observe();
+ });
+ await promise_rejects_dom(t, "SecurityError",
+ navigator.requestMediaKeySystemAccess("org.w3.clearkey",
+ [{
+ initDataTypes: ["webm"],
+ videoCapabilities: [{contentType: 'video/webm;codecs="vp8"'}],
+ }]),
+ "requestMediaKeySystemAccess() should not be allowed in this document.");
+ const [reports, observer] = await report;
+ check_report_format(reports, observer);
+}, "Encrypted Media report format");
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/feature-policy/reporting/encrypted-media-reporting.https.html.headers b/testing/web-platform/tests/feature-policy/reporting/encrypted-media-reporting.https.html.headers
new file mode 100644
index 0000000000..73753a2e41
--- /dev/null
+++ b/testing/web-platform/tests/feature-policy/reporting/encrypted-media-reporting.https.html.headers
@@ -0,0 +1 @@
+Feature-Policy: encrypted-media 'none'
diff --git a/testing/web-platform/tests/feature-policy/reporting/fullscreen-reporting.html b/testing/web-platform/tests/feature-policy/reporting/fullscreen-reporting.html
new file mode 100644
index 0000000000..d7b905744d
--- /dev/null
+++ b/testing/web-platform/tests/feature-policy/reporting/fullscreen-reporting.html
@@ -0,0 +1,45 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <script src='/resources/testharness.js'></script>
+ <script src='/resources/testharnessreport.js'></script>
+ </head>
+ <body>
+ <div id='fs'></div>
+ <script>
+var observer1;
+var observer2;
+
+var check_report_format = (reports, observer) => {
+ // Test that observer2 is notified, even if it is disconnected.
+ observer1.disconnect();
+ observer2.disconnect();
+ let report = reports[0];
+ assert_equals(report.type, "permissions-policy-violation");
+ assert_equals(report.url, document.location.href);
+ assert_equals(report.body.featureId, "fullscreen");
+ assert_equals(report.body.sourceFile, document.location.href);
+ assert_equals(typeof report.body.lineNumber, "number");
+ assert_equals(typeof report.body.columnNumber, "number");
+ assert_equals(report.body.disposition, "enforce");
+};
+
+var check_second_observer = (reports, observer) => {
+ let report = reports[0];
+ assert_equals(report.type, "permissions-policy-violation");
+ assert_equals(report.body.featureId, "fullscreen");
+};
+
+async_test(t => {
+ observer1 = new ReportingObserver(t.step_func(check_report_format),
+ {types: ['permissions-policy-violation']});
+ observer1.observe();
+ observer2 = new ReportingObserver(t.step_func_done(check_second_observer),
+ {types: ['permissions-policy-violation']});
+ observer2.observe();
+ document.getElementById('fs').requestFullscreen().then(t.unreached_func(
+ "Fullscreen should not be allowed in this document.")).catch(()=>{});
+}, "Fullscreen Report Format");
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/feature-policy/reporting/fullscreen-reporting.html.headers b/testing/web-platform/tests/feature-policy/reporting/fullscreen-reporting.html.headers
new file mode 100644
index 0000000000..d35e48ba40
--- /dev/null
+++ b/testing/web-platform/tests/feature-policy/reporting/fullscreen-reporting.html.headers
@@ -0,0 +1 @@
+Feature-Policy: fullscreen 'none'
diff --git a/testing/web-platform/tests/feature-policy/reporting/generic-sensor-reporting.https.html b/testing/web-platform/tests/feature-policy/reporting/generic-sensor-reporting.https.html
new file mode 100644
index 0000000000..c29c069ee3
--- /dev/null
+++ b/testing/web-platform/tests/feature-policy/reporting/generic-sensor-reporting.https.html
@@ -0,0 +1,55 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <script src='/resources/testharness.js'></script>
+ <script src='/resources/testharnessreport.js'></script>
+ </head>
+ <body>
+ <script>
+var sensor_features_verified = {
+ "accelerometer": false,
+ "ambient-light-sensor": false,
+ "magnetometer": false,
+ "gyroscope": false
+};
+
+var check_report_format = function(reports, observer) {
+ // Check each report in this batch. This observer callback may be called
+ // multiple times before all reports have been processed.
+ for (const report of reports) {
+
+ // Validate that the reported feature is one of the sensor features, and that
+ // we have not seen a report for this feature before.
+ assert_true(sensor_features_verified.hasOwnProperty(report.body.featureId));
+ assert_false(sensor_features_verified[report.body.featureId]);
+
+ // Validate the remainder of the report
+ assert_equals(report.type, "permissions-policy-violation");
+ assert_equals(report.url, document.location.href);
+ assert_equals(report.body.sourceFile, document.location.href);
+ assert_equals(typeof report.body.lineNumber, "number");
+ assert_equals(typeof report.body.columnNumber, "number");
+ assert_equals(report.body.disposition, "enforce");
+
+ sensor_features_verified[report.body.featureId] = true;
+ }
+
+ // Test is only done when reports for all features have been seen
+ for (let result of Object.values(sensor_features_verified)) {
+ if (!result)
+ return;
+ }
+ this.done();
+};
+
+async_test(t => {
+ new ReportingObserver(t.step_func(check_report_format),
+ {types: ['permissions-policy-violation']}).observe();
+ assert_throws_dom("SecurityError", () => new Accelerometer(), "Constructing sensors should be blocked by policy");
+ assert_throws_dom("SecurityError", () => new AmbientLightSensor(), "Constructing sensors should be blocked by policy");
+ assert_throws_dom("SecurityError", () => new Gyroscope(), "Constructing sensors should be blocked by policy");
+ assert_throws_dom("SecurityError", () => new Magnetometer(), "Constructing sensors should be blocked by policy");
+}, "Generic Sensor Report Format");
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/feature-policy/reporting/generic-sensor-reporting.https.html.headers b/testing/web-platform/tests/feature-policy/reporting/generic-sensor-reporting.https.html.headers
new file mode 100644
index 0000000000..80cc027530
--- /dev/null
+++ b/testing/web-platform/tests/feature-policy/reporting/generic-sensor-reporting.https.html.headers
@@ -0,0 +1 @@
+Feature-Policy: ambient-light-sensor 'none'; accelerometer 'none'; gyroscope 'none'; magnetometer 'none'
diff --git a/testing/web-platform/tests/feature-policy/reporting/geolocation-reporting.https.html b/testing/web-platform/tests/feature-policy/reporting/geolocation-reporting.https.html
new file mode 100644
index 0000000000..e0eb275bcc
--- /dev/null
+++ b/testing/web-platform/tests/feature-policy/reporting/geolocation-reporting.https.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <script src='/resources/testharness.js'></script>
+ <script src='/resources/testharnessreport.js'></script>
+ </head>
+ <body>
+ <script>
+var t = async_test("Geolocation Report Format");
+
+var check_report_format = (reports, observer) => {
+ let report = reports[0];
+ assert_equals(report.type, "permissions-policy-violation");
+ assert_equals(report.url, document.location.href);
+ assert_equals(report.body.featureId, "geolocation");
+ assert_equals(report.body.sourceFile, document.location.href);
+ assert_equals(typeof report.body.lineNumber, "number");
+ assert_equals(typeof report.body.columnNumber, "number");
+ assert_equals(report.body.disposition, "enforce");
+};
+
+new ReportingObserver(t.step_func_done(check_report_format),
+ {types: ['permissions-policy-violation']}).observe();
+
+navigator.geolocation.getCurrentPosition(
+ t.unreached_func("geolocation should be disabled in this document"),
+ () => {});
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/feature-policy/reporting/geolocation-reporting.https.html.headers b/testing/web-platform/tests/feature-policy/reporting/geolocation-reporting.https.html.headers
new file mode 100644
index 0000000000..7e75481ea6
--- /dev/null
+++ b/testing/web-platform/tests/feature-policy/reporting/geolocation-reporting.https.html.headers
@@ -0,0 +1 @@
+Feature-Policy: geolocation 'none'
diff --git a/testing/web-platform/tests/feature-policy/reporting/image.bmp b/testing/web-platform/tests/feature-policy/reporting/image.bmp
new file mode 100644
index 0000000000..f2b88690fc
--- /dev/null
+++ b/testing/web-platform/tests/feature-policy/reporting/image.bmp
Binary files differ
diff --git a/testing/web-platform/tests/feature-policy/reporting/microphone-reporting.https.html b/testing/web-platform/tests/feature-policy/reporting/microphone-reporting.https.html
new file mode 100644
index 0000000000..14ae976cde
--- /dev/null
+++ b/testing/web-platform/tests/feature-policy/reporting/microphone-reporting.https.html
@@ -0,0 +1,34 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <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='../../mediacapture-streams/permission-helper.js'></script>
+ </head>
+ <body>
+ <script>
+var t = async_test("Microphone Report Format");
+
+var check_report_format = (reports, observer) => {
+ let report = reports[0];
+ assert_equals(report.type, "permissions-policy-violation");
+ assert_equals(report.url, document.location.href);
+ assert_equals(report.body.featureId, "microphone");
+ assert_equals(report.body.sourceFile, document.location.href);
+ assert_equals(typeof report.body.lineNumber, "number");
+ assert_equals(typeof report.body.columnNumber, "number");
+ assert_equals(report.body.disposition, "enforce");
+};
+
+new ReportingObserver(t.step_func_done(check_report_format),
+ {types: ['permissions-policy-violation']}).observe();
+
+setMediaPermission().then(() => navigator.mediaDevices.getUserMedia({audio: true}))
+ .then(
+ t.unreached_func("UserMedia microphone access should not be allowed in this document.")
+).catch(() => {});
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/feature-policy/reporting/microphone-reporting.https.html.headers b/testing/web-platform/tests/feature-policy/reporting/microphone-reporting.https.html.headers
new file mode 100644
index 0000000000..a86e0a0778
--- /dev/null
+++ b/testing/web-platform/tests/feature-policy/reporting/microphone-reporting.https.html.headers
@@ -0,0 +1 @@
+Feature-Policy: microphone 'none'
diff --git a/testing/web-platform/tests/feature-policy/reporting/midi-reporting.https.html b/testing/web-platform/tests/feature-policy/reporting/midi-reporting.https.html
new file mode 100644
index 0000000000..6cc07f5371
--- /dev/null
+++ b/testing/web-platform/tests/feature-policy/reporting/midi-reporting.https.html
@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <script src='/resources/testharness.js'></script>
+ <script src='/resources/testharnessreport.js'></script>
+ </head>
+ <body>
+ <script>
+var check_report_format = (reports, observer) => {
+ let report = reports[0];
+ assert_equals(report.type, "permissions-policy-violation");
+ assert_equals(report.url, document.location.href);
+ assert_equals(report.body.featureId, "midi");
+ assert_equals(report.body.sourceFile, document.location.href);
+ assert_equals(typeof report.body.lineNumber, "number");
+ assert_equals(typeof report.body.columnNumber, "number");
+ assert_equals(report.body.disposition, "enforce");
+};
+
+promise_test(async (t) => {
+ const report = new Promise(resolve => {
+ new ReportingObserver((reports, observer) => resolve([reports, observer]),
+ {types: ['permissions-policy-violation']}).observe();
+ });
+ await promise_rejects_dom(t, 'SecurityError', navigator.requestMIDIAccess(),
+ "MIDI device access should not be allowed in this document.");
+ const [reports, observer] = await report;
+ check_report_format(reports, observer);
+}, "MIDI Report Format");
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/feature-policy/reporting/midi-reporting.https.html.headers b/testing/web-platform/tests/feature-policy/reporting/midi-reporting.https.html.headers
new file mode 100644
index 0000000000..0e145978a0
--- /dev/null
+++ b/testing/web-platform/tests/feature-policy/reporting/midi-reporting.https.html.headers
@@ -0,0 +1 @@
+Feature-Policy: midi 'none'
diff --git a/testing/web-platform/tests/feature-policy/reporting/payment-reporting.https.html b/testing/web-platform/tests/feature-policy/reporting/payment-reporting.https.html
new file mode 100644
index 0000000000..3c04db864d
--- /dev/null
+++ b/testing/web-platform/tests/feature-policy/reporting/payment-reporting.https.html
@@ -0,0 +1,37 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <script src='/resources/testharness.js'></script>
+ <script src='/resources/testharnessreport.js'></script>
+ <script src='../resources/feature-policy-report-json.js'></script>
+ </head>
+ <body>
+ <script>
+var t = async_test("PaymentRequest Report Format");
+
+var check_report_format = (reports, observer) => {
+ let report = reports[0];
+ assert_equals(report.type, "permissions-policy-violation");
+ assert_equals(report.url, document.location.href);
+ assert_equals(report.body.featureId, "payment");
+ assert_equals(report.body.sourceFile, document.location.href);
+ assert_equals(typeof report.body.lineNumber, "number");
+ assert_equals(typeof report.body.columnNumber, "number");
+ assert_equals(report.body.disposition, "enforce");
+ check_report_json(report);
+};
+
+new ReportingObserver(t.step_func_done(check_report_format),
+ {types: ['permissions-policy-violation']}).observe();
+
+t.step_func(() => {
+ assert_throws_dom('SecurityError',
+ () => new PaymentRequest(
+ [{ supportedMethods: 'https://example.com/pay' }],
+ { total: { label: 'Total', amount: { currency: 'USD', value: 0 }}},
+ {}).show(),
+ "PaymentRequest API should not be allowed in this document.");
+})();
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/feature-policy/reporting/payment-reporting.https.html.headers b/testing/web-platform/tests/feature-policy/reporting/payment-reporting.https.html.headers
new file mode 100644
index 0000000000..a2836778bc
--- /dev/null
+++ b/testing/web-platform/tests/feature-policy/reporting/payment-reporting.https.html.headers
@@ -0,0 +1 @@
+Feature-Policy: payment 'none'
diff --git a/testing/web-platform/tests/feature-policy/reporting/picture-in-picture-reporting.html b/testing/web-platform/tests/feature-policy/reporting/picture-in-picture-reporting.html
new file mode 100644
index 0000000000..177e4d5c02
--- /dev/null
+++ b/testing/web-platform/tests/feature-policy/reporting/picture-in-picture-reporting.html
@@ -0,0 +1,46 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <script src='/common/media.js'></script>
+ <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/picture-in-picture.js'></script>
+ </head>
+ <body>
+ <script>
+const check_report_format = (reports, observer) => {
+ const report = reports[0];
+ assert_equals(report.type, "permissions-policy-violation");
+ assert_equals(report.url, document.location.href);
+ assert_equals(report.body.featureId, "picture-in-picture");
+ assert_equals(report.body.sourceFile, document.location.href);
+ assert_equals(typeof report.body.lineNumber, "number");
+ assert_equals(typeof report.body.columnNumber, "number");
+ assert_equals(report.body.disposition, "enforce");
+};
+
+const loadVideo = () => new Promise(resolve => {
+ const video = document.createElement('video');
+ video.src = getVideoURI('/media/movie_5');
+ video.addEventListener('loadedmetadata', () => {
+ resolve(video);
+ }, { once: true });
+});
+
+promise_pip_test(async (t) => {
+ const report = new Promise(resolve => {
+ new ReportingObserver((reports, observer) => resolve([reports, observer]),
+ {types: ['permissions-policy-violation']}).observe();
+ });
+ const videoElement = await loadVideo();
+ await test_driver.bless('picture-in-picture');
+ await promise_rejects_dom(t, 'SecurityError', videoElement.requestPictureInPicture(),
+ "Picture-in-Picture should not be allowed in this document.");
+ const [reports, observer] = await report;
+ check_report_format(reports, observer);
+}, "Picture-in-Picture Report Format");
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/feature-policy/reporting/picture-in-picture-reporting.html.headers b/testing/web-platform/tests/feature-policy/reporting/picture-in-picture-reporting.html.headers
new file mode 100644
index 0000000000..1759381fdc
--- /dev/null
+++ b/testing/web-platform/tests/feature-policy/reporting/picture-in-picture-reporting.html.headers
@@ -0,0 +1 @@
+Feature-Policy: picture-in-picture 'none'
diff --git a/testing/web-platform/tests/feature-policy/reporting/serial-reporting.https.html b/testing/web-platform/tests/feature-policy/reporting/serial-reporting.https.html
new file mode 100644
index 0000000000..c96d8f878a
--- /dev/null
+++ b/testing/web-platform/tests/feature-policy/reporting/serial-reporting.https.html
@@ -0,0 +1,54 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <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>
+ </head>
+ <body>
+ <script>
+var check_report_format = ([reports, observer]) => {
+ let report = reports[0];
+ assert_equals(report.type, "permissions-policy-violation");
+ assert_equals(report.url, document.location.href);
+ assert_equals(report.body.featureId, "serial");
+ assert_equals(report.body.sourceFile, document.location.href);
+ assert_equals(typeof report.body.lineNumber, "number");
+ assert_equals(typeof report.body.columnNumber, "number");
+ assert_equals(report.body.disposition, "enforce");
+};
+
+promise_test(async t => {
+ const report = new Promise(resolve => {
+ new ReportingObserver((reports, observer) => resolve([reports, observer]),
+ {types: ['permissions-policy-violation']}).observe();
+ });
+
+ await test_driver.bless('Activate document for serial.requestPort');
+ try {
+ await navigator.serial.requestPort({ filters: [] });
+ assert_unreached("Serial port access should not be allowed in this document.");
+ } catch (e) {
+ assert_equals(e.code, DOMException.SECURITY_ERR);
+ }
+ check_report_format(await report);
+}, "requestPort in serial reporting mode");
+
+promise_test(async t => {
+ const report = new Promise(resolve => {
+ new ReportingObserver((reports, observer) => resolve([reports, observer]),
+ {types: ['permissions-policy-violation']}).observe();
+ });
+
+ try {
+ await navigator.serial.getPorts();
+ assert_unreached("Serial port access should not be allowed in this document.");
+ } catch (e) {
+ assert_equals(e.code, DOMException.SECURITY_ERR);
+ }
+ check_report_format(await report);
+}, "getPorts in serial reporting mode");
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/feature-policy/reporting/serial-reporting.https.html.headers b/testing/web-platform/tests/feature-policy/reporting/serial-reporting.https.html.headers
new file mode 100644
index 0000000000..be3e6afd42
--- /dev/null
+++ b/testing/web-platform/tests/feature-policy/reporting/serial-reporting.https.html.headers
@@ -0,0 +1 @@
+Feature-Policy: serial 'none'
diff --git a/testing/web-platform/tests/feature-policy/reporting/sync-xhr-reporting.html b/testing/web-platform/tests/feature-policy/reporting/sync-xhr-reporting.html
new file mode 100644
index 0000000000..416edf0cbb
--- /dev/null
+++ b/testing/web-platform/tests/feature-policy/reporting/sync-xhr-reporting.html
@@ -0,0 +1,36 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <script src='/resources/testharness.js'></script>
+ <script src='/resources/testharnessreport.js'></script>
+ <script src='../resources/feature-policy-report-json.js'></script>
+ </head>
+ <body>
+ <script>
+var t = async_test("Sync-xhr Report Format");
+
+var check_report_format = (reports, observer) => {
+ let report = reports[0];
+ assert_equals(report.type, "permissions-policy-violation");
+ assert_equals(report.url, document.location.href);
+ assert_equals(report.body.featureId, "sync-xhr");
+ assert_equals(report.body.sourceFile, document.location.href);
+ assert_equals(typeof report.body.lineNumber, "number");
+ assert_equals(typeof report.body.columnNumber, "number");
+ assert_equals(report.body.disposition, "enforce");
+ check_report_json(report);
+};
+
+new ReportingObserver(t.step_func_done(check_report_format),
+ {types: ['permissions-policy-violation']}).observe();
+
+t.step_func(() => {
+ var xhr = new XMLHttpRequest();
+ xhr.open("GET", document.location.href, false);
+ assert_throws_dom('NetworkError',
+ () => xhr.send(),
+ "Synchronous XHR.send should throw an exception when disabled");
+})();
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/feature-policy/reporting/sync-xhr-reporting.html.headers b/testing/web-platform/tests/feature-policy/reporting/sync-xhr-reporting.html.headers
new file mode 100644
index 0000000000..21a909e1fb
--- /dev/null
+++ b/testing/web-platform/tests/feature-policy/reporting/sync-xhr-reporting.html.headers
@@ -0,0 +1 @@
+Feature-Policy: sync-xhr 'none'
diff --git a/testing/web-platform/tests/feature-policy/reporting/usb-reporting.https.html b/testing/web-platform/tests/feature-policy/reporting/usb-reporting.https.html
new file mode 100644
index 0000000000..1ec5ba4370
--- /dev/null
+++ b/testing/web-platform/tests/feature-policy/reporting/usb-reporting.https.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <script src='/resources/testharness.js'></script>
+ <script src='/resources/testharnessreport.js'></script>
+ </head>
+ <body>
+ <script>
+var t = async_test("USB Report Format");
+
+var check_report_format = (reports, observer) => {
+ let report = reports[0];
+ assert_equals(report.type, "permissions-policy-violation");
+ assert_equals(report.url, document.location.href);
+ assert_equals(report.body.featureId, "usb");
+ assert_equals(report.body.sourceFile, document.location.href);
+ assert_equals(typeof report.body.lineNumber, "number");
+ assert_equals(typeof report.body.columnNumber, "number");
+ assert_equals(report.body.disposition, "enforce");
+};
+
+new ReportingObserver(t.step_func_done(check_report_format),
+ {types: ['permissions-policy-violation']}).observe();
+
+navigator.usb.getDevices().then(
+ t.unreached_func("USB device access should not be allowed in this document.")
+).catch(() => {});
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/feature-policy/reporting/usb-reporting.https.html.headers b/testing/web-platform/tests/feature-policy/reporting/usb-reporting.https.html.headers
new file mode 100644
index 0000000000..4fd1e26936
--- /dev/null
+++ b/testing/web-platform/tests/feature-policy/reporting/usb-reporting.https.html.headers
@@ -0,0 +1 @@
+Feature-Policy: usb 'none'
diff --git a/testing/web-platform/tests/feature-policy/reporting/vr-report-only.https.html b/testing/web-platform/tests/feature-policy/reporting/vr-report-only.https.html
new file mode 100644
index 0000000000..b64a2015f7
--- /dev/null
+++ b/testing/web-platform/tests/feature-policy/reporting/vr-report-only.https.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <script src='/resources/testharness.js'></script>
+ <script src='/resources/testharnessreport.js'></script>
+ </head>
+ <body>
+ <script>
+const check_report_format = ([reports, observer]) => {
+ const report = reports[0];
+ assert_equals(report.type, "feature-policy-violation");
+ assert_equals(report.url, document.location.href);
+ assert_equals(report.body.featureId, "vr");
+ assert_equals(report.body.sourceFile, document.location.href);
+ assert_equals(typeof report.body.lineNumber, "number");
+ assert_equals(typeof report.body.columnNumber, "number");
+ assert_equals(report.body.disposition, "report");
+};
+
+promise_test(async t => {
+ const report = new Promise(resolve => {
+ new ReportingObserver((reports, observer) => resolve([reports, observer]),
+ {types: ['feature-policy-violation']}).observe();
+ });
+ await navigator.getVRDisplays();
+ check_report_format(await report);
+}, "VR report only mode");
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/feature-policy/reporting/vr-report-only.https.html.headers b/testing/web-platform/tests/feature-policy/reporting/vr-report-only.https.html.headers
new file mode 100644
index 0000000000..0761021f45
--- /dev/null
+++ b/testing/web-platform/tests/feature-policy/reporting/vr-report-only.https.html.headers
@@ -0,0 +1 @@
+Feature-Policy-Report-Only: vr 'none'
diff --git a/testing/web-platform/tests/feature-policy/reporting/vr-reporting.https.html b/testing/web-platform/tests/feature-policy/reporting/vr-reporting.https.html
new file mode 100644
index 0000000000..b47f7e187e
--- /dev/null
+++ b/testing/web-platform/tests/feature-policy/reporting/vr-reporting.https.html
@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <script src='/resources/testharness.js'></script>
+ <script src='/resources/testharnessreport.js'></script>
+ </head>
+ <body>
+ <script>
+var check_report_format = (reports, observer) => {
+ let report = reports[0];
+ assert_equals(report.type, "feature-policy-violation");
+ assert_equals(report.url, document.location.href);
+ assert_equals(report.body.featureId, "vr");
+ assert_equals(report.body.sourceFile, document.location.href);
+ assert_equals(typeof report.body.lineNumber, "number");
+ assert_equals(typeof report.body.columnNumber, "number");
+ assert_equals(report.body.disposition, "enforce");
+};
+
+promise_test(async (t) => {
+ const report = new Promise(resolve => {
+ new ReportingObserver((reports, observer) => resolve([reports, observer]),
+ {types: ['feature-policy-violation']}).observe();
+ });
+ await promise_rejects_dom(t, 'SecurityError', navigator.getVRDisplays(),
+ "VR device access should not be allowed in this document.");
+ const [reports, observer] = await report;
+ check_report_format(reports, observer);
+}, "VR Report Format");
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/feature-policy/reporting/vr-reporting.https.html.headers b/testing/web-platform/tests/feature-policy/reporting/vr-reporting.https.html.headers
new file mode 100644
index 0000000000..d021af7563
--- /dev/null
+++ b/testing/web-platform/tests/feature-policy/reporting/vr-reporting.https.html.headers
@@ -0,0 +1 @@
+Feature-Policy: vr 'none'
diff --git a/testing/web-platform/tests/feature-policy/reporting/xr-reporting.https.html b/testing/web-platform/tests/feature-policy/reporting/xr-reporting.https.html
new file mode 100644
index 0000000000..d87d4fb028
--- /dev/null
+++ b/testing/web-platform/tests/feature-policy/reporting/xr-reporting.https.html
@@ -0,0 +1,33 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <script src='/resources/testharness.js'></script>
+ <script src='/resources/testharnessreport.js'></script>
+ </head>
+ <body>
+ <script>
+var check_report_format = (reports, observer) => {
+ let report = reports[0];
+ assert_equals(report.type, "permissions-policy-violation");
+ assert_equals(report.url, document.location.href);
+ assert_equals(report.body.featureId, "xr-spatial-tracking");
+ assert_equals(report.body.sourceFile, document.location.href);
+ assert_equals(typeof report.body.lineNumber, "number");
+ assert_equals(typeof report.body.columnNumber, "number");
+ assert_equals(report.body.disposition, "enforce");
+};
+
+promise_test(async (t) => {
+ const report = new Promise(resolve => {
+ new ReportingObserver((reports, observer) => resolve([reports, observer]),
+ {types: ['permissions-policy-violation']}).observe();
+ });
+ await promise_rejects_dom(t, 'SecurityError',
+ navigator.xr.isSessionSupported('immersive-vr'),
+ "XR spatial tracking should not be allowed in this document.");
+ const [reports, observer] = await report;
+ check_report_format(reports, observer);
+}, "XR Report Format");
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/feature-policy/reporting/xr-reporting.https.html.headers b/testing/web-platform/tests/feature-policy/reporting/xr-reporting.https.html.headers
new file mode 100644
index 0000000000..2c75896233
--- /dev/null
+++ b/testing/web-platform/tests/feature-policy/reporting/xr-reporting.https.html.headers
@@ -0,0 +1 @@
+Feature-Policy: xr-spatial-tracking 'none'
diff --git a/testing/web-platform/tests/feature-policy/resources/autoplay.js b/testing/web-platform/tests/feature-policy/resources/autoplay.js
new file mode 100644
index 0000000000..56780cf6dc
--- /dev/null
+++ b/testing/web-platform/tests/feature-policy/resources/autoplay.js
@@ -0,0 +1,28 @@
+
+
+function simulateGesture(t, callback) {
+ // Get or create the target element.
+ let target = document.getElementById('target');
+ if (!target) {
+ target = document.createElement('button');
+ target.setAttribute('id', 'target');
+ document.body.appendChild(target);
+ }
+
+ // Simulate a gesture in the top frame to remove any gesture based autoplay
+ // restrictions.
+ test_driver.click(target).then(callback, t.unreached_func('click failed'));
+}
+
+function isAutoplayAllowed() {
+ return new Promise((resolve, reject) => {
+ const video = document.createElement('video');
+ video.src = getVideoURI('/media/A4');
+ video.play().then(() => resolve(true), (e) => {
+ if (e.name == 'NotAllowedError')
+ resolve(false);
+ else
+ resolve(true);
+ });
+ });
+}
diff --git a/testing/web-platform/tests/feature-policy/resources/feature-policy-allowedfeatures.html b/testing/web-platform/tests/feature-policy/resources/feature-policy-allowedfeatures.html
new file mode 100644
index 0000000000..f4b020273f
--- /dev/null
+++ b/testing/web-platform/tests/feature-policy/resources/feature-policy-allowedfeatures.html
@@ -0,0 +1,7 @@
+<script>
+'use strict';
+
+window.onload = function() {
+ parent.postMessage(document.featurePolicy.allowedFeatures(), '*');
+}
+</script>
diff --git a/testing/web-platform/tests/feature-policy/resources/feature-policy-autoplay.html b/testing/web-platform/tests/feature-policy/resources/feature-policy-autoplay.html
new file mode 100644
index 0000000000..79f8eefb9d
--- /dev/null
+++ b/testing/web-platform/tests/feature-policy/resources/feature-policy-autoplay.html
@@ -0,0 +1,11 @@
+<script src="/common/media.js"></script>
+<script src=/feature-policy/resources/autoplay.js></script>
+<script>
+'use strict';
+
+window.addEventListener('load', () => {
+ isAutoplayAllowed().then((result) => {
+ window.parent.postMessage({ enabled: result }, '*');
+ });
+}, { once: true });
+</script>
diff --git a/testing/web-platform/tests/feature-policy/resources/feature-policy-clipboard-read.html b/testing/web-platform/tests/feature-policy/resources/feature-policy-clipboard-read.html
new file mode 100644
index 0000000000..10fc45fd93
--- /dev/null
+++ b/testing/web-platform/tests/feature-policy/resources/feature-policy-clipboard-read.html
@@ -0,0 +1,20 @@
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script>
+'use strict';
+
+(async () => {
+ try {
+ // TODO(https://crbug.com/1074482): Cross-origin focus is asynchronous and
+ // requires user gesture. Implement testing support for cross-origin focus.
+ window.focus(); // The Clipboard API requires focus.
+
+ await test_driver.set_permission({ name: 'clipboard-read' }, 'granted');
+ await navigator.clipboard.readText('test text');
+
+ window.parent.postMessage({ enabled: true }, "*");
+ } catch (e) {
+ window.parent.postMessage({ enabled: false }, "*");
+ }
+})();
+</script>
diff --git a/testing/web-platform/tests/feature-policy/resources/feature-policy-clipboard-write.html b/testing/web-platform/tests/feature-policy/resources/feature-policy-clipboard-write.html
new file mode 100644
index 0000000000..7eb96e3db0
--- /dev/null
+++ b/testing/web-platform/tests/feature-policy/resources/feature-policy-clipboard-write.html
@@ -0,0 +1,20 @@
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script>
+'use strict';
+
+(async () => {
+ try {
+ // TODO(https://crbug.com/1074482): Cross-origin focus is asynchronous and
+ // requires user gesture. Implement testing support for cross-origin focus.
+ window.focus(); // The Clipboard API requires focus.
+
+ await test_driver.set_permission({ name: 'clipboard-write' }, 'granted');
+ await navigator.clipboard.writeText('test text');
+
+ window.parent.postMessage({ enabled: true }, "*");
+ } catch (e) {
+ window.parent.postMessage({ enabled: false }, "*");
+ }
+})();
+</script>
diff --git a/testing/web-platform/tests/feature-policy/resources/feature-policy-generic-sensor.html b/testing/web-platform/tests/feature-policy/resources/feature-policy-generic-sensor.html
new file mode 100644
index 0000000000..59652e2e7a
--- /dev/null
+++ b/testing/web-platform/tests/feature-policy/resources/feature-policy-generic-sensor.html
@@ -0,0 +1,11 @@
+<script>
+"use strict";
+
+try {
+ const sensorName = location.hash.substring(1);
+ const sensor = new window[sensorName]();
+ window.parent.postMessage({ enabled: true }, "*");
+} catch (e) {
+ window.parent.postMessage({ enabled: false }, "*");
+}
+</script>
diff --git a/testing/web-platform/tests/feature-policy/resources/feature-policy-nested-subframe-policy.https.sub.html b/testing/web-platform/tests/feature-policy/resources/feature-policy-nested-subframe-policy.https.sub.html
new file mode 100644
index 0000000000..30525d8a3c
--- /dev/null
+++ b/testing/web-platform/tests/feature-policy/resources/feature-policy-nested-subframe-policy.https.sub.html
@@ -0,0 +1,57 @@
+<!DOCTYPE html>
+<body>
+<script>
+'use strict';
+const same_origin_src = '/feature-policy/resources/feature-policy-allowedfeatures.html';
+const cross_origin_src = 'https://{{domains[www1]}}:{{ports[https][0]}}' + same_origin_src;
+const subframe_header_policy = '?pipe=header(Feature-Policy, fullscreen ';
+const policy_all = '*';
+const policy_self = '\'self\'';
+const policy_none = '\'none\'';
+
+// Messages gathered from subframes. When all subframe messages are gathered,
+// it will be send back to top level frame.
+const subframe_messages = [];
+
+let local_frame_all = document.createElement('iframe');
+let local_frame_self = document.createElement('iframe');
+let local_frame_none = document.createElement('iframe');
+local_frame_all.src = same_origin_src + subframe_header_policy + policy_all + ';)';
+local_frame_self.src = same_origin_src + subframe_header_policy + policy_self + ';)';
+local_frame_none.src = same_origin_src + subframe_header_policy + policy_none + ';)';
+
+let remote_frame_all = document.createElement('iframe');
+let remote_frame_self = document.createElement('iframe');
+let remote_frame_none = document.createElement('iframe');
+remote_frame_all.src = cross_origin_src + subframe_header_policy + policy_all + ';)';
+remote_frame_self.src = cross_origin_src + subframe_header_policy + policy_self + ';)';
+remote_frame_none.src = cross_origin_src + subframe_header_policy + policy_none + ';)';
+
+window.addEventListener('message', function(evt) {
+ if (evt.source === local_frame_all.contentWindow) {
+ subframe_messages.push({frame: 'local', policy: policy_all, allowedfeatures: evt.data});
+ } else if (evt.source === local_frame_self.contentWindow) {
+ subframe_messages.push({frame: 'local', policy: policy_self, allowedfeatures: evt.data});
+ } else if (evt.source === local_frame_none.contentWindow) {
+ subframe_messages.push({frame: 'local', policy: policy_none, allowedfeatures: evt.data});
+ } else if (evt.source === remote_frame_all.contentWindow) {
+ subframe_messages.push({frame: 'remote', policy: policy_all, allowedfeatures: evt.data});
+ } else if (evt.source === remote_frame_self.contentWindow) {
+ subframe_messages.push({frame: 'remote', policy: policy_self, allowedfeatures: evt.data});
+ } else if (evt.source === remote_frame_none.contentWindow) {
+ subframe_messages.push({frame: 'remote', policy: policy_none, allowedfeatures: evt.data});
+ }
+
+ if (subframe_messages.length == 6)
+ parent.postMessage(subframe_messages, '*');
+});
+
+document.body.appendChild(local_frame_all);
+document.body.appendChild(local_frame_self);
+document.body.appendChild(local_frame_none);
+document.body.appendChild(remote_frame_all);
+document.body.appendChild(remote_frame_self);
+document.body.appendChild(remote_frame_none);
+</script>
+</body>
+
diff --git a/testing/web-platform/tests/feature-policy/resources/feature-policy-payment.html b/testing/web-platform/tests/feature-policy/resources/feature-policy-payment.html
new file mode 100644
index 0000000000..401a86eb23
--- /dev/null
+++ b/testing/web-platform/tests/feature-policy/resources/feature-policy-payment.html
@@ -0,0 +1,16 @@
+<script>
+'use strict';
+
+window.onload = function() {
+ var supportedInstruments = [ { supportedMethods: [ 'visa' ] } ];
+ var details = {
+ total: { label: 'Test', amount: { currency: 'USD', value: '5.00' } }
+ };
+ try {
+ new PaymentRequest(supportedInstruments, details);
+ parent.postMessage({ enabled: true }, '*');
+ } catch (e) {
+ parent.postMessage({ enabled: false }, '*');
+ }
+}
+</script>
diff --git a/testing/web-platform/tests/feature-policy/resources/feature-policy-picture-in-picture.html b/testing/web-platform/tests/feature-policy/resources/feature-policy-picture-in-picture.html
new file mode 100644
index 0000000000..2f33c44953
--- /dev/null
+++ b/testing/web-platform/tests/feature-policy/resources/feature-policy-picture-in-picture.html
@@ -0,0 +1,11 @@
+<script src=/common/media.js></script>
+<script src=/feature-policy/resources/picture-in-picture.js></script>
+<script>
+'use strict';
+
+window.addEventListener('load', () => {
+ isPictureInPictureAllowed().then(result => {
+ window.parent.postMessage({ enabled: result }, '*');
+ });
+}, { once: true });
+</script>
diff --git a/testing/web-platform/tests/feature-policy/resources/feature-policy-report-json.js b/testing/web-platform/tests/feature-policy/resources/feature-policy-report-json.js
new file mode 100644
index 0000000000..08a0ecaded
--- /dev/null
+++ b/testing/web-platform/tests/feature-policy/resources/feature-policy-report-json.js
@@ -0,0 +1,20 @@
+/**
+ * @fileoverview functions for ensuring feature policy report is serializable
+ */
+
+const check_report_json = (report) => {
+ // Ensures toJSON method exists on report.
+ assert_equals(typeof report.toJSON, "function");
+ const report_json = report.toJSON();
+ // Ensures toJSON() call is successful.
+ assert_equals(report.type, report_json.type);
+ assert_equals(report.url, report_json.url);
+ assert_equals(report.body.featureId, report_json.body.featureId);
+ assert_equals(report.body.disposition, report_json.body.disposition);
+ assert_equals(report.body.sourceFile, report_json.body.sourceFile);
+ assert_equals(report.body.lineNumber, report_json.body.lineNumber);
+ assert_equals(report.body.columnNumber, report_json.body.columnNumber);
+ // Ensures JSON.stringify() serializes the report correctly.
+ assert_false(JSON.stringify(report) === "{}");
+ assert_equals(JSON.stringify(report), JSON.stringify(report_json));
+} \ No newline at end of file
diff --git a/testing/web-platform/tests/feature-policy/resources/feature-policy-webvr.html b/testing/web-platform/tests/feature-policy/resources/feature-policy-webvr.html
new file mode 100644
index 0000000000..64a152bf1c
--- /dev/null
+++ b/testing/web-platform/tests/feature-policy/resources/feature-policy-webvr.html
@@ -0,0 +1,9 @@
+<script>
+'use strict';
+
+Promise.resolve().then(() => navigator.getVRDisplays()).then(displays => {
+ window.parent.postMessage({ enabled: true }, '*');
+}, error => {
+ window.parent.postMessage({ enabled: false }, '*');
+});
+</script>
diff --git a/testing/web-platform/tests/feature-policy/resources/featurepolicy.js b/testing/web-platform/tests/feature-policy/resources/featurepolicy.js
new file mode 100644
index 0000000000..e9b8804f96
--- /dev/null
+++ b/testing/web-platform/tests/feature-policy/resources/featurepolicy.js
@@ -0,0 +1,454 @@
+// Feature test to avoid timeouts
+function assert_feature_policy_supported() {
+ assert_not_equals(document.featurePolicy, undefined,
+ 'Feature Policy is supported');
+}
+// Tests whether a feature that is enabled/disabled by feature policy works
+// as expected.
+// Arguments:
+// feature_description: a short string describing what feature is being
+// tested. Examples: "usb.GetDevices()", "PaymentRequest()".
+// test: test created by testharness. Examples: async_test, promise_test.
+// src: URL where a feature's availability is checked. Examples:
+// "/feature-policy/resources/feature-policy-payment.html",
+// "/feature-policy/resources/feature-policy-usb.html".
+// expect_feature_available: a callback(data, feature_description) to
+// verify if a feature is available or unavailable as expected.
+// The file under the path "src" defines what "data" is sent back as a
+// postMessage. Inside the callback, some tests (e.g., EXPECT_EQ,
+// EXPECT_TRUE, etc) are run accordingly to test a feature's
+// availability.
+// Example: expect_feature_available_default(data, feature_description).
+// feature_name: Optional argument, only provided when testing iframe allow
+// attribute. "feature_name" is the feature name of a policy controlled
+// feature (https://wicg.github.io/feature-policy/#features).
+// See examples at:
+// https://github.com/WICG/feature-policy/blob/master/features.md
+// allow_attribute: Optional argument, only used for testing fullscreen
+// by passing "allowfullscreen".
+function test_feature_availability(
+ feature_description, test, src, expect_feature_available, feature_name,
+ allow_attribute) {
+ let frame = document.createElement('iframe');
+ frame.src = src;
+
+ if (typeof feature_name !== 'undefined') {
+ frame.allow = frame.allow.concat(";" + feature_name);
+ }
+
+ if (typeof allow_attribute !== 'undefined') {
+ frame.setAttribute(allow_attribute, true);
+ }
+
+ window.addEventListener('message', test.step_func(function handler(evt) {
+ if (evt.source === frame.contentWindow) {
+ expect_feature_available(evt.data, feature_description);
+ document.body.removeChild(frame);
+ window.removeEventListener('message', handler);
+ test.done();
+ }
+ }));
+
+ document.body.appendChild(frame);
+}
+
+// Default helper functions to test a feature's availability:
+function expect_feature_available_default(data, feature_description) {
+ assert_true(data.enabled, feature_description);
+}
+
+function expect_feature_unavailable_default(data, feature_description) {
+ assert_false(data.enabled, feature_description);
+}
+
+// This is the same as test_feature_availability() but instead of passing in a
+// function to check the result of the message sent back from an iframe, instead
+// just compares the result to an expected result passed in.
+// Arguments:
+// test: test created by testharness. Examples: async_test, promise_test.
+// src: the URL to load in an iframe in which to test the feature.
+// expected_result: the expected value to compare to the data passed back
+// from the src page by postMessage.
+// allow_attribute: Optional argument, only provided when an allow
+// attribute should be specified on the iframe.
+function test_feature_availability_with_post_message_result(
+ test, src, expected_result, allow_attribute) {
+ var test_result = function(data, feature_description) {
+ assert_equals(data, expected_result);
+ };
+ test_feature_availability(null, test, src, test_result, allow_attribute);
+}
+
+// If this page is intended to test the named feature (according to the URL),
+// tests the feature availability and posts the result back to the parent.
+// Otherwise, does nothing.
+function test_feature_in_iframe(feature_name, feature_promise_factory) {
+ if (location.hash.endsWith(`#${feature_name}`)) {
+ feature_promise_factory().then(
+ () => window.parent.postMessage('#OK', '*'),
+ (e) => window.parent.postMessage('#' + e.name, '*'));
+ }
+}
+
+// Returns true if the URL for this page indicates that it is embedded in an
+// iframe.
+function page_loaded_in_iframe() {
+ return location.hash.startsWith('#iframe');
+}
+
+// Returns a same-origin (relative) URL suitable for embedding in an iframe for
+// testing the availability of the feature.
+function same_origin_url(feature_name) {
+ // Append #iframe to the URL so we can detect the iframe'd version of the
+ // page.
+ return location.pathname + '#iframe#' + feature_name;
+}
+
+// Returns a cross-origin (absolute) URL suitable for embedding in an iframe for
+// testing the availability of the feature.
+function cross_origin_url(base_url, feature_name) {
+ return base_url + same_origin_url(feature_name);
+}
+
+// This function runs all feature policy tests for a particular feature that
+// has a default policy of "self". This includes testing:
+// 1. Feature usage succeeds by default in the top level frame.
+// 2. Feature usage succeeds by default in a same-origin iframe.
+// 3. Feature usage fails by default in a cross-origin iframe.
+// 4. Feature usage suceeds when an allow attribute is specified on a
+// cross-origin iframe.
+//
+// The same page which called this function will be loaded in the iframe in
+// order to test feature usage there. When this function is called in that
+// context it will simply run the feature and return a result back via
+// postMessage.
+//
+// Arguments:
+// cross_origin: A cross-origin URL base to be used to load the page which
+// called into this function.
+// feature_name: The name of the feature as it should be specified in an
+// allow attribute.
+// error_name: If feature usage does not succeed, this is the string
+// representation of the error that will be passed in the rejected
+// promise.
+// feature_promise_factory: A function which returns a promise which tests
+// feature usage. If usage succeeds, the promise should resolve. If it
+// fails, the promise should reject with an error that can be
+// represented as a string.
+function run_all_fp_tests_allow_self(
+ cross_origin, feature_name, error_name, feature_promise_factory) {
+ // This may be the version of the page loaded up in an iframe. If so, just
+ // post the result of running the feature promise back to the parent.
+ if (page_loaded_in_iframe()) {
+ test_feature_in_iframe(feature_name, feature_promise_factory);
+ return;
+ }
+
+ // Run the various tests.
+ // 1. Allowed in top-level frame.
+ promise_test(
+ () => feature_promise_factory(),
+ 'Default "' + feature_name +
+ '" feature policy ["self"] allows the top-level document.');
+
+ // 2. Allowed in same-origin iframe.
+ const same_origin_frame_pathname = same_origin_url(feature_name);
+ async_test(
+ t => {
+ test_feature_availability_with_post_message_result(
+ t, same_origin_frame_pathname, '#OK');
+ },
+ 'Default "' + feature_name +
+ '" feature policy ["self"] allows same-origin iframes.');
+
+ // 3. Blocked in cross-origin iframe.
+ const cross_origin_frame_url = cross_origin_url(cross_origin, feature_name);
+ async_test(
+ t => {
+ test_feature_availability_with_post_message_result(
+ t, cross_origin_frame_url, '#' + error_name);
+ },
+ 'Default "' + feature_name +
+ '" feature policy ["self"] disallows cross-origin iframes.');
+
+ // 4. Allowed in cross-origin iframe with "allow" attribute.
+ async_test(
+ t => {
+ test_feature_availability_with_post_message_result(
+ t, cross_origin_frame_url, '#OK', feature_name);
+ },
+ 'Feature policy "' + feature_name +
+ '" can be enabled in cross-origin iframes using "allow" attribute.');
+}
+
+// This function runs all feature policy tests for a particular feature that
+// has a default policy of "*". This includes testing:
+// 1. Feature usage succeeds by default in the top level frame.
+// 2. Feature usage succeeds by default in a same-origin iframe.
+// 3. Feature usage succeeds by default in a cross-origin iframe.
+// 4. Feature usage fails when an allow attribute is specified on a
+// cross-origin iframe with a value of "feature-name 'none'".
+//
+// The same page which called this function will be loaded in the iframe in
+// order to test feature usage there. When this function is called in that
+// context it will simply run the feature and return a result back via
+// postMessage.
+//
+// Arguments:
+// cross_origin: A cross-origin URL base to be used to load the page which
+// called into this function.
+// feature_name: The name of the feature as it should be specified in an
+// allow attribute.
+// error_name: If feature usage does not succeed, this is the string
+// representation of the error that will be passed in the rejected
+// promise.
+// feature_promise_factory: A function which returns a promise which tests
+// feature usage. If usage succeeds, the promise should resolve. If it
+// fails, the promise should reject with an error that can be
+// represented as a string.
+function run_all_fp_tests_allow_all(
+ cross_origin, feature_name, error_name, feature_promise_factory) {
+ // This may be the version of the page loaded up in an iframe. If so, just
+ // post the result of running the feature promise back to the parent.
+ if (page_loaded_in_iframe()) {
+ test_feature_in_iframe(feature_name, feature_promise_factory);
+ return;
+ }
+
+ // Run the various tests.
+ // 1. Allowed in top-level frame.
+ promise_test(
+ () => feature_promise_factory(),
+ 'Default "' + feature_name +
+ '" feature policy ["*"] allows the top-level document.');
+
+ // 2. Allowed in same-origin iframe.
+ const same_origin_frame_pathname = same_origin_url(feature_name);
+ async_test(
+ t => {
+ test_feature_availability_with_post_message_result(
+ t, same_origin_frame_pathname, '#OK');
+ },
+ 'Default "' + feature_name +
+ '" feature policy ["*"] allows same-origin iframes.');
+
+ // 3. Allowed in cross-origin iframe.
+ const cross_origin_frame_url = cross_origin_url(cross_origin, feature_name);
+ async_test(
+ t => {
+ test_feature_availability_with_post_message_result(
+ t, cross_origin_frame_url, '#OK');
+ },
+ 'Default "' + feature_name +
+ '" feature policy ["*"] allows cross-origin iframes.');
+
+ // 4. Blocked in cross-origin iframe with "allow" attribute set to 'none'.
+ async_test(
+ t => {
+ test_feature_availability_with_post_message_result(
+ t, cross_origin_frame_url, '#' + error_name,
+ feature_name + " 'none'");
+ },
+ 'Feature policy "' + feature_name +
+ '" can be disabled in cross-origin iframes using "allow" attribute.');
+
+ // 5. Blocked in same-origin iframe with "allow" attribute set to 'none'.
+ async_test(
+ t => {
+ test_feature_availability_with_post_message_result(
+ t, same_origin_frame_pathname, '#' + error_name,
+ feature_name + " 'none'");
+ },
+ 'Feature policy "' + feature_name +
+ '" can be disabled in same-origin iframes using "allow" attribute.');
+}
+
+// This function tests that a given policy allows each feature for the correct
+// list of origins specified by the |expected_policy|.
+// Arguments:
+// expected_policy: A list of {feature, allowlist} pairs where the feature is
+// enabled for every origin in the allowlist, in the |policy|.
+// policy: Either a document.featurePolicy or an iframe.featurePolicy to be
+// tested.
+// message: A short description of what policy is being tested.
+function test_allowlists(expected_policy, policy, message) {
+ for (var allowlist of allowlists) {
+ test(function() {
+ assert_array_equals(
+ policy.getAllowlistForFeature(allowlist.feature),
+ allowlist.allowlist);
+ }, message + ' for feature ' + allowlist.feature);
+ }
+}
+
+// This function tests that a subframe's document policy allows a given feature.
+// A feature is allowed in a frame either through inherited policy or specified
+// by iframe allow attribute.
+// Arguments:
+// test: test created by testharness. Examples: async_test, promise_test.
+// feature: feature name that should be allowed in the frame.
+// src: the URL to load in the frame.
+// allow: the allow attribute (container policy) of the iframe
+function test_allowed_feature_for_subframe(message, feature, src, allow) {
+ let frame = document.createElement('iframe');
+ if (typeof allow !== 'undefined') {
+ frame.allow = allow;
+ }
+ promise_test(function() {
+ assert_feature_policy_supported();
+ frame.src = src;
+ return new Promise(function(resolve, reject) {
+ window.addEventListener('message', function handler(evt) {
+ resolve(evt.data);
+ }, { once: true });
+ document.body.appendChild(frame);
+ }).then(function(data) {
+ assert_true(data.includes(feature), feature);
+ });
+ }, message);
+}
+
+// This function tests that a subframe's document policy disallows a given
+// feature. A feature is allowed in a frame either through inherited policy or
+// specified by iframe allow attribute.
+// Arguments:
+// test: test created by testharness. Examples: async_test, promise_test.
+// feature: feature name that should not be allowed in the frame.
+// src: the URL to load in the frame.
+// allow: the allow attribute (container policy) of the iframe
+function test_disallowed_feature_for_subframe(message, feature, src, allow) {
+ let frame = document.createElement('iframe');
+ if (typeof allow !== 'undefined') {
+ frame.allow = allow;
+ }
+ promise_test(function() {
+ assert_feature_policy_supported();
+ frame.src = src;
+ return new Promise(function(resolve, reject) {
+ window.addEventListener('message', function handler(evt) {
+ resolve(evt.data);
+ }, { once: true });
+ document.body.appendChild(frame);
+ }).then(function(data) {
+ assert_false(data.includes(feature), feature);
+ });
+ }, message);
+}
+
+// This function tests that a subframe with header policy defined on a given
+// feature allows and disallows the feature as expected.
+// Arguments:
+// feature: feature name.
+// frame_header_policy: either *, 'self' or 'none', defines the frame
+// document's header policy on |feature|.
+// src: the URL to load in the frame.
+// test_expects: contains 6 expected results of either |feature| is allowed
+// or not inside of a local or remote iframe nested inside
+// the subframe given the header policy to be either *,
+// 'slef', or 'none'.
+// test_name: name of the test.
+function test_subframe_header_policy(
+ feature, frame_header_policy, src, test_expects, test_name) {
+ let frame = document.createElement('iframe');
+ promise_test(function() {
+ assert_feature_policy_supported()
+ frame.src = src + '?pipe=sub|header(Feature-Policy,' + feature + ' '
+ + frame_header_policy + ';)';
+ return new Promise(function(resolve) {
+ window.addEventListener('message', function handler(evt) {
+ resolve(evt.data);
+ });
+ document.body.appendChild(frame);
+ }).then(function(results) {
+ for (var j = 0; j < results.length; j++) {
+ var data = results[j];
+
+ function test_result(message, test_expect) {
+ if (test_expect) {
+ assert_true(data.allowedfeatures.includes(feature), message);
+ } else {
+ assert_false(data.allowedfeatures.includes(feature), message);
+ }
+ }
+
+ if (data.frame === 'local') {
+ if (data.policy === '*') {
+ test_result('local_all:', test_expects.local_all);
+ }
+ if (data.policy === '\'self\'') {
+ test_result('local_self:', test_expects.local_self);
+ }
+ if (data.policy === '\'none\'') {
+ test_result('local_none:', test_expects.local_none);
+ }
+ }
+
+ if (data.frame === 'remote') {
+ if (data.policy === '*') {
+ test_result('remote_all:', test_expects.remote_all);
+ }
+ if (data.policy === '\'self\'') {
+ test_result('remote_self:', test_expects.remote_self);
+ }
+ if (data.policy === '\'none\'') {
+ test_result('remote_none:', test_expects.remote_none);
+ }
+ }
+ }
+ });
+ }, test_name);
+}
+
+// This function tests that frame policy allows a given feature correctly. A
+// feature is allowed in a frame either through inherited policy or specified
+// by iframe allow attribute.
+// Arguments:
+// feature: feature name.
+// src: the URL to load in the frame. If undefined, the iframe will have a
+// srcdoc="" attribute
+// test_expect: boolean value of whether the feature should be allowed.
+// allow: optional, the allow attribute (container policy) of the iframe.
+// allowfullscreen: optional, boolean value of allowfullscreen attribute.
+// sandbox: optional boolean. If true, the frame will be sandboxed (with
+// allow-scripts, so that tests can run in it.)
+function test_frame_policy(
+ feature, src, srcdoc, test_expect, allow, allowfullscreen, sandbox) {
+ let frame = document.createElement('iframe');
+ document.body.appendChild(frame);
+ // frame_policy should be dynamically updated as allow and allowfullscreen is
+ // updated.
+ var frame_policy = frame.featurePolicy;
+ if (typeof allow !== 'undefined') {
+ frame.setAttribute('allow', allow);
+ }
+ if (!!allowfullscreen) {
+ frame.setAttribute('allowfullscreen', true);
+ }
+ if (!!sandbox) {
+ frame.setAttribute('sandbox', 'allow-scripts');
+ }
+ if (!!src) {
+ frame.src = src;
+ }
+ if (!!srcdoc) {
+ frame.srcdoc = "<h1>Hello world!</h1>";
+ }
+ if (test_expect) {
+ assert_true(frame_policy.allowedFeatures().includes(feature));
+ } else {
+ assert_false(frame_policy.allowedFeatures().includes(feature));
+ }
+}
+
+function expect_reports(report_count, policy_name, description) {
+ async_test(t => {
+ var num_received_reports = 0;
+ new ReportingObserver(t.step_func((reports, observer) => {
+ const relevant_reports = reports.filter(r => (r.body.featureId === policy_name));
+ num_received_reports += relevant_reports.length;
+ if (num_received_reports >= report_count) {
+ t.done();
+ }
+ }), {types: ['permissions-policy-violation'], buffered: true}).observe();
+ }, description);
+}
diff --git a/testing/web-platform/tests/feature-policy/resources/nested-sandbox.html b/testing/web-platform/tests/feature-policy/resources/nested-sandbox.html
new file mode 100644
index 0000000000..30af207092
--- /dev/null
+++ b/testing/web-platform/tests/feature-policy/resources/nested-sandbox.html
@@ -0,0 +1,8 @@
+<!DOCTYPE html>
+<title>Return fullscreen feature policy state</title>
+<script>
+ "use strict";
+ window.onload = () => {
+ window.parent.postMessage(document.featurePolicy.allowedFeatures().includes("fullscreen"),"*");
+ };
+</script>
diff --git a/testing/web-platform/tests/feature-policy/resources/picture-in-picture.js b/testing/web-platform/tests/feature-policy/resources/picture-in-picture.js
new file mode 100644
index 0000000000..1bf3c1c12a
--- /dev/null
+++ b/testing/web-platform/tests/feature-policy/resources/picture-in-picture.js
@@ -0,0 +1,30 @@
+function async_pip_test(func, name) {
+ async_test(t => {
+ assert_true('pictureInPictureEnabled' in document, 'Picture-in-Picture API is available');
+ func(t);
+ }, name);
+}
+
+function promise_pip_test(func, name) {
+ promise_test(async t => {
+ assert_true('pictureInPictureEnabled' in document, 'Picture-in-Picture API is available');
+ return func(t);
+ }, name);
+}
+
+function isPictureInPictureAllowed() {
+ return new Promise(resolve => {
+ let video = document.createElement('video');
+ video.src = getVideoURI('/media/movie_5');
+ video.onloadedmetadata = () => {
+ video.requestPictureInPicture()
+ .then(() => resolve(document.pictureInPictureEnabled))
+ .catch(e => {
+ if (e.name == 'NotAllowedError')
+ resolve(document.pictureInPictureEnabled);
+ else
+ resolve(false);
+ });
+ };
+ });
+}
diff --git a/testing/web-platform/tests/feature-policy/resources/redirect-on-load.html b/testing/web-platform/tests/feature-policy/resources/redirect-on-load.html
new file mode 100644
index 0000000000..1711655b03
--- /dev/null
+++ b/testing/web-platform/tests/feature-policy/resources/redirect-on-load.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<body>
+<script>
+// Automatically redirects the page to a new URL on load.
+// Load this document with a URL like:
+// "feature-policy/resources/redirect-on-load.html#https://www.example.com/"
+window.onload = function () {
+ document.location = document.location.hash.substring(1);
+}
+</script>
+</body>
diff --git a/testing/web-platform/tests/feature-policy/resources/sandbox-self.html b/testing/web-platform/tests/feature-policy/resources/sandbox-self.html
new file mode 100644
index 0000000000..3488338f96
--- /dev/null
+++ b/testing/web-platform/tests/feature-policy/resources/sandbox-self.html
@@ -0,0 +1,23 @@
+<!DOCTYPE html>
+<title>Return fullscreen feature policy state from self and a sandboxed child frame</title>
+<script>
+ "use strict";
+ window.onload = () => {
+ let frame = document.createElement('iframe');
+ frame.src = "/feature-policy/resources/nested-sandbox.html";
+ frame.sandbox = "allow-scripts";
+
+ var handle_message = evt => {
+ if (evt.source === frame.contentWindow) {
+ window.parent.postMessage({
+ "child": document.featurePolicy.allowedFeatures().includes("fullscreen"),
+ "grandchild": evt.data
+ },"*");
+ document.body.removeChild(frame);
+ window.removeEventListener('message', handle_message);
+ }
+ };
+ window.addEventListener('message', handle_message);
+ document.body.appendChild(frame);
+ };
+</script>
diff --git a/testing/web-platform/tests/feature-policy/resources/sandbox-self.html.headers b/testing/web-platform/tests/feature-policy/resources/sandbox-self.html.headers
new file mode 100644
index 0000000000..16c20a7649
--- /dev/null
+++ b/testing/web-platform/tests/feature-policy/resources/sandbox-self.html.headers
@@ -0,0 +1 @@
+Feature-Policy: fullscreen 'self'