summaryrefslogtreecommitdiffstats
path: root/testing/web-platform/tests/permissions-policy
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--testing/web-platform/tests/permissions-policy/META.yml3
-rw-r--r--testing/web-platform/tests/permissions-policy/README.md59
-rw-r--r--testing/web-platform/tests/permissions-policy/bluetooth-allowed-by-permissions-policy-attribute-redirect-on-load.https.sub.html27
-rw-r--r--testing/web-platform/tests/permissions-policy/bluetooth-allowed-by-permissions-policy-attribute.https.sub.html25
-rw-r--r--testing/web-platform/tests/permissions-policy/bluetooth-allowed-by-permissions-policy.https.sub.html31
-rw-r--r--testing/web-platform/tests/permissions-policy/bluetooth-allowed-by-permissions-policy.https.sub.html.headers1
-rw-r--r--testing/web-platform/tests/permissions-policy/bluetooth-default-permissions-policy.https.sub.html31
-rw-r--r--testing/web-platform/tests/permissions-policy/bluetooth-disabled-by-permissions-policy.https.sub.html35
-rw-r--r--testing/web-platform/tests/permissions-policy/bluetooth-disabled-by-permissions-policy.https.sub.html.headers1
-rw-r--r--testing/web-platform/tests/permissions-policy/experimental-features/focus-without-user-activation-disabled-tentative.html51
-rw-r--r--testing/web-platform/tests/permissions-policy/experimental-features/focus-without-user-activation-enabled-tentative.sub.html51
-rw-r--r--testing/web-platform/tests/permissions-policy/experimental-features/resources/common.js94
-rw-r--r--testing/web-platform/tests/permissions-policy/experimental-features/resources/focus-without-user-activation-iframe-tentative.html47
-rw-r--r--testing/web-platform/tests/permissions-policy/experimental-features/resources/lazyload-contents.html13
-rw-r--r--testing/web-platform/tests/permissions-policy/experimental-features/resources/lazyload.pngbin0 -> 20819 bytes
-rw-r--r--testing/web-platform/tests/permissions-policy/experimental-features/resources/permissions-policy-trust-token-redemption.html56
-rw-r--r--testing/web-platform/tests/permissions-policy/experimental-features/resources/unload-helper.js41
-rw-r--r--testing/web-platform/tests/permissions-policy/experimental-features/resources/vertical-scroll-scrollable-content.html16
-rw-r--r--testing/web-platform/tests/permissions-policy/experimental-features/resources/vertical-scroll-scrollbar-ref.html13
-rw-r--r--testing/web-platform/tests/permissions-policy/experimental-features/resources/vertical-scroll-scrollintoview.html45
-rw-r--r--testing/web-platform/tests/permissions-policy/experimental-features/resources/vertical-scroll-touch-action.html14
-rw-r--r--testing/web-platform/tests/permissions-policy/experimental-features/resources/vertical-scroll-touch-block.html42
-rw-r--r--testing/web-platform/tests/permissions-policy/experimental-features/resources/vertical-scroll-wheel-block.html22
-rw-r--r--testing/web-platform/tests/permissions-policy/experimental-features/resources/vertical-scroll.js25
-rw-r--r--testing/web-platform/tests/permissions-policy/experimental-features/trust-token-redemption-default-permissions-policy.tentative.https.sub.html62
-rw-r--r--testing/web-platform/tests/permissions-policy/experimental-features/trust-token-redemption-supported-by-permissions-policy.tentative.html9
-rw-r--r--testing/web-platform/tests/permissions-policy/experimental-features/unload-allowed-by-default.tentative.window.js22
-rw-r--r--testing/web-platform/tests/permissions-policy/experimental-features/unload-disallowed-subframe.tentative.window.js21
-rw-r--r--testing/web-platform/tests/permissions-policy/experimental-features/unload-disallowed.tentative.window.js21
-rw-r--r--testing/web-platform/tests/permissions-policy/experimental-features/vertical-scroll-disabled-frame-no-scroll-manual.tentative.html113
-rw-r--r--testing/web-platform/tests/permissions-policy/experimental-features/vertical-scroll-disabled-scrollbar-tentative.html4
-rw-r--r--testing/web-platform/tests/permissions-policy/experimental-features/vertical-scroll-main-frame-manual.tentative.html46
-rw-r--r--testing/web-platform/tests/permissions-policy/experimental-features/vertical-scroll-main-frame-manual.tentative.html.headers1
-rw-r--r--testing/web-platform/tests/permissions-policy/experimental-features/vertical-scroll-scrollintoview.tentative.html117
-rw-r--r--testing/web-platform/tests/permissions-policy/experimental-features/vertical-scroll-touch-action-manual.tentative.html103
-rw-r--r--testing/web-platform/tests/permissions-policy/experimental-features/vertical-scroll-touch-block-manual.tentative.html237
-rw-r--r--testing/web-platform/tests/permissions-policy/experimental-features/vertical-scroll-wheel-block-manual.tentative.html145
-rw-r--r--testing/web-platform/tests/permissions-policy/idlharness.window.js20
-rw-r--r--testing/web-platform/tests/permissions-policy/payment-allowed-by-permissions-policy-attribute-redirect-on-load.https.sub.html39
-rw-r--r--testing/web-platform/tests/permissions-policy/payment-allowed-by-permissions-policy-attribute.https.sub.html26
-rw-r--r--testing/web-platform/tests/permissions-policy/payment-allowed-by-permissions-policy.https.sub.html40
-rw-r--r--testing/web-platform/tests/permissions-policy/payment-allowed-by-permissions-policy.https.sub.html.headers1
-rw-r--r--testing/web-platform/tests/permissions-policy/payment-default-permissions-policy.https.sub.html47
-rw-r--r--testing/web-platform/tests/permissions-policy/payment-disabled-by-permissions-policy.https.sub.html45
-rw-r--r--testing/web-platform/tests/permissions-policy/payment-disabled-by-permissions-policy.https.sub.html.headers1
-rw-r--r--testing/web-platform/tests/permissions-policy/payment-extension-allowed-by-permissions-policy-attribute.https.sub.html26
-rw-r--r--testing/web-platform/tests/permissions-policy/payment-supported-by-permissions-policy.tentative.html11
-rw-r--r--testing/web-platform/tests/permissions-policy/permissions-policy-frame-policy-allowed-for-all.https.sub.html150
-rw-r--r--testing/web-platform/tests/permissions-policy/permissions-policy-frame-policy-allowed-for-all.https.sub.html.sub.headers1
-rw-r--r--testing/web-platform/tests/permissions-policy/permissions-policy-frame-policy-allowed-for-self.https.sub.html184
-rw-r--r--testing/web-platform/tests/permissions-policy/permissions-policy-frame-policy-allowed-for-self.https.sub.html.sub.headers1
-rw-r--r--testing/web-platform/tests/permissions-policy/permissions-policy-frame-policy-allowed-for-some-override.https.sub.html83
-rw-r--r--testing/web-platform/tests/permissions-policy/permissions-policy-frame-policy-allowed-for-some-override.https.sub.html.sub.headers1
-rw-r--r--testing/web-platform/tests/permissions-policy/permissions-policy-frame-policy-allowed-for-some.https.sub.html136
-rw-r--r--testing/web-platform/tests/permissions-policy/permissions-policy-frame-policy-allowed-for-some.https.sub.html.sub.headers1
-rw-r--r--testing/web-platform/tests/permissions-policy/permissions-policy-frame-policy-disallowed-for-all.https.sub.html150
-rw-r--r--testing/web-platform/tests/permissions-policy/permissions-policy-frame-policy-disallowed-for-all.https.sub.html.sub.headers1
-rw-r--r--testing/web-platform/tests/permissions-policy/permissions-policy-frame-policy-timing-iframe-camera.https.sub.html15
-rw-r--r--testing/web-platform/tests/permissions-policy/permissions-policy-frame-policy-timing.https.sub.html69
-rw-r--r--testing/web-platform/tests/permissions-policy/permissions-policy-header-policy-allowed-for-all.https.sub.html61
-rw-r--r--testing/web-platform/tests/permissions-policy/permissions-policy-header-policy-allowed-for-all.https.sub.html.sub.headers1
-rw-r--r--testing/web-platform/tests/permissions-policy/permissions-policy-header-policy-allowed-for-malformed-wildcard.https.sub.html55
-rw-r--r--testing/web-platform/tests/permissions-policy/permissions-policy-header-policy-allowed-for-malformed-wildcard.https.sub.html.sub.headers1
-rw-r--r--testing/web-platform/tests/permissions-policy/permissions-policy-header-policy-allowed-for-self.https.sub.html45
-rw-r--r--testing/web-platform/tests/permissions-policy/permissions-policy-header-policy-allowed-for-self.https.sub.html.sub.headers1
-rw-r--r--testing/web-platform/tests/permissions-policy/permissions-policy-header-policy-allowed-for-some.https.sub.html75
-rw-r--r--testing/web-platform/tests/permissions-policy/permissions-policy-header-policy-allowed-for-some.https.sub.html.sub.headers1
-rw-r--r--testing/web-platform/tests/permissions-policy/permissions-policy-header-policy-allowed-for-wildcard.https.sub.html68
-rw-r--r--testing/web-platform/tests/permissions-policy/permissions-policy-header-policy-allowed-for-wildcard.https.sub.html.sub.headers1
-rw-r--r--testing/web-platform/tests/permissions-policy/permissions-policy-header-policy-declined.https.sub.html74
-rw-r--r--testing/web-platform/tests/permissions-policy/permissions-policy-header-policy-declined.https.sub.html.sub.headers1
-rw-r--r--testing/web-platform/tests/permissions-policy/permissions-policy-header-policy-disallowed-for-all.https.sub.html46
-rw-r--r--testing/web-platform/tests/permissions-policy/permissions-policy-header-policy-disallowed-for-all.https.sub.html.sub.headers2
-rw-r--r--testing/web-platform/tests/permissions-policy/permissions-policy-javascript-url-frame-policy.https.html40
-rw-r--r--testing/web-platform/tests/permissions-policy/permissions-policy-nested-header-policy-allowed-for-all.https.sub.html66
-rw-r--r--testing/web-platform/tests/permissions-policy/permissions-policy-nested-header-policy-allowed-for-all.https.sub.html.sub.headers1
-rw-r--r--testing/web-platform/tests/permissions-policy/permissions-policy-nested-header-policy-allowed-for-self.https.sub.html63
-rw-r--r--testing/web-platform/tests/permissions-policy/permissions-policy-nested-header-policy-allowed-for-self.https.sub.html.sub.headers1
-rw-r--r--testing/web-platform/tests/permissions-policy/permissions-policy-nested-header-policy-disallowed-for-all.https.sub.html51
-rw-r--r--testing/web-platform/tests/permissions-policy/permissions-policy-nested-header-policy-disallowed-for-all.https.sub.html.sub.headers1
-rw-r--r--testing/web-platform/tests/permissions-policy/permissions-policy-opaque-origin-history.https.html44
-rw-r--r--testing/web-platform/tests/permissions-policy/permissions-policy-opaque-origin.https.html23
-rw-r--r--testing/web-platform/tests/permissions-policy/picture-in-picture-allowed-by-permissions-policy-attribute-redirect-on-load.https.sub.html31
-rw-r--r--testing/web-platform/tests/permissions-policy/picture-in-picture-allowed-by-permissions-policy-attribute.https.sub.html30
-rw-r--r--testing/web-platform/tests/permissions-policy/picture-in-picture-allowed-by-permissions-policy.https.sub.html33
-rw-r--r--testing/web-platform/tests/permissions-policy/picture-in-picture-allowed-by-permissions-policy.https.sub.html.headers1
-rw-r--r--testing/web-platform/tests/permissions-policy/picture-in-picture-default-permissions-policy.https.sub.html34
-rw-r--r--testing/web-platform/tests/permissions-policy/picture-in-picture-disabled-by-permissions-policy.https.sub.html33
-rw-r--r--testing/web-platform/tests/permissions-policy/picture-in-picture-disabled-by-permissions-policy.https.sub.html.headers1
-rw-r--r--testing/web-platform/tests/permissions-policy/picture-in-picture-supported-by-permissions-policy.html11
-rw-r--r--testing/web-platform/tests/permissions-policy/policy-extends-to-sandbox.html26
-rw-r--r--testing/web-platform/tests/permissions-policy/reporting/bluetooth-report-only.https.html67
-rw-r--r--testing/web-platform/tests/permissions-policy/reporting/bluetooth-report-only.https.html.headers1
-rw-r--r--testing/web-platform/tests/permissions-policy/reporting/bluetooth-reporting.https.html83
-rw-r--r--testing/web-platform/tests/permissions-policy/reporting/bluetooth-reporting.https.html.headers1
-rw-r--r--testing/web-platform/tests/permissions-policy/reporting/camera-report-only.https.html31
-rw-r--r--testing/web-platform/tests/permissions-policy/reporting/camera-report-only.https.html.headers1
-rw-r--r--testing/web-platform/tests/permissions-policy/reporting/camera-reporting.https.html33
-rw-r--r--testing/web-platform/tests/permissions-policy/reporting/camera-reporting.https.html.headers1
-rw-r--r--testing/web-platform/tests/permissions-policy/reporting/encrypted-media-report-only.https.html30
-rw-r--r--testing/web-platform/tests/permissions-policy/reporting/encrypted-media-report-only.https.html.headers1
-rw-r--r--testing/web-platform/tests/permissions-policy/reporting/encrypted-media-reporting.https.html37
-rw-r--r--testing/web-platform/tests/permissions-policy/reporting/encrypted-media-reporting.https.html.headers1
-rw-r--r--testing/web-platform/tests/permissions-policy/reporting/fullscreen-report-only.html31
-rw-r--r--testing/web-platform/tests/permissions-policy/reporting/fullscreen-report-only.html.headers1
-rw-r--r--testing/web-platform/tests/permissions-policy/reporting/fullscreen-reporting.html45
-rw-r--r--testing/web-platform/tests/permissions-policy/reporting/fullscreen-reporting.html.headers1
-rw-r--r--testing/web-platform/tests/permissions-policy/reporting/generic-sensor-report-only.https.html55
-rw-r--r--testing/web-platform/tests/permissions-policy/reporting/generic-sensor-report-only.https.html.headers1
-rw-r--r--testing/web-platform/tests/permissions-policy/reporting/generic-sensor-reporting.https.html55
-rw-r--r--testing/web-platform/tests/permissions-policy/reporting/generic-sensor-reporting.https.html.headers1
-rw-r--r--testing/web-platform/tests/permissions-policy/reporting/geolocation-report-only.https.html36
-rw-r--r--testing/web-platform/tests/permissions-policy/reporting/geolocation-report-only.https.html.headers1
-rw-r--r--testing/web-platform/tests/permissions-policy/reporting/geolocation-reporting.https.html30
-rw-r--r--testing/web-platform/tests/permissions-policy/reporting/geolocation-reporting.https.html.headers1
-rw-r--r--testing/web-platform/tests/permissions-policy/reporting/image.bmpbin0 -> 6538 bytes
-rw-r--r--testing/web-platform/tests/permissions-policy/reporting/microphone-report-only.https.html31
-rw-r--r--testing/web-platform/tests/permissions-policy/reporting/microphone-report-only.https.html.headers1
-rw-r--r--testing/web-platform/tests/permissions-policy/reporting/microphone-reporting.https.html33
-rw-r--r--testing/web-platform/tests/permissions-policy/reporting/microphone-reporting.https.html.headers1
-rw-r--r--testing/web-platform/tests/permissions-policy/reporting/midi-report-only.https.html34
-rw-r--r--testing/web-platform/tests/permissions-policy/reporting/midi-report-only.https.html.headers1
-rw-r--r--testing/web-platform/tests/permissions-policy/reporting/midi-reporting.https.html32
-rw-r--r--testing/web-platform/tests/permissions-policy/reporting/midi-reporting.https.html.headers1
-rw-r--r--testing/web-platform/tests/permissions-policy/reporting/payment-report-only.https.html40
-rw-r--r--testing/web-platform/tests/permissions-policy/reporting/payment-report-only.https.html.headers1
-rw-r--r--testing/web-platform/tests/permissions-policy/reporting/payment-reporting.https.html37
-rw-r--r--testing/web-platform/tests/permissions-policy/reporting/payment-reporting.https.html.headers1
-rw-r--r--testing/web-platform/tests/permissions-policy/reporting/picture-in-picture-report-only.html40
-rw-r--r--testing/web-platform/tests/permissions-policy/reporting/picture-in-picture-report-only.html.headers1
-rw-r--r--testing/web-platform/tests/permissions-policy/reporting/picture-in-picture-reporting.html46
-rw-r--r--testing/web-platform/tests/permissions-policy/reporting/picture-in-picture-reporting.html.headers1
-rw-r--r--testing/web-platform/tests/permissions-policy/reporting/screen-wake-lock-reporting.https.html32
-rw-r--r--testing/web-platform/tests/permissions-policy/reporting/screen-wake-lock-reporting.https.html.headers1
-rw-r--r--testing/web-platform/tests/permissions-policy/reporting/serial-report-only.https.html46
-rw-r--r--testing/web-platform/tests/permissions-policy/reporting/serial-report-only.https.html.headers1
-rw-r--r--testing/web-platform/tests/permissions-policy/reporting/serial-reporting.https.html54
-rw-r--r--testing/web-platform/tests/permissions-policy/reporting/serial-reporting.https.html.headers1
-rw-r--r--testing/web-platform/tests/permissions-policy/reporting/sync-xhr-report-only.html28
-rw-r--r--testing/web-platform/tests/permissions-policy/reporting/sync-xhr-report-only.html.headers1
-rw-r--r--testing/web-platform/tests/permissions-policy/reporting/sync-xhr-reporting.html36
-rw-r--r--testing/web-platform/tests/permissions-policy/reporting/sync-xhr-reporting.html.headers1
-rw-r--r--testing/web-platform/tests/permissions-policy/reporting/usb-report-only.https.html30
-rw-r--r--testing/web-platform/tests/permissions-policy/reporting/usb-report-only.https.html.headers1
-rw-r--r--testing/web-platform/tests/permissions-policy/reporting/usb-reporting.https.html30
-rw-r--r--testing/web-platform/tests/permissions-policy/reporting/usb-reporting.https.html.headers1
-rw-r--r--testing/web-platform/tests/permissions-policy/reporting/xr-report-only.https.html37
-rw-r--r--testing/web-platform/tests/permissions-policy/reporting/xr-report-only.https.html.headers1
-rw-r--r--testing/web-platform/tests/permissions-policy/reporting/xr-reporting.https.html33
-rw-r--r--testing/web-platform/tests/permissions-policy/reporting/xr-reporting.https.html.headers1
-rw-r--r--testing/web-platform/tests/permissions-policy/resources/autoplay.js28
-rw-r--r--testing/web-platform/tests/permissions-policy/resources/nested-sandbox.html8
-rw-r--r--testing/web-platform/tests/permissions-policy/resources/opaque-origin-history1.sub.https.html15
-rw-r--r--testing/web-platform/tests/permissions-policy/resources/opaque-origin-history2.https.html3
-rw-r--r--testing/web-platform/tests/permissions-policy/resources/opaque-origin1.sub.https.html4
-rw-r--r--testing/web-platform/tests/permissions-policy/resources/opaque-origin2.https.html8
-rw-r--r--testing/web-platform/tests/permissions-policy/resources/permissions-policy-allowedfeatures.html7
-rw-r--r--testing/web-platform/tests/permissions-policy/resources/permissions-policy-autoplay.html11
-rw-r--r--testing/web-platform/tests/permissions-policy/resources/permissions-policy-battery.html9
-rw-r--r--testing/web-platform/tests/permissions-policy/resources/permissions-policy-bluetooth.html9
-rw-r--r--testing/web-platform/tests/permissions-policy/resources/permissions-policy-clipboard-read.html20
-rw-r--r--testing/web-platform/tests/permissions-policy/resources/permissions-policy-clipboard-write.html20
-rw-r--r--testing/web-platform/tests/permissions-policy/resources/permissions-policy-compute-pressure.html16
-rw-r--r--testing/web-platform/tests/permissions-policy/resources/permissions-policy-generic-sensor.html11
-rw-r--r--testing/web-platform/tests/permissions-policy/resources/permissions-policy-geolocation.html22
-rw-r--r--testing/web-platform/tests/permissions-policy/resources/permissions-policy-idle-detection-worker.html10
-rw-r--r--testing/web-platform/tests/permissions-policy/resources/permissions-policy-idle-detection-worker.js16
-rw-r--r--testing/web-platform/tests/permissions-policy/resources/permissions-policy-idle-detection.html10
-rw-r--r--testing/web-platform/tests/permissions-policy/resources/permissions-policy-local-fonts.html11
-rw-r--r--testing/web-platform/tests/permissions-policy/resources/permissions-policy-nested-subframe-policy.https.sub.html57
-rw-r--r--testing/web-platform/tests/permissions-policy/resources/permissions-policy-payment-extension.html60
-rw-r--r--testing/web-platform/tests/permissions-policy/resources/permissions-policy-payment.html17
-rw-r--r--testing/web-platform/tests/permissions-policy/resources/permissions-policy-picture-in-picture.html11
-rw-r--r--testing/web-platform/tests/permissions-policy/resources/permissions-policy-report-json.js20
-rw-r--r--testing/web-platform/tests/permissions-policy/resources/permissions-policy-screen-wakelock.html18
-rw-r--r--testing/web-platform/tests/permissions-policy/resources/permissions-policy-serial-worker.html10
-rw-r--r--testing/web-platform/tests/permissions-policy/resources/permissions-policy-serial-worker.js14
-rw-r--r--testing/web-platform/tests/permissions-policy/resources/permissions-policy-serial.html9
-rw-r--r--testing/web-platform/tests/permissions-policy/resources/permissions-policy-usb-worker.html10
-rw-r--r--testing/web-platform/tests/permissions-policy/resources/permissions-policy-usb-worker.js14
-rw-r--r--testing/web-platform/tests/permissions-policy/resources/permissions-policy-usb.html9
-rw-r--r--testing/web-platform/tests/permissions-policy/resources/permissions-policy.js460
-rw-r--r--testing/web-platform/tests/permissions-policy/resources/picture-in-picture.js30
-rw-r--r--testing/web-platform/tests/permissions-policy/resources/redirect-on-load.html11
-rw-r--r--testing/web-platform/tests/permissions-policy/resources/sandbox-self.html23
-rw-r--r--testing/web-platform/tests/permissions-policy/resources/sandbox-self.html.headers1
186 files changed, 5807 insertions, 0 deletions
diff --git a/testing/web-platform/tests/permissions-policy/META.yml b/testing/web-platform/tests/permissions-policy/META.yml
new file mode 100644
index 0000000000..7b2e2cbffe
--- /dev/null
+++ b/testing/web-platform/tests/permissions-policy/META.yml
@@ -0,0 +1,3 @@
+spec: https://w3c.github.io/webappsec-permissions-policy/
+ - bakulf
+ - clelland
diff --git a/testing/web-platform/tests/permissions-policy/README.md b/testing/web-platform/tests/permissions-policy/README.md
new file mode 100644
index 0000000000..ae17c099e5
--- /dev/null
+++ b/testing/web-platform/tests/permissions-policy/README.md
@@ -0,0 +1,59 @@
+# Permissions Policy Guide
+## How to Test a New Feature with permissions policy
+
+This directory contains a framework to test features with permissions policy.
+
+When adding a new feature to permissions policy, the following cases should be tested:
+* feature enabled by header policy [HTTP tests]
+ + test when feature is enabled by permissions policy HTTP header;
+* feature disabled by header policy [HTTP tests]
+ + test when feature is disabled by permissions policy HTTP header;
+* feature enabled on self origin by header policy [HTTP tests]
+ + test when feature is enabled only on self origin by permissions 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
+`/permissions-policy/resources/permissions-policy.js`. Please refer to the comments
+in `/permissions-policy/resources/permissions-policy.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-permissions-policy.https.sub.html.headers`. Example:
+
+ Permissions-Policy: feature-name=*
+
+
+* In `<feature-name>-<enabled | disabled | enabled-on-self-origin>-by-permissions-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:
+`/permissions-policy/payment-disabled-by-permissions-policy.https.sub.html`
+`/permissions-policy/payment-disabled-by-permissions-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:
+`/permissions-policy/payment-allowed-by-permissions-policy-attribute.https.sub.html`
+
+### How to Write Container Policy Tests with Redirect
+Similar to the section above, append
+`/permissions-policy/resources/redirect-on-load.html#` to the argument `src`
+passed to `test_feature_availability()`.
+
+Example:
+`/permissions-policy/payment-allowed-by-permissions-policy-attribute-redirect-on-load.https.sub.html`
+
diff --git a/testing/web-platform/tests/permissions-policy/bluetooth-allowed-by-permissions-policy-attribute-redirect-on-load.https.sub.html b/testing/web-platform/tests/permissions-policy/bluetooth-allowed-by-permissions-policy-attribute-redirect-on-load.https.sub.html
new file mode 100644
index 0000000000..a5f4479511
--- /dev/null
+++ b/testing/web-platform/tests/permissions-policy/bluetooth-allowed-by-permissions-policy-attribute-redirect-on-load.https.sub.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<body>
+ <script src=/resources/testharness.js></script>
+ <script src=/resources/testharnessreport.js></script>
+ <script src=/bluetooth/resources/bluetooth-test.js></script>
+ <script src=/permissions-policy/resources/permissions-policy.js></script>
+ <script>
+ 'use strict'
+
+ const relative_path = '/permissions-policy/resources/permissions-policy-bluetooth.html';
+ const base_src = '/permissions-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 feature_name = 'permissions policy "bluetooth"';
+ const header = 'permissions policy allow="bluetooth"';
+
+ bluetooth_test(() => {
+ async_test(t => {
+ test_feature_availability('bluetooth.getDevices()', t, same_origin_src, expect_feature_available_default, 'bluetooth');
+ }, header + ' allows same-origin navigation in an iframe.');
+
+ async_test(t => {
+ test_feature_availability('bluetooth.getDevices()', t, cross_origin_src, expect_feature_unavailable_default, 'bluetooth');
+ }, header + ' disallows cross-origin navigation in an iframe.');
+ });
+ </script>
+</body> \ No newline at end of file
diff --git a/testing/web-platform/tests/permissions-policy/bluetooth-allowed-by-permissions-policy-attribute.https.sub.html b/testing/web-platform/tests/permissions-policy/bluetooth-allowed-by-permissions-policy-attribute.https.sub.html
new file mode 100644
index 0000000000..5b1f69e86c
--- /dev/null
+++ b/testing/web-platform/tests/permissions-policy/bluetooth-allowed-by-permissions-policy-attribute.https.sub.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<body>
+ <script src=/resources/testharness.js></script>
+ <script src=/resources/testharnessreport.js></script>
+ <script src=/bluetooth/resources/bluetooth-test.js></script>
+ <script src=/permissions-policy/resources/permissions-policy.js></script>
+ <script>
+ 'use strict'
+
+ const same_origin_src = '/permissions-policy/resources/permissions-policy-bluetooth.html';
+ const cross_origin_src = 'https://{{domains[www]}}:{{ports[https][0]}}' + same_origin_src;
+ const feature_name = 'permissions policy "bluetooth"';
+ const header = 'allow="bluetooth" attribute';
+
+ bluetooth_test(() => {
+ async_test(t => {
+ test_feature_availability('bluetooth.getDevices()', t, same_origin_src, expect_feature_available_default, 'bluetooth');
+ }, feature_name + ' can be enabled in same-origin iframe using ' + header);
+
+ async_test(t => {
+ test_feature_availability('bluetooth.getDevices()', t, cross_origin_src, expect_feature_available_default, 'bluetooth');
+ }, feature_name + ' can be enabled in cross-origin iframe using ' + header);
+ });
+ </script>
+</body> \ No newline at end of file
diff --git a/testing/web-platform/tests/permissions-policy/bluetooth-allowed-by-permissions-policy.https.sub.html b/testing/web-platform/tests/permissions-policy/bluetooth-allowed-by-permissions-policy.https.sub.html
new file mode 100644
index 0000000000..283c613271
--- /dev/null
+++ b/testing/web-platform/tests/permissions-policy/bluetooth-allowed-by-permissions-policy.https.sub.html
@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<body>
+ <script src=/resources/testharness.js></script>
+ <script src=/resources/testharnessreport.js></script>
+ <script src=/bluetooth/resources/bluetooth-test.js></script>
+ <script src=/permissions-policy/resources/permissions-policy.js></script>
+ <script>
+ 'use strict';
+ const same_origin_src = '/permissions-policy/resources/permissions-policy-bluetooth.html';
+ const cross_origin_src = 'https://{{domains[www]}}:{{ports[https][0]}}' + same_origin_src;
+ const header = 'permissions policy header "bluetooth=*"';
+
+ bluetooth_test(() => {
+ promise_test(() => {
+ return navigator.bluetooth.getDevices();
+ }, header + ' allows the top-level document.');
+
+ async_test(t => {
+ test_feature_availability('bluetooth.getDevices()', t, same_origin_src, expect_feature_available_default);
+ }, header + ' allows same-origin iframes.');
+
+ async_test(t => {
+ test_feature_availability('bluetooth.getDevices()', t, cross_origin_src, expect_feature_unavailable_default);
+ }, header + ' disallows cross-origin iframes.');
+
+ async_test(t => {
+ test_feature_availability('bluetooth.getDevices()', t, cross_origin_src, expect_feature_available_default, 'bluetooth');
+ }, header + ' allow="bluetooth" allows cross-origin iframes.');
+ });
+ </script>
+</body> \ No newline at end of file
diff --git a/testing/web-platform/tests/permissions-policy/bluetooth-allowed-by-permissions-policy.https.sub.html.headers b/testing/web-platform/tests/permissions-policy/bluetooth-allowed-by-permissions-policy.https.sub.html.headers
new file mode 100644
index 0000000000..a053d1a213
--- /dev/null
+++ b/testing/web-platform/tests/permissions-policy/bluetooth-allowed-by-permissions-policy.https.sub.html.headers
@@ -0,0 +1 @@
+Permissions-Policy: bluetooth=* \ No newline at end of file
diff --git a/testing/web-platform/tests/permissions-policy/bluetooth-default-permissions-policy.https.sub.html b/testing/web-platform/tests/permissions-policy/bluetooth-default-permissions-policy.https.sub.html
new file mode 100644
index 0000000000..51ab40bca9
--- /dev/null
+++ b/testing/web-platform/tests/permissions-policy/bluetooth-default-permissions-policy.https.sub.html
@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<body>
+ <script src=/resources/testharness.js></script>
+ <script src=/resources/testharnessreport.js></script>
+ <script src=/bluetooth/resources/bluetooth-test.js></script>
+ <script src=/permissions-policy/resources/permissions-policy.js></script>
+ <script>
+ 'use strict';
+ const same_origin_src = '/permissions-policy/resources/permissions-policy-bluetooth.html';
+ const cross_origin_src = 'https://{{domains[www]}}:{{ports[https][0]}}' + same_origin_src;
+ const header = 'Default "bluetooth" permissions policy';
+
+ bluetooth_test(() => {
+ promise_test(() => {
+ return navigator.bluetooth.getDevices();
+ }, header + ' allows the top-level document.');
+
+ async_test(t => {
+ test_feature_availability('bluetooth.getDevices()', t, same_origin_src, expect_feature_available_default);
+ }, header + ' allows same-origin iframes.');
+
+ async_test(t => {
+ test_feature_availability('bluetooth.getDevices()', t, cross_origin_src, expect_feature_unavailable_default);
+ }, header + ' disallows cross-origin iframes.');
+
+ async_test(t => {
+ test_feature_availability('bluetooth.getDevices()', t, cross_origin_src, expect_feature_available_default, 'bluetooth');
+ }, header + ' allow="bluetooth" allows cross-origin iframes.');
+ });
+ </script>
+</body> \ No newline at end of file
diff --git a/testing/web-platform/tests/permissions-policy/bluetooth-disabled-by-permissions-policy.https.sub.html b/testing/web-platform/tests/permissions-policy/bluetooth-disabled-by-permissions-policy.https.sub.html
new file mode 100644
index 0000000000..7a20a9f01f
--- /dev/null
+++ b/testing/web-platform/tests/permissions-policy/bluetooth-disabled-by-permissions-policy.https.sub.html
@@ -0,0 +1,35 @@
+<!DOCTYPE html>
+<body>
+ <script src=/resources/testharness.js></script>
+ <script src=/resources/testharnessreport.js></script>
+ <script src=/bluetooth/resources/bluetooth-test.js></script>
+ <script src=/permissions-policy/resources/permissions-policy.js></script>
+ <script>
+ 'use strict';
+ const same_origin_src = '/permissions-policy/resources/permissions-policy-bluetooth.html';
+ const cross_origin_src = 'https://{{domains[www]}}:{{ports[https][0]}}' + same_origin_src;
+ const header = 'permissions policy header "bluetooth=()"';
+
+ bluetooth_test(() => {
+ promise_test(() => {
+ return navigator.bluetooth.getDevices().then(() => {
+ assert_unreached('expected promise to reject with SecurityError.');
+ }, error => {
+ assert_equals(error.name, 'SecurityError');
+ });
+ }, header + ' disallows the top-level document.')
+
+ async_test(t => {
+ test_feature_availability('bluetooth.getDevices()', t, same_origin_src, expect_feature_unavailable_default);
+ }, header + ' disallows same-origin iframes.');
+
+ async_test(t => {
+ test_feature_availability('bluetooth.getDevices()', t, cross_origin_src, expect_feature_unavailable_default);
+ }, header + ' disallows cross-origin iframes.');
+
+ async_test(t => {
+ test_feature_availability('bluetooth.getDevices()', t, cross_origin_src, expect_feature_unavailable_default, 'bluetooth');
+ }, header + ' allow="bluetooth" has no effect on cross-origin iframes.');
+ });
+ </script>
+</body> \ No newline at end of file
diff --git a/testing/web-platform/tests/permissions-policy/bluetooth-disabled-by-permissions-policy.https.sub.html.headers b/testing/web-platform/tests/permissions-policy/bluetooth-disabled-by-permissions-policy.https.sub.html.headers
new file mode 100644
index 0000000000..10b94729dd
--- /dev/null
+++ b/testing/web-platform/tests/permissions-policy/bluetooth-disabled-by-permissions-policy.https.sub.html.headers
@@ -0,0 +1 @@
+Permissions-Policy: bluetooth=() \ No newline at end of file
diff --git a/testing/web-platform/tests/permissions-policy/experimental-features/focus-without-user-activation-disabled-tentative.html b/testing/web-platform/tests/permissions-policy/experimental-features/focus-without-user-activation-disabled-tentative.html
new file mode 100644
index 0000000000..3980fd1219
--- /dev/null
+++ b/testing/web-platform/tests/permissions-policy/experimental-features/focus-without-user-activation-disabled-tentative.html
@@ -0,0 +1,51 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="./resources/common.js"></script>
+<title> 'focus-without-user-activation' Policy : Correctly block automatic focus when policy disabled
+</title>
+<body>
+<script>
+ "use strict"
+ // Note: According to html spec: https://html.spec.whatwg.org/#attr-fe-autofocus,
+ // topDocument's autofocus processed flag initially is false and is set to true
+ // after flushing autofocus candidates, i.e. flush of autofocus candidates
+ // only happens once per page load.
+ // In order to test the behaviour with both focus-without-user-activation on and off:
+ // two test files are necessary:
+ // - focus-without-user-activation-disabled-tentative.html
+ // - focus-without-user-activation-enabled-tentative.sub.html
+
+ // Use same origin url here because when iframe document has cross origin
+ // url, autofocus will be blocked by default with following console error:
+ // "Blocked autofocusing on a form control in a cross-origin subframe."
+ const url = "/permissions-policy/experimental-features/resources/focus-without-user-activation-iframe-tentative.html";
+
+ function subframe_focused(subframe, event_name, timeout) {
+ return new Promise(resolve => {
+ window.onmessage = m => resolve(m.data.focused);
+ subframe.contentWindow.postMessage({
+ event: event_name,
+ timeout: timeout
+ }, "*");
+ });
+ }
+
+ promise_test( async (instance) => {
+ const frame = createIframe(document.body, {
+ sandbox: "allow-scripts allow-same-origin",
+ allow: "focus-without-user-activation 'none'",
+ src: url
+ });
+
+ await wait_for_load(frame);
+ assert_false(await subframe_focused(frame, "autofocus", 400), "'autofocus' should not work.");
+ window.focus(); // Reset focus state in subframe.
+ assert_false(await subframe_focused(frame, "focus-input", 400), "'element.focus' should not work.");
+ window.focus(); // Reset focus state in subframe.
+ assert_false(await subframe_focused(frame, "focus-window", 400), "'window.focus' should not work.");
+ window.focus(); // Reset focus state in subframe.
+ }, "When the policy is disabled, 'autofocus' and scripted focus do not focus " +
+ "the document.");
+</script>
+</body>
diff --git a/testing/web-platform/tests/permissions-policy/experimental-features/focus-without-user-activation-enabled-tentative.sub.html b/testing/web-platform/tests/permissions-policy/experimental-features/focus-without-user-activation-enabled-tentative.sub.html
new file mode 100644
index 0000000000..5722947f86
--- /dev/null
+++ b/testing/web-platform/tests/permissions-policy/experimental-features/focus-without-user-activation-enabled-tentative.sub.html
@@ -0,0 +1,51 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="./resources/common.js"></script>
+<title> 'focus-without-user-activation' Policy : Correctly block automatic focus when policy disabled
+</title>
+<body>
+<script>
+ "use strict"
+ // Note: According to html spec: https://html.spec.whatwg.org/#attr-fe-autofocus,
+ // topDocument's autofocus processed flag initially is false and is set to true
+ // after flushing autofocus candidates, i.e. flush of autofocus candidates
+ // only happens once per page load.
+ // In order to test the behaviour with both focus-without-user-activation on and off:
+ // two test files are necessary:
+ // - focus-without-user-activation-disabled-tentative.html
+ // - focus-without-user-activation-enabled-tentative.sub.html
+
+ // Cross origin subframe should not be able to use autofocus to steal focus
+ // from main frame by default. However, with focus-without-user-activation
+ // enabled for subframe, subframe should be able to autofocus.
+ const url = "http://{{hosts[alt][www1]}}:{{ports[http][0]}}/permissions-policy/experimental-features/resources/focus-without-user-activation-iframe-tentative.html";
+
+ function subframe_focused(subframe, event_name, timeout) {
+ return new Promise(resolve => {
+ window.onmessage = m => resolve(m.data.focused);
+ subframe.contentWindow.postMessage({
+ event: event_name,
+ timeout: timeout
+ }, "*");
+ });
+ }
+
+ promise_test( async (instance) => {
+ const frame = createIframe(document.body, {
+ sandbox: "allow-scripts allow-same-origin",
+ allow: "focus-without-user-activation *",
+ src: url
+ });
+
+ await wait_for_load(frame);
+ assert_true(await subframe_focused(frame, "autofocus"), "'autofocus' should work.");
+ window.focus(); // Reset focus state in subframe.
+ assert_true(await subframe_focused(frame, "focus-input"), "'element.focus' should work.");
+ window.focus(); // Reset focus state in subframe.
+ assert_true(await subframe_focused(frame, "focus-window"), "'window.focus' should work.");
+ window.focus(); // Reset focus state in subframe.
+ }, "When the policy is enabled, 'autofocus' and scripted focus do focus " +
+ "the document.");
+</script>
+</body>
diff --git a/testing/web-platform/tests/permissions-policy/experimental-features/resources/common.js b/testing/web-platform/tests/permissions-policy/experimental-features/resources/common.js
new file mode 100644
index 0000000000..308f787da6
--- /dev/null
+++ b/testing/web-platform/tests/permissions-policy/experimental-features/resources/common.js
@@ -0,0 +1,94 @@
+const url_base = "/permissions-policy/experimental-features/resources/";
+window.messageResponseCallback = null;
+
+function setFeatureState(iframe, feature, origins) {
+ iframe.setAttribute("allow", `${feature} ${origins};`);
+}
+
+// Returns a promise which is resolved when the <iframe> is navigated to |url|
+// and "load" handler has been called.
+function loadUrlInIframe(iframe, url) {
+ return new Promise((resolve) => {
+ iframe.addEventListener("load", resolve);
+ iframe.src = url;
+ });
+}
+
+// Posts |message| to |target| and resolves the promise with the response coming
+// back from |target|.
+function sendMessageAndGetResponse(target, message) {
+ return new Promise((resolve) => {
+ window.messageResponseCallback = resolve;
+ target.postMessage(message, "*");
+ });
+}
+
+
+function onMessage(e) {
+ if (window.messageResponseCallback) {
+ window.messageResponseCallback(e.data);
+ window.messageResponseCallback = null;
+ }
+}
+
+window.addEventListener("message", onMessage);
+
+// Waits for |load_timeout| before resolving the promise. It will resolve the
+// promise sooner if a message event with |e.data.id| of |id| is received.
+// In such a case the response is the contents of the message |e.data.contents|.
+// Otherwise, returns false (when timeout occurs).
+function waitForMessageOrTimeout(t, id, load_timeout) {
+ return new Promise((resolve) => {
+ window.addEventListener(
+ "message",
+ (e) => {
+ if (!e.data || e.data.id !== id)
+ return;
+ resolve(e.data.contents);
+ }
+ );
+ t.step_timeout(() => { resolve(false); }, load_timeout);
+ });
+}
+
+function createIframe(container, attributes) {
+ var new_iframe = document.createElement("iframe");
+ for (attr_name in attributes)
+ new_iframe.setAttribute(attr_name, attributes[attr_name]);
+ container.appendChild(new_iframe);
+ return new_iframe;
+}
+
+// Returns a promise which is resolved when |load| event is dispatched for |e|.
+function wait_for_load(e) {
+ return new Promise((resolve) => {
+ e.addEventListener("load", resolve);
+ });
+}
+
+setup(() => {
+ window.reporting_observer_instance = new ReportingObserver((reports, observer) => {
+ if (window.reporting_observer_callback) {
+ reports.forEach(window.reporting_observer_callback);
+ }
+ }, {types: ["permissions-policy-violation"]});
+ window.reporting_observer_instance.observe();
+ window.reporting_observer_callback = null;
+});
+
+// Waits for a violation in |feature| and source file containing |file_name|.
+function wait_for_violation_in_file(feature, file_name) {
+ return new Promise( (resolve) => {
+ assert_equals(null, window.reporting_observer_callback);
+ window.reporting_observer_callback = (report) => {
+ var feature_match = (feature === report.body.featureId);
+ var file_name_match =
+ !file_name ||
+ (report.body.sourceFile.indexOf(file_name) !== -1);
+ if (feature_match && file_name_match) {
+ window.reporting_observer_callback = null;
+ resolve(report);
+ }
+ };
+ });
+}
diff --git a/testing/web-platform/tests/permissions-policy/experimental-features/resources/focus-without-user-activation-iframe-tentative.html b/testing/web-platform/tests/permissions-policy/experimental-features/resources/focus-without-user-activation-iframe-tentative.html
new file mode 100644
index 0000000000..3d5aab95ad
--- /dev/null
+++ b/testing/web-platform/tests/permissions-policy/experimental-features/resources/focus-without-user-activation-iframe-tentative.html
@@ -0,0 +1,47 @@
+<!doctype html>
+<input autofocus onfocus="autofocus_onfocus()"/>
+<script>
+ let autofocused = false;
+ function autofocus_onfocus() {
+ autofocused = true;
+ }
+
+ /**
+ * @param target object: Target to call |focus()| with.
+ * @param timeout integer | undefined: Timeout to wait for the focus event.
+ * If unspecified, a timeout will not be set.
+ * @param focus_target boolean | undefined: Wether to focus the target after
+ * listening for |onfocus| event.
+ */
+ function wait_focus_event(target, timeout, focus_target) {
+ return new Promise((resolve) => {
+ if (timeout)
+ setTimeout(() => resolve(false), timeout);
+
+ target.onfocus = () => resolve(true);
+ if (focus_target)
+ target.focus();
+ });
+ }
+
+ function post_result(destination, result) {
+ destination.postMessage({focused: result}, "*");
+ }
+
+ window.addEventListener("message", (e) => {
+ if (e.data.event === "autofocus") {
+ if (autofocused)
+ post_result(e.source, true);
+
+ wait_focus_event(document.querySelector("input"), e.data.timeout)
+ .then(result => post_result(e.source, result));
+ } else if (e.data.event === "focus-window") {
+ wait_focus_event(window, e.data.timeout, true /* focus_target */)
+ .then(result => post_result(e.source, result));
+ } else if (e.data.event === "focus-input") {
+ const input_element = document.querySelector("input");
+ wait_focus_event(input_element, e.data.timeout, true /* focus_target */)
+ .then(result => post_result(e.source, result));
+ }
+});
+</script>
diff --git a/testing/web-platform/tests/permissions-policy/experimental-features/resources/lazyload-contents.html b/testing/web-platform/tests/permissions-policy/experimental-features/resources/lazyload-contents.html
new file mode 100644
index 0000000000..a6e98c24e6
--- /dev/null
+++ b/testing/web-platform/tests/permissions-policy/experimental-features/resources/lazyload-contents.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<body>
+ <p>This page is lazyloaded.</p>
+ <script>
+ let query_index = window.location.href.indexOf("?id=");
+ let id = window.location.href.substr(query_index + 4);
+ window.addEventListener("load", () => {
+ let p = document.querySelector("p");
+ let contents = p ? p.innerHTML : "null";
+ window.parent.postMessage({"id": id, "contents": contents}, "*");
+ });
+ </script>
+</body>
diff --git a/testing/web-platform/tests/permissions-policy/experimental-features/resources/lazyload.png b/testing/web-platform/tests/permissions-policy/experimental-features/resources/lazyload.png
new file mode 100644
index 0000000000..fd3da53a29
--- /dev/null
+++ b/testing/web-platform/tests/permissions-policy/experimental-features/resources/lazyload.png
Binary files differ
diff --git a/testing/web-platform/tests/permissions-policy/experimental-features/resources/permissions-policy-trust-token-redemption.html b/testing/web-platform/tests/permissions-policy/experimental-features/resources/permissions-policy-trust-token-redemption.html
new file mode 100644
index 0000000000..db24b04824
--- /dev/null
+++ b/testing/web-platform/tests/permissions-policy/experimental-features/resources/permissions-policy-trust-token-redemption.html
@@ -0,0 +1,56 @@
+<script>
+ 'use strict';
+
+ window.onload = function() {
+ // When the trust-token-redemption permissions policy is enabled, redemption
+ // and signing ("send-redemption-record") should both be available; when it's disabled,
+ // they should both be unavailable. Send the number of available operations
+ // upstream in order to enforce this in assertions.
+ let num_enabled = 4;
+ try {
+ new Request("https://issuer.example/", {
+ 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({
+ type: 'availability-result',
+ num_operations_enabled: num_enabled,
+ }, '*');
+ }
+</script>
diff --git a/testing/web-platform/tests/permissions-policy/experimental-features/resources/unload-helper.js b/testing/web-platform/tests/permissions-policy/experimental-features/resources/unload-helper.js
new file mode 100644
index 0000000000..9739ead69d
--- /dev/null
+++ b/testing/web-platform/tests/permissions-policy/experimental-features/resources/unload-helper.js
@@ -0,0 +1,41 @@
+// Code used by controlling frame of the unload policy tests.
+
+const MAIN_FRAME = 'main';
+const SUBFRAME = 'sub';
+
+async function isUnloadAllowed(remoteContextWrapper) {
+ return remoteContextWrapper.executeScript(() => {
+ return document.featurePolicy.allowsFeature('unload');
+ });
+}
+
+// Checks whether a frame runs unload handlers.
+// This checks the policy directly and also installs an unload handler and
+// navigates the frame checking that the handler ran.
+async function assertWindowRunsUnload(
+ remoteContextWrapper, name, {shouldRunUnload}) {
+ const maybeNot = shouldRunUnload ? '' : 'not ';
+ assert_equals(
+ await isUnloadAllowed(remoteContextWrapper), shouldRunUnload,
+ `${name}: unload in ${name} should ${maybeNot}be allowed`);
+
+ // Set up recording of whether unload handler ran.
+ await remoteContextWrapper.executeScript((name) => {
+ localStorage.setItem(name, 'did not run');
+ addEventListener('unload', () => localStorage.setItem(name, 'did run'));
+ }, [name]);
+
+ // Navigate away and then back.
+ const second = await remoteContextWrapper.navigateToNew();
+ // Navigating back ensures that the unload has completed.
+ // Also if the navigation is cross-site then we have to return
+ // to the original origin in order to read the recorded unload.
+ second.historyBack();
+
+ // Check that unload handlers ran as expected.
+ const recordedUnload = await remoteContextWrapper.executeScript(
+ (name) => localStorage.getItem(name), [name]);
+ assert_equals(
+ recordedUnload, `did ${maybeNot}run`,
+ `${name}: unload should ${maybeNot}have run`);
+}
diff --git a/testing/web-platform/tests/permissions-policy/experimental-features/resources/vertical-scroll-scrollable-content.html b/testing/web-platform/tests/permissions-policy/experimental-features/resources/vertical-scroll-scrollable-content.html
new file mode 100644
index 0000000000..9f78ea4bc2
--- /dev/null
+++ b/testing/web-platform/tests/permissions-policy/experimental-features/resources/vertical-scroll-scrollable-content.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<style>
+ body, html {
+ height: 100%;
+ width: 100%;
+ }
+ #spacer {
+ width: 1500px;
+ height: 1500px;
+ background-color: red;
+ }
+</style>
+<body>
+ <p>This page is scrollable.</p>
+ <div id="spacer"></div>
+</body>
diff --git a/testing/web-platform/tests/permissions-policy/experimental-features/resources/vertical-scroll-scrollbar-ref.html b/testing/web-platform/tests/permissions-policy/experimental-features/resources/vertical-scroll-scrollbar-ref.html
new file mode 100644
index 0000000000..fd432b33f6
--- /dev/null
+++ b/testing/web-platform/tests/permissions-policy/experimental-features/resources/vertical-scroll-scrollbar-ref.html
@@ -0,0 +1,13 @@
+<!doctype html>
+<title>Ref: vertical-scroll test for scrollbar</title>
+<iframe src="/permissions-policy/experimental-features/resources/vertical-scroll-scrollable-content.html"></iframe>
+<script>
+ let iframe = document.querySelector("iframe");
+ let overflow_y = "visible";
+ if (window.location.search.indexOf("no-vertical-scrollbar") !== -1)
+ overflow_y = "hidden"
+ iframe.addEventListener("load", () => {
+ iframe.contentDocument.body.style.overflowY = overflow_y;
+ });
+</script>
+
diff --git a/testing/web-platform/tests/permissions-policy/experimental-features/resources/vertical-scroll-scrollintoview.html b/testing/web-platform/tests/permissions-policy/experimental-features/resources/vertical-scroll-scrollintoview.html
new file mode 100644
index 0000000000..7bed27c260
--- /dev/null
+++ b/testing/web-platform/tests/permissions-policy/experimental-features/resources/vertical-scroll-scrollintoview.html
@@ -0,0 +1,45 @@
+<!DOCTYPE html>
+<style>
+html, body, #container {
+ width: 100%;
+ height: 100%;
+}
+
+#spacer {
+ width: 200%;
+ height: 200%;
+}
+</style>
+<div id="container">
+ <div id="spacer"></div>
+ <button>Element To Scroll</button>
+</div>
+<script>
+ window.addEventListener('message', onMessageReceived);
+
+ function scrollingElementBounds() {
+ var rect = document.querySelector("button").getBoundingClientRect();
+ return {
+ x: rect.x, y: rect.y, width: rect.width, height: rect.height
+ };
+ }
+
+ function onMessageReceived(e) {
+ if (!e.data || !e.data.type)
+ return;
+ switch(e.data.type) {
+ case "scroll":
+ document.querySelector("button").scrollIntoView({behavior: "instant"});
+ ackMessage({bounds: scrollingElementBounds()}, e.source);
+ break;
+
+ case "scrolling-element-bounds":
+ ackMessage({bounds: scrollingElementBounds()}, e.source);
+ break;
+ }
+ }
+
+ function ackMessage(msg, source) {
+ source.postMessage(msg, "*");
+ }
+</script>
diff --git a/testing/web-platform/tests/permissions-policy/experimental-features/resources/vertical-scroll-touch-action.html b/testing/web-platform/tests/permissions-policy/experimental-features/resources/vertical-scroll-touch-action.html
new file mode 100644
index 0000000000..51b715f30a
--- /dev/null
+++ b/testing/web-platform/tests/permissions-policy/experimental-features/resources/vertical-scroll-touch-action.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<style>
+ body, html {
+ height: 100%;
+ width: 100%;
+ overflow: hidden;
+ }
+ body {
+ touch-action: none;
+ }
+</style>
+<body>
+ <p>This page blocks all 'touch-action'.</p>
+</body>
diff --git a/testing/web-platform/tests/permissions-policy/experimental-features/resources/vertical-scroll-touch-block.html b/testing/web-platform/tests/permissions-policy/experimental-features/resources/vertical-scroll-touch-block.html
new file mode 100644
index 0000000000..4c204055af
--- /dev/null
+++ b/testing/web-platform/tests/permissions-policy/experimental-features/resources/vertical-scroll-touch-block.html
@@ -0,0 +1,42 @@
+<!DOCTYPE html>
+<style>
+body, div, html {
+ height: 100%;
+ width: 100%;
+ overflow: hidden;
+}
+p {
+ margin-bottom: 1000px;
+ margin-right: 1000px;
+}
+</style>
+<body>
+ <div id="blocker">
+ <p>This page blocks 'touchstart' and 'touchmove'.</p>
+ </div>
+ <script>
+ function doSomethingUnimportant(e) {
+ return false !== e;
+ }
+
+ function preventDefault(e) {
+ e.preventDefault();
+ }
+
+ document.addEventListener("touchstart", doSomethingUnimportant, {passive: false});
+ document.addEventListener("touchmove", doSomethingUnimportant, {passive: false});
+
+ function messageHandler(e) {
+ let msg = e.data;
+ let element = document.querySelector(msg.query);
+ if (msg.prevent) {
+ element.addEventListener(msg.event_type, preventDefault, {passive: false});
+ } else {
+ element.addEventListener(msg.event_type, doSomethingUnimportant);
+ }
+ e.source.postMessage(msg, "*");
+ }
+
+ window.addEventListener("message", messageHandler);
+ </script>
+</body>
diff --git a/testing/web-platform/tests/permissions-policy/experimental-features/resources/vertical-scroll-wheel-block.html b/testing/web-platform/tests/permissions-policy/experimental-features/resources/vertical-scroll-wheel-block.html
new file mode 100644
index 0000000000..21fc2b9b39
--- /dev/null
+++ b/testing/web-platform/tests/permissions-policy/experimental-features/resources/vertical-scroll-wheel-block.html
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<style>
+ body, html {
+ height: 100%;
+ width: 100%;
+ overflow: hidden;
+ }
+</style>
+<body>
+ <p>This page blocks all 'mouse-wheel'.</p>
+<script>
+ function defaultScroll() {
+ window.scrollTo(0, 0);
+ }
+
+ document.body.addEventListener(
+ "wheel",
+ (e) => { e.preventDefault(); defaultScroll(); }, {passive: false});
+
+ defaultScroll();
+</script>
+</body>
diff --git a/testing/web-platform/tests/permissions-policy/experimental-features/resources/vertical-scroll.js b/testing/web-platform/tests/permissions-policy/experimental-features/resources/vertical-scroll.js
new file mode 100644
index 0000000000..88835cc602
--- /dev/null
+++ b/testing/web-platform/tests/permissions-policy/experimental-features/resources/vertical-scroll.js
@@ -0,0 +1,25 @@
+function rectMaxY(rect) {
+ return rect.height + rect.y;
+}
+
+function rectMaxX(rect) {
+ return rect.width + rect.x;
+}
+
+function isEmptyRect(rect) {
+ return !rect.width || !rect.height;
+}
+
+// Returns true if the given rectangles intersect.
+function rects_intersect(rect1, rect2) {
+ if (isEmptyRect(rect1) || isEmptyRect(rect2))
+ return false;
+ return rect1.x < rectMaxX(rect2) &&
+ rect2.x < rectMaxX(rect1) &&
+ rect1.y < rectMaxY(rect2) &&
+ rect2.y < rectMaxY(rect1);
+}
+
+function rectToString(rect) {
+ return `Location: (${rect.x}, ${rect.y}) Size: (${rect.width}, ${rect.height})`;
+}
diff --git a/testing/web-platform/tests/permissions-policy/experimental-features/trust-token-redemption-default-permissions-policy.tentative.https.sub.html b/testing/web-platform/tests/permissions-policy/experimental-features/trust-token-redemption-default-permissions-policy.tentative.https.sub.html
new file mode 100644
index 0000000000..2073687910
--- /dev/null
+++ b/testing/web-platform/tests/permissions-policy/experimental-features/trust-token-redemption-default-permissions-policy.tentative.https.sub.html
@@ -0,0 +1,62 @@
+<!DOCTYPE html>
+<title>Test that trust token redemption is enabled/disabled according to the permissions policy</title>
+
+<body>
+ <script src=/resources/testharness.js></script>
+ <script src=/resources/testharnessreport.js></script>
+ <script src=/permissions-policy/resources/permissions-policy.js></script>
+ <script>
+ 'use strict';
+ const same_origin_src = '/permissions-policy/experimental-features/resources/permissions-policy-trust-token-redemption.html';
+ const cross_origin_src = 'https://{{domains[www]}}:{{ports[https][0]}}' +
+ same_origin_src;
+ const header = 'Default "trust-token-redemption" permissions policy ["self"]';
+
+ test(() => {
+ try {
+ // The permissions policy gates redemption and signing via both the Fetch
+ // and XHR interfaces.
+ new Request("https://issuer.example/", {
+ 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/permissions-policy/experimental-features/trust-token-redemption-supported-by-permissions-policy.tentative.html b/testing/web-platform/tests/permissions-policy/experimental-features/trust-token-redemption-supported-by-permissions-policy.tentative.html
new file mode 100644
index 0000000000..e349eadc5d
--- /dev/null
+++ b/testing/web-platform/tests/permissions-policy/experimental-features/trust-token-redemption-supported-by-permissions-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/permissions-policy/experimental-features/unload-allowed-by-default.tentative.window.js b/testing/web-platform/tests/permissions-policy/experimental-features/unload-allowed-by-default.tentative.window.js
new file mode 100644
index 0000000000..3fdc9ed047
--- /dev/null
+++ b/testing/web-platform/tests/permissions-policy/experimental-features/unload-allowed-by-default.tentative.window.js
@@ -0,0 +1,22 @@
+// META: title='unload' Policy : allowed by default
+// META: script=/common/dispatcher/dispatcher.js
+// META: script=/common/get-host-info.sub.js
+// META: script=/common/utils.js
+// META: script=/html/browsers/browsing-the-web/remote-context-helper/resources/remote-context-helper.js
+// META: script=./resources/unload-helper.js
+// META: timeout=long
+
+'use strict';
+
+// Check that unload is allowed by policy in main frame and subframe by default.
+promise_test(async t => {
+ const rcHelper =
+ new RemoteContextHelper({scripts: ['./resources/unload-helper.js']});
+ // In the same browsing context group to ensure BFCache is not used.
+ const main = await rcHelper.addWindow();
+ const sameOriginSubframe = await main.addIframe();
+ const crossOriginSubframe = await main.addIframe({ origin: 'HTTP_REMOTE_ORIGIN' });
+ await assertWindowRunsUnload(sameOriginSubframe, 'sameOriginSubframe', { shouldRunUnload: true });
+ await assertWindowRunsUnload(crossOriginSubframe, 'crossOriginSubframe', { shouldRunUnload: true });
+ await assertWindowRunsUnload(main, 'main', {shouldRunUnload: true});
+});
diff --git a/testing/web-platform/tests/permissions-policy/experimental-features/unload-disallowed-subframe.tentative.window.js b/testing/web-platform/tests/permissions-policy/experimental-features/unload-disallowed-subframe.tentative.window.js
new file mode 100644
index 0000000000..b2fb19ae12
--- /dev/null
+++ b/testing/web-platform/tests/permissions-policy/experimental-features/unload-disallowed-subframe.tentative.window.js
@@ -0,0 +1,21 @@
+// META: title='unload' Policy : allowed in main frame but disallowed in subframe
+// META: script=/common/dispatcher/dispatcher.js
+// META: script=/common/utils.js
+// META: script=/html/browsers/browsing-the-web/remote-context-helper/resources/remote-context-helper.js
+// META: script=./resources/unload-helper.js
+// META: timeout=long
+
+'use strict';
+
+// Check that unload is allowed by policy in main but can be disabled in the
+// subframe.
+promise_test(async t => {
+ const rcHelper =
+ new RemoteContextHelper({scripts: ['./resources/unload-helper.js']});
+ // In the same browsing context group to ensure BFCache is not used.
+ const main = await rcHelper.addWindow();
+ const subframe =
+ await main.addIframe({headers: [['Permissions-Policy', 'unload=()']]});
+ await assertWindowRunsUnload(subframe, 'subframe', {shouldRunUnload: false});
+ await assertWindowRunsUnload(main, 'main', {shouldRunUnload: true});
+});
diff --git a/testing/web-platform/tests/permissions-policy/experimental-features/unload-disallowed.tentative.window.js b/testing/web-platform/tests/permissions-policy/experimental-features/unload-disallowed.tentative.window.js
new file mode 100644
index 0000000000..c93443c101
--- /dev/null
+++ b/testing/web-platform/tests/permissions-policy/experimental-features/unload-disallowed.tentative.window.js
@@ -0,0 +1,21 @@
+// META: title='unload' Policy : disallowed when header is ()
+// META: script=/common/dispatcher/dispatcher.js
+// META: script=/common/utils.js
+// META: script=/html/browsers/browsing-the-web/remote-context-helper/resources/remote-context-helper.js
+// META: script=./resources/unload-helper.js
+// META: timeout=long
+
+'use strict';
+
+// Check that unload can be disabled by policy in main frame and subframe.
+promise_test(async t => {
+ const rcHelper =
+ new RemoteContextHelper({scripts: ['./resources/unload-helper.js']});
+ // In the same browsing context group to ensure BFCache is not used.
+ const main = await rcHelper.addWindow(
+ {headers: [['Permissions-Policy', 'unload=()']]},
+ );
+ const subframe = await main.addIframe();
+ await assertWindowRunsUnload(subframe, 'subframe', {shouldRunUnload: false});
+ await assertWindowRunsUnload(main, 'main', {shouldRunUnload: false});
+});
diff --git a/testing/web-platform/tests/permissions-policy/experimental-features/vertical-scroll-disabled-frame-no-scroll-manual.tentative.html b/testing/web-platform/tests/permissions-policy/experimental-features/vertical-scroll-disabled-frame-no-scroll-manual.tentative.html
new file mode 100644
index 0000000000..67cae05ee8
--- /dev/null
+++ b/testing/web-platform/tests/permissions-policy/experimental-features/vertical-scroll-disabled-frame-no-scroll-manual.tentative.html
@@ -0,0 +1,113 @@
+<!doctype html>
+<meta name="timeout" content="long">
+<title>vertical-scroll test for touch-action</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/permissions-policy/experimental-features/resources/common.js"></script>
+<script src="/permissions-policy/experimental-features/resources/vertical-scroll.js"></script>
+<style>
+html, body {
+ height: 100%;
+ width: 100%;
+}
+
+iframe {
+ width: 90%;
+ height: 90%;
+ margin: 0;
+ padding: 0;
+}
+
+.spacer {
+ width: 100%;
+ height: 100%;
+ margin: 100%;
+}
+</style>
+<iframe></iframe>
+<br/>
+<p>Spacers below to make page scrollable</p>
+<br/>
+<div class="spacer"></div>
+<div class="spacer"></div>
+<p> EOF </p>
+
+<script>
+ "use strict";
+
+ let url = url_base + "vertical-scroll-scrollable-content.html";
+ let iframeElement = document.querySelector("iframe");
+
+ // Wait for the helper scripts to load.
+ promise_test(async() => {
+ if (window.input_api_ready)
+ return Promise.resolve();
+ await new Promise( (r) => {
+ window.resolve_on_input_api_ready = r;
+ });
+ }, "Make sure input injection API is ready.");
+
+ // Sanity-check: Verify we can scroll using the test-API (empty <iframe>).
+ promise_test(async() => {
+ window.scrollTo(0, 0);
+
+ await inject_input("down");
+ assert_greater_than(window.scrollY, 0, "Page must have scrolled down.");
+
+ await inject_input("right");
+ assert_greater_than(window.scrollX, 0, "Page must have scrolled right.");
+ }, "Verify that the page normally scrolls.");
+
+ // Sanity-check: <iframe> normally scrolls.
+ promise_test(async() => {
+ // Make sure <window> can scroll both towards right and bottom.
+ window.scrollTo(0, 0);
+
+ await loadUrlInIframe(iframeElement, url);
+ iframeElement.contentWindow.scrollTo(0, 0);
+
+ await inject_input("down");
+ assert_greater_than(
+ iframeElement.contentWindow.scrollY,
+ 0,
+ "<iframe> must have scrolled down.");
+
+
+ // Apply the scroll gesture.
+ await inject_input("right");
+ assert_greater_than(
+ iframeElement.contentWindow.scrollX,
+ 0,
+ "<iframe> must have scrolled right.");
+
+ }, "Verify that the <iframe> normally scrolls.");
+
+ // Disable 'vertical-scroll': <iframe> should only scroll horizontally.
+ promise_test(async() => {
+ window.scrollTo(0, 0);
+
+ // Disallow vertical scroll and reload the <iframe>.
+ setFeatureState(iframeElement, "vertical-scroll", "'none'");
+ await loadUrlInIframe(iframeElement, url);
+ iframeElement.contentWindow.scrollTo(0, 0);
+
+ // Apply the scroll gesture. Main frame should and <iframe> should not
+ // scroll vertically.
+ await inject_input("down");
+ assert_equals(iframeElement.contentWindow.scrollY,
+ 0,
+ "<iframe> must not scroll vertically.");
+ assert_greater_than(window.scrollY,
+ 0,
+ "Page must scroll vertically.");
+
+ window.scrollTo(0, 0);
+ iframeElement.contentWindow.scrollTo(0, 0);
+
+ await inject_input("right");
+ assert_greater_than(iframeElement.contentWindow.scrollX,
+ 0,
+ "<iframe> must have scrolled right.");
+ }, "When 'vertical-scroll' is disabled in a document, scrollable contents " +
+ "can only *horizontally* scroll.");
+</script>
diff --git a/testing/web-platform/tests/permissions-policy/experimental-features/vertical-scroll-disabled-scrollbar-tentative.html b/testing/web-platform/tests/permissions-policy/experimental-features/vertical-scroll-disabled-scrollbar-tentative.html
new file mode 100644
index 0000000000..6522254076
--- /dev/null
+++ b/testing/web-platform/tests/permissions-policy/experimental-features/vertical-scroll-disabled-scrollbar-tentative.html
@@ -0,0 +1,4 @@
+<!doctype html>
+<title>vertical-scroll test for vertical scrollbar</title>
+<link rel="match" href="/permissions-policy/experimental-features/resources/vertical-scroll-scrollbar-ref.html?no-vertical-scrollbar">
+<iframe src="/permissions-policy/experimental-features/resources/vertical-scroll-scrollable-content.html" allow="vertical-scroll 'none'"></iframe>
diff --git a/testing/web-platform/tests/permissions-policy/experimental-features/vertical-scroll-main-frame-manual.tentative.html b/testing/web-platform/tests/permissions-policy/experimental-features/vertical-scroll-main-frame-manual.tentative.html
new file mode 100644
index 0000000000..cda6c49abb
--- /dev/null
+++ b/testing/web-platform/tests/permissions-policy/experimental-features/vertical-scroll-main-frame-manual.tentative.html
@@ -0,0 +1,46 @@
+<!DOCTYPE html>
+<title>Ensure 'vertical-scroll' does not affect main frame</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/permissions-policy/experimental-features/resources/common.js"></script>
+<script src="/permissions-policy/experimental-features/resources/vertical-scroll.js"></script>
+<style>
+html, body {
+ height: 100%;
+ width: 100%;
+}
+.spacer {
+ width: 100%;
+ height: 100%;
+ margin-top: 100%;
+ margin-bottom: 100%;
+}
+</style>
+<p> Making sure there is room for vertical scroll </p>
+<div class="spacer"></div>
+<div class="spacer"></div>
+</p>EOP</p>
+<script>
+ "use strict";
+
+ // Sanity check.
+ test(() => {
+ assert_false(document.featurePolicy.allowsFeature("vertical-scroll"),
+ "Expected 'vertical-scroll' to be disabled.");
+ }, "'vertical-scroll' disabled in main document.");
+
+ // Wait for the helper scripts to load.
+ promise_test(async() => {
+ if (window.input_api_ready)
+ return Promise.resolve();
+ await new Promise( (r) => {
+ window.resolve_on_input_api_ready = r;
+ });
+ }, "Make sure input injection API is ready.");
+
+ promise_test(async() => {
+ window.scrollTo(0, 0);
+ await inject_input("down");
+ assert_greater_than(window.scrollY, 0, "Page must have scrolled down.");
+ }, "Verify that the page scrolls vertically.");
+</script>
diff --git a/testing/web-platform/tests/permissions-policy/experimental-features/vertical-scroll-main-frame-manual.tentative.html.headers b/testing/web-platform/tests/permissions-policy/experimental-features/vertical-scroll-main-frame-manual.tentative.html.headers
new file mode 100644
index 0000000000..8d9c01e148
--- /dev/null
+++ b/testing/web-platform/tests/permissions-policy/experimental-features/vertical-scroll-main-frame-manual.tentative.html.headers
@@ -0,0 +1 @@
+Permissions-Policy: vertical-scroll=()
diff --git a/testing/web-platform/tests/permissions-policy/experimental-features/vertical-scroll-scrollintoview.tentative.html b/testing/web-platform/tests/permissions-policy/experimental-features/vertical-scroll-scrollintoview.tentative.html
new file mode 100644
index 0000000000..4f78dd914a
--- /dev/null
+++ b/testing/web-platform/tests/permissions-policy/experimental-features/vertical-scroll-scrollintoview.tentative.html
@@ -0,0 +1,117 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/permissions-policy/experimental-features/resources/common.js"></script>
+<script src="/permissions-policy/experimental-features/resources/vertical-scroll.js"></script>
+<style>
+html, body {
+ height: 100%;
+ width: 100%;
+}
+
+iframe {
+ width: 95%;
+ height: 95%;
+ overflow: scroll;
+ margin-top: 200%;
+}
+
+.spacer {
+ width: 100%;
+ height: 100%;
+ margin-top: 100%;
+ margin-bottom: 100%;
+}
+
+</style>
+<p> An &lt;iframe&gt; further below which is not allowed to block scroll.</p>
+<div class="spacer"></div>
+<iframe></iframe>
+<p> Making sure there is room for vertical scroll </p>
+<script>
+ "use strict";
+
+ let url = url_base + "vertical-scroll-scrollintoview.html";
+ let iframeElement = document.querySelector("iframe");
+
+ function iframeBounds() {
+ return iframeElement.getBoundingClientRect();
+ }
+
+ // Enabled 'vertical-scroll': scrollIntoView should work in all frames.
+ promise_test(async() => {
+ window.scrollTo(0, 0);
+ await loadUrlInIframe(iframeElement, url);
+
+ await sendMessageAndGetResponse(
+ iframeElement.contentWindow,
+ {type: "scrolling-element-bounds"}).then((response) => {
+ let iframeBoundsAtOrigin = {
+ x: 0,
+ y: 0,
+ width: iframeBounds().width,
+ height: iframeBounds().height};
+ let scrollingElementBounds = response.bounds;
+ assert_false(
+ rects_intersect(iframeBoundsAtOrigin, scrollingElementBounds),
+ "Scrolling element should not be visible in <iframe>." +
+ `Scrolling element's bounds is: ${rectToString(response.bounds)} ` +
+ "but <iframe>'s size is:" +
+ `${iframeBounds().width}x${iframeBounds().height}.`);
+ });
+
+ // Scroll the scrolling element inside the <iframe>.
+ await sendMessageAndGetResponse(iframeElement.contentWindow,
+ {type: "scroll"});
+ // The page should have scrolled vertically.
+ assert_greater_than(window.scrollY,
+ 0,
+ "Main frame must scroll vertically.");
+ }, "Calling 'scrollIntoView()' inside a <iframe> will propagate up by" +
+ " default('vertical-scroll' enabled).");
+
+ // Disabled 'vertical-scroll': The scope of scrollIntoView is within the set
+ // of disabled frames (does not propagate to main frame).
+ promise_test(async() => {
+ window.scrollTo(0, 0);
+ setFeatureState(iframeElement, "vertical-scroll", "'none'");
+ await loadUrlInIframe(iframeElement, url);
+
+ await sendMessageAndGetResponse(
+ iframeElement.contentWindow,
+ {type: "scrolling-element-bounds"}).then((response) => {
+ let iframeBoundsAtOrigin = {
+ x: 0,
+ y: 0,
+ width: iframeBounds().width,
+ height: iframeBounds().height};
+ let scrollingElementBounds = response.bounds;
+ assert_false(rects_intersect(iframeBoundsAtOrigin, scrollingElementBounds),
+ "Scrolling element should not be visible in <iframe>." +
+ `Scrolling element's bounds is: ${rectToString(response.bounds)}` +
+ "but <iframe>'s size is:" +
+ `${iframeBounds().width}x${iframeBounds().height}.`);
+ });
+
+ // Scroll scrolling element inside the <iframe>.
+ await sendMessageAndGetResponse(iframeElement.contentWindow,
+ {type: "scroll"}).then((response) => {
+ // Make sure the nested <iframe> is visible.
+ let scrollingElementBounds = response.bounds;
+ let iframeBoundsAtOrigin = {
+ x: 0,
+ y: 0,
+ width: iframeBounds().width,
+ height: iframeBounds().height};
+ // The scrolling element should be visible inside <iframe>.
+ assert_true(rects_intersect(iframeBoundsAtOrigin, scrollingElementBounds),
+ "Scrolling element should be visible in <iframe>." +
+ `Scrolling element's bounds is: ${rectToString(response.bounds)}` +
+ "but <iframe>'s size is:" +
+ `${iframeBounds().width}, ${iframeBounds().height}.`);
+ // The page however should not have scrolled.
+ assert_equals(window.scrollY, 0, "Main frame must not scroll vertically.");
+ });
+ }, "Calling 'scrollIntoView()' inside a <iframe> with" +
+ " 'vertical-scroll none;'will not propagate upwards.");
+</script>
diff --git a/testing/web-platform/tests/permissions-policy/experimental-features/vertical-scroll-touch-action-manual.tentative.html b/testing/web-platform/tests/permissions-policy/experimental-features/vertical-scroll-touch-action-manual.tentative.html
new file mode 100644
index 0000000000..8c2eec93b7
--- /dev/null
+++ b/testing/web-platform/tests/permissions-policy/experimental-features/vertical-scroll-touch-action-manual.tentative.html
@@ -0,0 +1,103 @@
+<!doctype html>
+<meta name="timeout" content="long">
+<title>vertical-scroll test for touch-action</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/permissions-policy/experimental-features/resources/common.js"></script>
+<script src="/permissions-policy/experimental-features/resources/vertical-scroll.js"></script>
+<style>
+html, body {
+ height: 100%;
+ width: 100%;
+}
+
+iframe {
+ width: 90%;
+ height: 90%;
+ margin: 0;
+ padding: 0;
+}
+
+.spacer {
+ width: 100%;
+ height: 100%;
+ margin: 100%;
+}
+</style>
+<iframe></iframe>
+<br/>
+<p>Spacers below to make page scrollable</p>
+<br/>
+<div class="spacer"></div>
+<div class="spacer"></div>
+<p> EOF </p>
+
+<script>
+ "use strict";
+
+ let url = url_base + "vertical-scroll-touch-action.html";
+ let iframeElement = document.querySelector("iframe");
+
+ // Wait for the helper scripts to load.
+ promise_test(async() => {
+ if (window.input_api_ready)
+ return Promise.resolve();
+ await new Promise( (r) => {
+ window.resolve_on_input_api_ready = r;
+ });
+ }, "Make sure input injection API is ready.");
+
+ // Sanity-check: Verify we can scroll using the test-API (empty <iframe>).
+ promise_test(async() => {
+ window.scrollTo(0, 0);
+
+ await inject_input("down");
+ assert_greater_than(window.scrollY, 0, "Page must have scrolled down.");
+
+ await inject_input("right");
+ assert_greater_than(window.scrollX, 0, "Page must have scrolled right.");
+ }, "Verify that the page normally scrolls.");
+
+ // Enable 'vertical-scroll': "touch-action" should be able to block scroll.
+ promise_test(async() => {
+ // Make sure <window> can scroll both towards right and bottom.
+ window.scrollTo(0, 0);
+
+ await loadUrlInIframe(iframeElement, url);
+
+ // Apply the scroll gesture.
+ await inject_input("down");
+
+ // The scroll should normally bubble and affect this page, but the <iframe>
+ // is allowed to block it.
+ assert_equals(window.scrollY,
+ 0,
+ "Main frame must not scroll vertically");
+ }, "Vertical scrolling through 'touch-action' can normally be blocked if" +
+ " 'pan-y' is not present.");
+
+ // Disable 'vertical-scroll': "touch-action" should not be able to block
+ // scroll.
+ promise_test(async() => {
+ window.scrollTo(0, 0);
+
+ // Disallow vertical scroll and reload the <iframe>.
+ setFeatureState(iframeElement, "vertical-scroll", "'none'");
+ await loadUrlInIframe(iframeElement, url);
+
+ // Apply the scroll gesture. Main frame should scroll vertically.
+ await inject_input("down");
+
+ assert_greater_than(window.scrollY,
+ 0,
+ "Main frame must scroll vertically.");
+ window.scrollTo(0, 0);
+
+ await inject_input("right");
+ assert_equals(
+ window.scrollX,
+ 0,
+ "'pan-x' can be blocked even when 'vertical-scroll' is disabled");
+ }, "Vertical scrolling (and only vertical scrolling) through 'touch-action'" +
+ " cannot be blocked by a disabled frame.");
+</script>
diff --git a/testing/web-platform/tests/permissions-policy/experimental-features/vertical-scroll-touch-block-manual.tentative.html b/testing/web-platform/tests/permissions-policy/experimental-features/vertical-scroll-touch-block-manual.tentative.html
new file mode 100644
index 0000000000..341e543934
--- /dev/null
+++ b/testing/web-platform/tests/permissions-policy/experimental-features/vertical-scroll-touch-block-manual.tentative.html
@@ -0,0 +1,237 @@
+<!doctype html>
+<title>vertical-scroll test for touch-action</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/permissions-policy/experimental-features/resources/common.js"></script>
+<script src="/permissions-policy/experimental-features/resources/vertical-scroll.js"></script>
+<style>
+html, body {
+ height: 100%;
+ width: 100%;
+}
+
+iframe {
+ width: 90%;
+ height: 90%;
+ margin: 0;
+ padding: 0;
+}
+
+.spacer {
+ width: 100%;
+ height: 100%;
+ margin: 100%;
+}
+</style>
+<iframe></iframe>
+<br/>
+<p>Spacers below to make page scrollable</p>
+<br/>
+<div class="spacer"></div>
+<div class="spacer"></div>
+<p> EOF </p>
+
+<script>
+ "use strict";
+
+ let url = url_base + "vertical-scroll-touch-block.html";
+ let iframeElement = document.querySelector("iframe");
+
+ // Wait for the helper scripts to load.
+ promise_test(async() => {
+ if (window.input_api_ready)
+ return Promise.resolve();
+ await new Promise( (r) => {
+ window.resolve_on_input_api_ready = r;
+ });
+
+ }, "Make sure input injection API is ready.");
+
+ // Sanity-check: Verify we can scroll using the test-API. The <iframe> with no
+ // query arguments would not preventDefault() any events.
+ promise_test(async() => {
+ window.scrollTo(0, 0);
+ await loadUrlInIframe(iframeElement, url);
+
+ await inject_scroll("down");
+ assert_greater_than(window.scrollY, 0, "Page must have scrolled down.");
+
+ await inject_scroll("right");
+ assert_greater_than(window.scrollX, 0, "Page must have scrolled right.");
+ }, "Verify that the page normally scrolls.");
+
+ // Enable 'vertical-scroll': Blocking "touchstart" can block scrolling.
+ promise_test(async() => {
+ window.scrollTo(0, 0);
+ setFeatureState(iframeElement, "vertical-scroll", "*");
+ await loadUrlInIframe(iframeElement, url);
+ await sendMessageAndGetResponse(iframeElement.contentWindow,
+ {
+ event_type: "touchstart",
+ query: "#blocker",
+ prevent: true
+ });
+
+ await inject_scroll("down");
+ assert_equals(window.scrollY,
+ 0,
+ "Main frame must not scroll vertically.");
+ }, "Calling 'preventDefault' on 'touchstart' blocks vertical scroll when " +
+ " the feature is enabled.");
+
+ // Disable 'vertical-scroll': Blocking "touchstart" cannot block vertical
+ // scroll.
+ promise_test(async() => {
+ window.scrollTo(0, 0);
+ setFeatureState(iframeElement, "vertical-scroll", "'none'");
+ await loadUrlInIframe(iframeElement, url);
+ await sendMessageAndGetResponse(iframeElement.contentWindow,
+ {
+ event_type: "touchstart",
+ query: "#blocker",
+ prevent: true
+ });
+
+ await inject_scroll("down");
+ assert_greater_than(window.scrollY,
+ 0,
+ "Main frame must scroll vertically.");
+
+ // Horizontal scrolling must still be blocked.
+ window.scrollTo(0, 0);
+ await inject_scroll("right");
+ assert_equals(window.scrollX,
+ 0,
+ "Main frame must not scroll horizontally.");
+ }, "When feature is disabled, calling 'preventDefault' on 'touchstart' does" +
+ " not block vertical scroll, but still bocks horizontal scroll.");
+
+ // Enable 'vertical-scroll': Blocking "touchmove" can block scrolling.
+ promise_test(async() => {
+ // Make sure <window> can scroll both towards right and bottom.
+ window.scrollTo(0, 0);
+ setFeatureState(iframeElement, "vertical-scroll", "*");
+ await loadUrlInIframe(iframeElement, url);
+ await sendMessageAndGetResponse(iframeElement.contentWindow,
+ {
+ event_type: "touchmove",
+ query: "#blocker",
+ prevent: true
+ });
+
+ await inject_scroll("down");
+ assert_equals(window.scrollY,
+ 0,
+ "Main frame must not scroll vertically");
+ }, "Calling 'preventDefault' on 'touchmove' blocks vertical scroll when " +
+ "the feature is enabled.");
+
+ // Disable 'vertical-scroll': Blocking "touchmove" cannot block vertical
+ // scroll.
+ promise_test(async() => {
+ window.scrollTo(0, 0);
+ setFeatureState(iframeElement, "vertical-scroll", "'none'");
+ await loadUrlInIframe(iframeElement, url);
+ await sendMessageAndGetResponse(iframeElement.contentWindow,
+ {
+ event_type: "touchmove",
+ query: "#blocker",
+ prevent: true
+ });
+ await new Promise((r) => {
+ window.setTimeout(r, 100);
+ });
+
+ await inject_scroll("down");
+ assert_greater_than(window.scrollY,
+ 0,
+ "Main frame must scroll vertically.");
+ }, "When feature is disabled, calling 'preventDefault' on 'touchmove' does" +
+ " not block vertical scroll.");
+
+ // Disable 'vertical-scroll': Add a non-preventing handler, and then a
+ // preventing handler and verify vertical scroll is possible and horizontal
+ // is blocked.
+ promise_test(async() => {
+ setFeatureState(iframeElement, "vertical-scroll", "'none'");
+ await loadUrlInIframe(iframeElement, url);
+
+ // Add a non-blocking handler.
+ await sendMessageAndGetResponse(iframeElement.contentWindow,
+ {
+ event_type: "touchstart",
+ query: "#blocker",
+ prevent: false
+ });
+
+ window.scrollTo(0, 0);
+ await inject_scroll("down");
+ assert_greater_than(window.scrollY, 0, "Page must have scrolled down.");
+
+ window.scrollTo(0, 0);
+ await inject_scroll("right");
+ assert_greater_than(window.scrollX, 0, "Page must have scrolled right.");
+
+ // Add a blocking handler.
+ await sendMessageAndGetResponse(iframeElement.contentWindow,
+ {
+ event_type: "touchstart",
+ query: "#blocker",
+ prevent: true
+ });
+ window.scrollTo(0, 0);
+ await inject_scroll("down");
+ assert_greater_than(window.scrollY,
+ 0,
+ "Page must have scrolled down (preventing handler).");
+
+ window.scrollTo(0, 0);
+ await inject_scroll("right");
+ assert_equals(window.scrollX,
+ 0,
+ "Page must not have scrolled horizontally.");
+ }, "Verify that starting with a non-preventing listener and then switching" +
+ " to a preventing one works as intended.");
+
+ // Disable 'vertical-scroll': A preventing 'touchstart' handler does not allow
+ // other multi-touch actions such as 'pinch-zoom'.
+ promise_test(async() => {
+ assert_true(!!window.visualViewport,
+ "'visualViewport' is needed for this test.");
+
+ setFeatureState(iframeElement, "vertical-scroll", "'none'");
+ await loadUrlInIframe(iframeElement, url);
+ await sendMessageAndGetResponse(iframeElement.contentWindow,
+ {
+ event_type: "touchstart",
+ query: "#blocker",
+ prevent: false
+ });
+
+ // Sanity check: Pinch zoom in should work since 'touchstart' is not
+ // preventing.
+ let current_scale_factor = window.visualViewport.scale;
+ await inject_zoom("in");
+ let new_scale_factor = window.visualViewport.scale;
+ assert_greater_than(
+ new_scale_factor,
+ current_scale_factor,
+ "Pinch zoom should work since 'touchstart' is not prevented.");
+
+ // Add a preventing handler and verify pinch zoom is now blocked.
+ await sendMessageAndGetResponse(iframeElement.contentWindow,
+ {
+ event_type: "touchstart",
+ query: "#blocker",
+ prevent: true
+ });
+ current_scale_factor = new_scale_factor;
+ await inject_zoom("out");
+ new_scale_factor = window.visualViewport.scale;
+ assert_equals(
+ new_scale_factor,
+ current_scale_factor,
+ "Pinch zoom must be blocked.");
+ }, "Verify that pinch zoom is blocked in frames with 'vertical-scroll' none'" +
+ " where 'touchstart' is prevent defaulted.");
+</script>
diff --git a/testing/web-platform/tests/permissions-policy/experimental-features/vertical-scroll-wheel-block-manual.tentative.html b/testing/web-platform/tests/permissions-policy/experimental-features/vertical-scroll-wheel-block-manual.tentative.html
new file mode 100644
index 0000000000..2627fd4154
--- /dev/null
+++ b/testing/web-platform/tests/permissions-policy/experimental-features/vertical-scroll-wheel-block-manual.tentative.html
@@ -0,0 +1,145 @@
+<!doctype html>
+<title>vertical-scroll test for 'mousewheel'</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/permissions-policy/experimental-features/resources/common.js"></script>
+<script src="/permissions-policy/experimental-features/resources/vertical-scroll.js"></script>
+<style>
+html, body {
+ height: 100%;
+ width: 100%;
+}
+
+iframe {
+ width: 90%;
+ height: 90%;
+ margin: 0;
+ padding: 0;
+}
+
+.spacer {
+ width: 100%;
+ height: 100%;
+ margin: 100%;
+}
+
+</style>
+<iframe></iframe>
+<br/>
+<p>Spacers below to make page scrollable</p>
+<br/>
+<div class="spacer"></div>
+<div class="spacer"></div>
+<p> EOF </p>
+<script>
+ "use strict";
+
+ let url = url_base + "vertical-scroll-wheel-block.html";
+ function iframeElement() {
+ return document.querySelector("iframe");
+ }
+
+ // Used as the polling interval when waiting for a specific condition.
+ let verify_scroll_offset_delay = 5;
+ let no_scroll_timout = 50;
+
+ function waitUntilSatisfied(testInstance, predicate) {
+ return new Promise((r) => {
+ function testPredicate() {
+ if (predicate()) {
+ r();
+ } else {
+ testInstance.step_timeout(testPredicate, verify_scroll_offset_delay);
+ }
+ }
+ testPredicate();
+ });
+ }
+
+ function resetScroll(testInstance) {
+ window.scrollTo({top: 0, left: 0, behavior: "instant"});
+ return waitUntilSatisfied(testInstance, () => {
+ return window.scrollX === 0 && window.scrollY === 0;
+ });
+ }
+
+ function waitForMinimumScrollOffset(testInstance, minX, minY) {
+ return waitUntilSatisfied(testInstance, () => {
+ return window.scrollX >= minX && window.scrollY >= minY;
+ });
+ }
+
+ function waitFor(testInstance, delay) {
+ let checked_once = false;
+ return waitUntilSatisfied(testInstance, () => {
+ if (checked_once)
+ return true;
+ checked_once = true;
+ return false;
+ });
+ }
+
+ // Wait for the helper scripts to load.
+ promise_test(async() => {
+ if (window.input_api_ready)
+ return Promise.resolve();
+ await new Promise((r) => {
+ window.resolve_on_input_api_ready = r;
+ });
+ }, "Make sure input injection API is ready.");
+
+ // Sanity-check: Test API for scrolling along y-axis works as expected.
+ promise_test(async(testInstance) => {
+ await resetScroll(testInstance);
+ await inject_wheel_scroll("down");
+ await waitForMinimumScrollOffset(testInstance, 0, 1);
+ assert_greater_than(window.scrollY, 0, "Expected vertical scroll.");
+ }, "Sanity-check: the page scrolls vertically in response to vertical wheel.");
+
+ // Sanity-check: Test API for scrolling along x-axis works as expected.
+ promise_test(async(testInstance) => {
+ await resetScroll(testInstance);
+ await inject_wheel_scroll("right");
+ await waitForMinimumScrollOffset(testInstance, 1, 0);
+ assert_greater_than(window.scrollX, 0, "Expected horizontal scroll.");
+ }, "Sanity-check: the page scrolls horizontally in response to horizontal wheel.");
+
+ // Test that when 'vertical-scroll' is enabled, vertical scroll can be
+ // blocked by canceling 'wheel' event.
+ promise_test(async(testInstance) => {
+ setFeatureState(iframeElement(), "vertical-scroll", "*");
+ await loadUrlInIframe(iframeElement(), url);
+
+ await resetScroll(testInstance);
+ await inject_wheel_scroll("down")
+ await waitFor(testInstance, no_scroll_timout);
+ assert_equals(window.scrollY, 0, "Did not expected vertical scroll.");
+ }, "When 'vertical-scroll' is enabled canceling vertical 'wheel' " +
+ "blocks vertical scrolling.");
+
+ // Test that when 'vertical-scroll' is disabled, vertical scroll cannot
+ // be blocked by canceling 'wheel' event.
+ promise_test(async(testInstance) => {
+ setFeatureState(iframeElement(), "vertical-scroll", "'none'");
+ await loadUrlInIframe(iframeElement(), url);
+
+ await resetScroll(testInstance);
+ await inject_wheel_scroll("down");
+ await waitForMinimumScrollOffset(testInstance, 0, 1);
+ assert_greater_than(window.scrollY, 0, "Expected vertical scroll.");
+ }, "When 'vertical-scroll' is disabled canceling vertical 'wheel' " +
+ "does not block vertical scrolling.");
+
+ // Test that when 'vertical-scroll' is disabled, horizontal scroll can be
+ // blocked by canceling 'wheel' event.
+ promise_test(async(testInstance) => {
+ setFeatureState(iframeElement(), "vertical-scroll", "'none'");
+ await loadUrlInIframe(iframeElement(), url);
+
+ await resetScroll(testInstance);
+ await inject_wheel_scroll("right");
+ await waitFor(testInstance, no_scroll_timout);
+ assert_equals(window.scrollX, 0, "Did not expect horizontal scroll.");
+ }, "When 'vertical-scroll' is disabled canceling horizontal 'wheel' " +
+ "blocks horizontal scrolling.");
+</script>
diff --git a/testing/web-platform/tests/permissions-policy/idlharness.window.js b/testing/web-platform/tests/permissions-policy/idlharness.window.js
new file mode 100644
index 0000000000..90dbb4961a
--- /dev/null
+++ b/testing/web-platform/tests/permissions-policy/idlharness.window.js
@@ -0,0 +1,20 @@
+// META: script=/resources/WebIDLParser.js
+// META: script=/resources/idlharness.js
+// META: timeout=long
+
+"use strict";
+
+// https://wicg.github.io/permissions-policy/
+
+idl_test(
+ ['permissions-policy'],
+ ['reporting', 'html', 'dom'],
+ idl_array => {
+ idl_array.add_objects({
+ Document: ['document'],
+ HTMLIFrameElement: ['document.createElement("iframe")'],
+ PermissionsPolicy: ['document.permissionsPolicy'],
+ // TODO: PermissionsPolicyViolationReportBody
+ });
+ }
+);
diff --git a/testing/web-platform/tests/permissions-policy/payment-allowed-by-permissions-policy-attribute-redirect-on-load.https.sub.html b/testing/web-platform/tests/permissions-policy/payment-allowed-by-permissions-policy-attribute-redirect-on-load.https.sub.html
new file mode 100644
index 0000000000..e43f4c80ce
--- /dev/null
+++ b/testing/web-platform/tests/permissions-policy/payment-allowed-by-permissions-policy-attribute-redirect-on-load.https.sub.html
@@ -0,0 +1,39 @@
+<!DOCTYPE html>
+<body>
+ <script src=/resources/testharness.js></script>
+ <script src=/resources/testharnessreport.js></script>
+ <script src=/permissions-policy/resources/permissions-policy.js></script>
+ <script>
+ 'use strict';
+ var relative_path = '/permissions-policy/resources/permissions-policy-payment.html';
+ var base_src = '/permissions-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 = 'permissions 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.');
+
+ async_test(t => {
+ test_feature_availability(
+ 'PaymentRequest()', t, same_origin_src,
+ expect_feature_available_default, 'payment', 'allowpaymentrequest');
+ }, header + ' allowpaymentrequest=true allows same-origin navigation in an iframe.');
+
+ async_test(t => {
+ test_feature_availability(
+ 'PaymentRequest()', t, cross_origin_src,
+ expect_feature_unavailable_default, 'payment', 'allowpaymentrequest');
+ }, header + ' allowpaymentrequest=true disallows cross-origin navigation in an iframe.');
+ </script>
+</body>
diff --git a/testing/web-platform/tests/permissions-policy/payment-allowed-by-permissions-policy-attribute.https.sub.html b/testing/web-platform/tests/permissions-policy/payment-allowed-by-permissions-policy-attribute.https.sub.html
new file mode 100644
index 0000000000..779e9d666c
--- /dev/null
+++ b/testing/web-platform/tests/permissions-policy/payment-allowed-by-permissions-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=/permissions-policy/resources/permissions-policy.js></script>
+ <script>
+ 'use strict';
+ var same_origin_src = '/permissions-policy/resources/permissions-policy-payment.html';
+ var cross_origin_src = 'https://{{domains[www]}}:{{ports[https][0]}}' +
+ same_origin_src;
+ var feature_name = 'permissions 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/permissions-policy/payment-allowed-by-permissions-policy.https.sub.html b/testing/web-platform/tests/permissions-policy/payment-allowed-by-permissions-policy.https.sub.html
new file mode 100644
index 0000000000..456626c350
--- /dev/null
+++ b/testing/web-platform/tests/permissions-policy/payment-allowed-by-permissions-policy.https.sub.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<body>
+ <script src=/resources/testharness.js></script>
+ <script src=/resources/testharnessreport.js></script>
+ <script src=/permissions-policy/resources/permissions-policy.js></script>
+ <script>
+ 'use strict';
+ var same_origin_src = '/permissions-policy/resources/permissions-policy-payment.html';
+ var cross_origin_src = 'https://{{domains[www]}}:{{ports[https][0]}}' +
+ same_origin_src;
+ var header = 'permissions 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/permissions-policy/payment-allowed-by-permissions-policy.https.sub.html.headers b/testing/web-platform/tests/permissions-policy/payment-allowed-by-permissions-policy.https.sub.html.headers
new file mode 100644
index 0000000000..73449d512d
--- /dev/null
+++ b/testing/web-platform/tests/permissions-policy/payment-allowed-by-permissions-policy.https.sub.html.headers
@@ -0,0 +1 @@
+Permissions-Policy: payment=*
diff --git a/testing/web-platform/tests/permissions-policy/payment-default-permissions-policy.https.sub.html b/testing/web-platform/tests/permissions-policy/payment-default-permissions-policy.https.sub.html
new file mode 100644
index 0000000000..71ab15ae89
--- /dev/null
+++ b/testing/web-platform/tests/permissions-policy/payment-default-permissions-policy.https.sub.html
@@ -0,0 +1,47 @@
+<!DOCTYPE html>
+<body>
+ <script src=/resources/testharness.js></script>
+ <script src=/resources/testharnessreport.js></script>
+ <script src=/permissions-policy/resources/permissions-policy.js></script>
+ <script>
+ 'use strict';
+ var same_origin_src = '/permissions-policy/resources/permissions-policy-payment.html';
+ var cross_origin_src = 'https://{{domains[www]}}:{{ports[https][0]}}' +
+ same_origin_src;
+ var header = 'Default "payment" permissions policy';
+
+ 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.');
+
+ async_test(t => {
+ test_feature_availability(
+ 'PaymentRequest()', t, same_origin_src,
+ expect_feature_available_default, undefined, 'allowpaymentrequest');
+ }, header + ' allowpaymentrequest=true allows same-origin iframes.');
+
+ async_test(t => {
+ test_feature_availability(
+ 'PaymentRequest()', t, cross_origin_src,
+ expect_feature_available_default, undefined, 'allowpaymentrequest');
+ }, header + ' allowpaymentrequest=true allows cross-origin iframes.');
+ </script>
+</body>
diff --git a/testing/web-platform/tests/permissions-policy/payment-disabled-by-permissions-policy.https.sub.html b/testing/web-platform/tests/permissions-policy/payment-disabled-by-permissions-policy.https.sub.html
new file mode 100644
index 0000000000..90f3a0ee03
--- /dev/null
+++ b/testing/web-platform/tests/permissions-policy/payment-disabled-by-permissions-policy.https.sub.html
@@ -0,0 +1,45 @@
+<!DOCTYPE html>
+<body>
+ <script src=/resources/testharness.js></script>
+ <script src=/resources/testharnessreport.js></script>
+ <script src=/permissions-policy/resources/permissions-policy.js></script>
+ <script>
+ 'use strict';
+ var same_origin_src = '/permissions-policy/resources/permissions-policy-payment.html';
+ var cross_origin_src = 'https://{{domains[www]}}:{{ports[https][0]}}' +
+ same_origin_src;
+ var header = 'permissions 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.');
+
+ async_test(t => {
+ test_feature_availability(
+ 'PaymentRequest()', t, same_origin_src,
+ expect_feature_unavailable_default, undefined, 'allowpaymentrequest');
+ }, header + ' allowpaymentrequest=true disallows same-origin iframes.');
+
+ async_test(t => {
+ test_feature_availability(
+ 'PaymentRequest()', t, cross_origin_src,
+ expect_feature_unavailable_default, undefined, 'allowpaymentrequest');
+ }, header + ' allowpaymentrequest=true disallows cross-origin iframes.');
+ </script>
+</body>
diff --git a/testing/web-platform/tests/permissions-policy/payment-disabled-by-permissions-policy.https.sub.html.headers b/testing/web-platform/tests/permissions-policy/payment-disabled-by-permissions-policy.https.sub.html.headers
new file mode 100644
index 0000000000..49f799d138
--- /dev/null
+++ b/testing/web-platform/tests/permissions-policy/payment-disabled-by-permissions-policy.https.sub.html.headers
@@ -0,0 +1 @@
+Permissions-Policy: payment=()
diff --git a/testing/web-platform/tests/permissions-policy/payment-extension-allowed-by-permissions-policy-attribute.https.sub.html b/testing/web-platform/tests/permissions-policy/payment-extension-allowed-by-permissions-policy-attribute.https.sub.html
new file mode 100644
index 0000000000..de55531b6c
--- /dev/null
+++ b/testing/web-platform/tests/permissions-policy/payment-extension-allowed-by-permissions-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=/permissions-policy/resources/permissions-policy.js></script>
+ <script>
+ 'use strict';
+ var same_origin_src = '/permissions-policy/resources/permissions-policy-payment-extension.html';
+ var cross_origin_src = 'https://{{domains[www]}}:{{ports[https][0]}}' +
+ same_origin_src;
+ var feature_name = 'permissions policy "payment"';
+ var header = 'allow="payment" attribute';
+
+ async_test(t => {
+ test_feature_availability_with_post_message_result(
+ t, cross_origin_src, "NotSupportedError#The 'payment' feature is not " +
+ "enabled in this document. Permissions Policy may be used to " +
+ "delegate Web Payment capabilities to cross-origin child frames.");
+ }, feature_name + ' is not supported in cross-origin iframe without ' + header);
+
+ async_test(t => {
+ test_feature_availability_with_post_message_result(
+ t, cross_origin_src, 'OK', 'payment');
+ }, feature_name + ' can be enabled in cross-origin iframe using ' + header);
+ </script>
+</body>
diff --git a/testing/web-platform/tests/permissions-policy/payment-supported-by-permissions-policy.tentative.html b/testing/web-platform/tests/permissions-policy/payment-supported-by-permissions-policy.tentative.html
new file mode 100644
index 0000000000..3b9928fe19
--- /dev/null
+++ b/testing/web-platform/tests/permissions-policy/payment-supported-by-permissions-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-permissions-policy/#dom-permissionspolicy-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.permissionsPolicy.features());
+}, 'document.featurePolicy.features should advertise payment.');
+</script>
diff --git a/testing/web-platform/tests/permissions-policy/permissions-policy-frame-policy-allowed-for-all.https.sub.html b/testing/web-platform/tests/permissions-policy/permissions-policy-frame-policy-allowed-for-all.https.sub.html
new file mode 100644
index 0000000000..329b6a9ea9
--- /dev/null
+++ b/testing/web-platform/tests/permissions-policy/permissions-policy-frame-policy-allowed-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=/permissions-policy/resources/permissions-policy.js></script>
+ <!-- Permissions-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 = '/permissions-policy/resources/permissions-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(Permissions-Policy,fullscreen=';
+ var pipe_end = ';)';
+ var header_policies = ["*", "self", "()"];
+
+ // 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, true);
+ }, '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, true);
+ }, '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].replace(")", "\\)") + 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 = "Permissions-Policy: fullscreen=' + header_policies[j] + ';".');
+ test(function() {
+ test_frame_policy(
+ 'fullscreen',
+ cross_origin_src + pipe_front + header_policies[j].replace(")", "\\)") + 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 = "Permissions-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/permissions-policy/permissions-policy-frame-policy-allowed-for-all.https.sub.html.sub.headers b/testing/web-platform/tests/permissions-policy/permissions-policy-frame-policy-allowed-for-all.https.sub.html.sub.headers
new file mode 100644
index 0000000000..2cbb8a82c6
--- /dev/null
+++ b/testing/web-platform/tests/permissions-policy/permissions-policy-frame-policy-allowed-for-all.https.sub.html.sub.headers
@@ -0,0 +1 @@
+Permissions-Policy: fullscreen=*
diff --git a/testing/web-platform/tests/permissions-policy/permissions-policy-frame-policy-allowed-for-self.https.sub.html b/testing/web-platform/tests/permissions-policy/permissions-policy-frame-policy-allowed-for-self.https.sub.html
new file mode 100644
index 0000000000..1aa219bffe
--- /dev/null
+++ b/testing/web-platform/tests/permissions-policy/permissions-policy-frame-policy-allowed-for-self.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=/permissions-policy/resources/permissions-policy.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 = '/permissions-policy/resources/permissions-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: 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: true, dataOriginTestExpect: false}];
+ var pipe_front = '?pipe=sub|header(Permissions-Policy, fullscreen=';
+ var pipe_end = ';)';
+ var header_policies = ["*", "self", "()"];
+
+ // 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 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 = "Permissions-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 = "Permissions-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/permissions-policy/permissions-policy-frame-policy-allowed-for-self.https.sub.html.sub.headers b/testing/web-platform/tests/permissions-policy/permissions-policy-frame-policy-allowed-for-self.https.sub.html.sub.headers
new file mode 100644
index 0000000000..ff7ae41353
--- /dev/null
+++ b/testing/web-platform/tests/permissions-policy/permissions-policy-frame-policy-allowed-for-self.https.sub.html.sub.headers
@@ -0,0 +1 @@
+Permissions-Policy: fullscreen=self
diff --git a/testing/web-platform/tests/permissions-policy/permissions-policy-frame-policy-allowed-for-some-override.https.sub.html b/testing/web-platform/tests/permissions-policy/permissions-policy-frame-policy-allowed-for-some-override.https.sub.html
new file mode 100644
index 0000000000..250564440f
--- /dev/null
+++ b/testing/web-platform/tests/permissions-policy/permissions-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=/permissions-policy/resources/permissions-policy.js></script>
+ <!-- Permissions-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 = '/permissions-policy/resources/permissions-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 permissions policy with same_origin_src and cross_origin_src.
+ var policies = [
+ {allow: "*", sameOriginTestExpect: true, crossOriginTestExpect: true, crossOrigin1TestExpect: true, 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/permissions-policy/permissions-policy-frame-policy-allowed-for-some-override.https.sub.html.sub.headers b/testing/web-platform/tests/permissions-policy/permissions-policy-frame-policy-allowed-for-some-override.https.sub.html.sub.headers
new file mode 100644
index 0000000000..d3aa9ff66a
--- /dev/null
+++ b/testing/web-platform/tests/permissions-policy/permissions-policy-frame-policy-allowed-for-some-override.https.sub.html.sub.headers
@@ -0,0 +1 @@
+Permissions-Policy: fullscreen=self https://{{domains[www]}}:{{ports[https][0]}} https://www.example.com
diff --git a/testing/web-platform/tests/permissions-policy/permissions-policy-frame-policy-allowed-for-some.https.sub.html b/testing/web-platform/tests/permissions-policy/permissions-policy-frame-policy-allowed-for-some.https.sub.html
new file mode 100644
index 0000000000..a27018e98f
--- /dev/null
+++ b/testing/web-platform/tests/permissions-policy/permissions-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=/permissions-policy/resources/permissions-policy.js></script>
+ <!-- Permissions-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 = '/permissions-policy/resources/permissions-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 permissions policy with same_origin_src and cross_origin_src.
+ var policies = [
+ {allow: "*", sameOriginTestExpect: true, crossOriginTestExpect: true, crossOrigin1TestExpect: true, 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(Permissions-Policy, fullscreen=';
+ var pipe_end = ')';
+ var header_policies = ["*", "self", "()"];
+
+ // 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, true);
+ }, '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 = "Permissions-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 = "Permissions-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 = "Permissions-Policy: fullscreen=' + header_policies[j] + ';".');
+ }
+ }
+ </script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/permissions-policy/permissions-policy-frame-policy-allowed-for-some.https.sub.html.sub.headers b/testing/web-platform/tests/permissions-policy/permissions-policy-frame-policy-allowed-for-some.https.sub.html.sub.headers
new file mode 100644
index 0000000000..af08d49c2e
--- /dev/null
+++ b/testing/web-platform/tests/permissions-policy/permissions-policy-frame-policy-allowed-for-some.https.sub.html.sub.headers
@@ -0,0 +1 @@
+Permissions-Policy: fullscreen=(self "https://{{domains[www]}}:{{ports[https][0]}}" "https://www.example.com")
diff --git a/testing/web-platform/tests/permissions-policy/permissions-policy-frame-policy-disallowed-for-all.https.sub.html b/testing/web-platform/tests/permissions-policy/permissions-policy-frame-policy-disallowed-for-all.https.sub.html
new file mode 100644
index 0000000000..e7869b1ea1
--- /dev/null
+++ b/testing/web-platform/tests/permissions-policy/permissions-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=/permissions-policy/resources/permissions-policy.js></script>
+ <!-- Permissions-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 = '/permissions-policy/resources/permissions-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(Permissions-Policy, fullscreen=';
+ var pipe_end = ';)';
+ var header_policies = ["*", "self", "()"];
+
+ // 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 = "Permissions-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 = "Permissions-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/permissions-policy/permissions-policy-frame-policy-disallowed-for-all.https.sub.html.sub.headers b/testing/web-platform/tests/permissions-policy/permissions-policy-frame-policy-disallowed-for-all.https.sub.html.sub.headers
new file mode 100644
index 0000000000..a65abd6e45
--- /dev/null
+++ b/testing/web-platform/tests/permissions-policy/permissions-policy-frame-policy-disallowed-for-all.https.sub.html.sub.headers
@@ -0,0 +1 @@
+Permissions-Policy: fullscreen=()
diff --git a/testing/web-platform/tests/permissions-policy/permissions-policy-frame-policy-timing-iframe-camera.https.sub.html b/testing/web-platform/tests/permissions-policy/permissions-policy-frame-policy-timing-iframe-camera.https.sub.html
new file mode 100644
index 0000000000..995ac2134e
--- /dev/null
+++ b/testing/web-platform/tests/permissions-policy/permissions-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/permissions-policy/permissions-policy-frame-policy-timing.https.sub.html b/testing/web-platform/tests/permissions-policy/permissions-policy-frame-policy-timing.https.sub.html
new file mode 100644
index 0000000000..5b2a488863
--- /dev/null
+++ b/testing/web-platform/tests/permissions-policy/permissions-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 = 'permissions-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/permissions-policy/permissions-policy-header-policy-allowed-for-all.https.sub.html b/testing/web-platform/tests/permissions-policy/permissions-policy-header-policy-allowed-for-all.https.sub.html
new file mode 100644
index 0000000000..16a2b60c75
--- /dev/null
+++ b/testing/web-platform/tests/permissions-policy/permissions-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=/permissions-policy/resources/permissions-policy.js></script>
+ <!-- Permissions-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 = '/permissions-policy/resources/permissions-policy-allowedfeatures.html';
+ var cross_origin_src = cross_origin + same_origin_src;
+ var header_policy = 'Permissions-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/permissions-policy/permissions-policy-header-policy-allowed-for-all.https.sub.html.sub.headers b/testing/web-platform/tests/permissions-policy/permissions-policy-header-policy-allowed-for-all.https.sub.html.sub.headers
new file mode 100644
index 0000000000..2cbb8a82c6
--- /dev/null
+++ b/testing/web-platform/tests/permissions-policy/permissions-policy-header-policy-allowed-for-all.https.sub.html.sub.headers
@@ -0,0 +1 @@
+Permissions-Policy: fullscreen=*
diff --git a/testing/web-platform/tests/permissions-policy/permissions-policy-header-policy-allowed-for-malformed-wildcard.https.sub.html b/testing/web-platform/tests/permissions-policy/permissions-policy-header-policy-allowed-for-malformed-wildcard.https.sub.html
new file mode 100644
index 0000000000..daa47efb67
--- /dev/null
+++ b/testing/web-platform/tests/permissions-policy/permissions-policy-header-policy-allowed-for-malformed-wildcard.https.sub.html
@@ -0,0 +1,55 @@
+<!DOCTYPE html>
+<body>
+ <script src=/resources/testharness.js></script>
+ <script src=/resources/testharnessreport.js></script>
+ <script src=/permissions-policy/resources/permissions-policy.js></script>
+ <!-- Permissions-Policy: fullscreen=$MALFORMED_WILDCARD_ORIGINS self; -->
+ <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 = '/permissions-policy/resources/permissions-policy-allowedfeatures.html';
+ var cross_origin_src = cross_origin + same_origin_src;
+ var cross_origin_src1 = cross_origin1 + same_origin_src;
+ var header_policy = `Permissions-Policy: fullscreen=("$MALFORMED_WILDCARD_ORIGINS self")`;
+
+ // Test that fullscreen's allowlist lists all the malformed wildcards and self.
+ test(function() {
+ assert_array_equals(
+ document.featurePolicy.getAllowlistForFeature('fullscreen').sort(),
+ ["https://%2A.%2A.{{domains[]}}:{{ports[https][0]}}", "https://example.%2A.{{domains[]}}:{{ports[https][0]}}", "https://{{domains[]}}:{{ports[https][0]}}"].sort());
+ }, header_policy + ' -- test allowlist lists all the malformed wildcards and self.');
+
+ // Test that fullscreen is allowed on same-origin subframes with or without an allow attribute.
+ test_allowed_feature_for_subframe(
+ header_policy + ' -- test fullscreen is allowed on same-origin subframe',
+ 'fullscreen',
+ same_origin_src);
+ test_allowed_feature_for_subframe(
+ header_policy + ' -- test fullscreen is allowed on same-origin subframe with allow attribute',
+ 'fullscreen',
+ same_origin_src,
+ "fullscreen " + same_origin);
+
+ // Test that fullscreen is disallowed 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);
+ test_disallowed_feature_for_subframe(
+ header_policy + ' -- test fullscreen is disallowed on cross-origin subframe allow attribute',
+ 'fullscreen',
+ cross_origin_src,
+ "fullscreen " + cross_origin);
+ test_disallowed_feature_for_subframe(
+ header_policy + ' -- test fullscreen is disallowed on another cross-origin subframe',
+ 'fullscreen',
+ cross_origin_src1);
+ test_disallowed_feature_for_subframe(
+ header_policy + ' -- test fullscreen is disallowed on another cross-origin subframe allow attribute',
+ 'fullscreen',
+ cross_origin_src1,
+ "fullscreen " + cross_origin1);
+ </script>
+</body>
diff --git a/testing/web-platform/tests/permissions-policy/permissions-policy-header-policy-allowed-for-malformed-wildcard.https.sub.html.sub.headers b/testing/web-platform/tests/permissions-policy/permissions-policy-header-policy-allowed-for-malformed-wildcard.https.sub.html.sub.headers
new file mode 100644
index 0000000000..cfb0f3f5ae
--- /dev/null
+++ b/testing/web-platform/tests/permissions-policy/permissions-policy-header-policy-allowed-for-malformed-wildcard.https.sub.html.sub.headers
@@ -0,0 +1 @@
+Permissions-Policy: fullscreen=("*://{{domains[]}}:{{ports[https][0]}}" "https://{{domains[]}}:*" "https://*.*.{{domains[]}}:{{ports[https][0]}}" "https://example.*.{{domains[]}}:{{ports[https][0]}}" self)
diff --git a/testing/web-platform/tests/permissions-policy/permissions-policy-header-policy-allowed-for-self.https.sub.html b/testing/web-platform/tests/permissions-policy/permissions-policy-header-policy-allowed-for-self.https.sub.html
new file mode 100644
index 0000000000..363ff0b981
--- /dev/null
+++ b/testing/web-platform/tests/permissions-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=/permissions-policy/resources/permissions-policy.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 = '/permissions-policy/resources/permissions-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/permissions-policy/permissions-policy-header-policy-allowed-for-self.https.sub.html.sub.headers b/testing/web-platform/tests/permissions-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/permissions-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/permissions-policy/permissions-policy-header-policy-allowed-for-some.https.sub.html b/testing/web-platform/tests/permissions-policy/permissions-policy-header-policy-allowed-for-some.https.sub.html
new file mode 100644
index 0000000000..e9d8ac1458
--- /dev/null
+++ b/testing/web-platform/tests/permissions-policy/permissions-policy-header-policy-allowed-for-some.https.sub.html
@@ -0,0 +1,75 @@
+<!DOCTYPE html>
+<body>
+ <script src=/resources/testharness.js></script>
+ <script src=/resources/testharnessreport.js></script>
+ <script src=/permissions-policy/resources/permissions-policy.js></script>
+ <!-- Permissions-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 = '/permissions-policy/resources/permissions-policy-allowedfeatures.html';
+ var cross_origin_src = cross_origin + same_origin_src;
+ var cross_origin_src1 = cross_origin1 + same_origin_src;
+ var header_policy = `Permissions-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 disallowd 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
+ 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/permissions-policy/permissions-policy-header-policy-allowed-for-some.https.sub.html.sub.headers b/testing/web-platform/tests/permissions-policy/permissions-policy-header-policy-allowed-for-some.https.sub.html.sub.headers
new file mode 100644
index 0000000000..af08d49c2e
--- /dev/null
+++ b/testing/web-platform/tests/permissions-policy/permissions-policy-header-policy-allowed-for-some.https.sub.html.sub.headers
@@ -0,0 +1 @@
+Permissions-Policy: fullscreen=(self "https://{{domains[www]}}:{{ports[https][0]}}" "https://www.example.com")
diff --git a/testing/web-platform/tests/permissions-policy/permissions-policy-header-policy-allowed-for-wildcard.https.sub.html b/testing/web-platform/tests/permissions-policy/permissions-policy-header-policy-allowed-for-wildcard.https.sub.html
new file mode 100644
index 0000000000..713d697e64
--- /dev/null
+++ b/testing/web-platform/tests/permissions-policy/permissions-policy-header-policy-allowed-for-wildcard.https.sub.html
@@ -0,0 +1,68 @@
+<!DOCTYPE html>
+<body>
+ <script src=/resources/testharness.js></script>
+ <script src=/resources/testharnessreport.js></script>
+ <script src=/permissions-policy/resources/permissions-policy.js></script>
+ <!-- Permissions-Policy: fullscreen=self wildcard_origin; -->
+ <script>
+ 'use strict';
+ var same_origin = 'https://{{domains[]}}:{{ports[https][0]}}';
+ var wildcard_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 = '/permissions-policy/resources/permissions-policy-allowedfeatures.html';
+ var cross_origin_src = cross_origin + same_origin_src;
+ var cross_origin_src1 = cross_origin1 + same_origin_src;
+ var header_policy = `Permissions-Policy: fullscreen=("${wildcard_origin}")`;
+
+ // Test that fullscreen's allowlist is [self wildcard_origin].
+ test(function() {
+ assert_array_equals(
+ document.featurePolicy.getAllowlistForFeature('fullscreen').sort(),
+ [wildcard_origin, same_origin].sort());
+ }, header_policy + ' -- test allowlist is [self wildcard_origin].');
+
+ // Test that fullscreen is allowed on same-origin subframes with or without an allow attribute.
+ test_allowed_feature_for_subframe(
+ header_policy + ' -- test fullscreen is allowed on same-origin subframe',
+ 'fullscreen',
+ same_origin_src);
+ test_allowed_feature_for_subframe(
+ header_policy + ' -- test fullscreen is allowed on same-origin subframe even with allow attribute',
+ 'fullscreen',
+ same_origin_src,
+ "fullscreen " + same_origin);
+
+ // Test that fullscreen is disallowed 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);
+ test_allowed_feature_for_subframe(
+ header_policy + ' -- test fullscreen is allowed on cross-origin subframe allow attribute',
+ 'fullscreen',
+ cross_origin_src,
+ "fullscreen " + cross_origin);
+ test_disallowed_feature_for_subframe(
+ header_policy + ' -- test fullscreen is disallowed on another cross-origin subframe',
+ 'fullscreen',
+ cross_origin_src1);
+ test_allowed_feature_for_subframe(
+ header_policy + ' -- test fullscreen is allowed on another cross-origin subframe allow attribute',
+ 'fullscreen',
+ cross_origin_src1,
+ "fullscreen " + cross_origin1);
+
+ // Test that wildcard allow attribute isn't supported.
+ test_disallowed_feature_for_subframe(
+ header_policy + ' -- test fullscreen is disallowed on cross-origin subframe with wildcard allow attribute',
+ 'fullscreen',
+ cross_origin_src,
+ "fullscreen " + wildcard_origin);
+ test_disallowed_feature_for_subframe(
+ header_policy + ' -- test fullscreen is disallowed on another cross-origin subframe with wildcard allow attribute',
+ 'fullscreen',
+ cross_origin_src1,
+ "fullscreen " + wildcard_origin);
+ </script>
+</body>
diff --git a/testing/web-platform/tests/permissions-policy/permissions-policy-header-policy-allowed-for-wildcard.https.sub.html.sub.headers b/testing/web-platform/tests/permissions-policy/permissions-policy-header-policy-allowed-for-wildcard.https.sub.html.sub.headers
new file mode 100644
index 0000000000..cc492ecc92
--- /dev/null
+++ b/testing/web-platform/tests/permissions-policy/permissions-policy-header-policy-allowed-for-wildcard.https.sub.html.sub.headers
@@ -0,0 +1 @@
+Permissions-Policy: fullscreen=(self "https://*.{{domains[]}}:{{ports[https][0]}}")
diff --git a/testing/web-platform/tests/permissions-policy/permissions-policy-header-policy-declined.https.sub.html b/testing/web-platform/tests/permissions-policy/permissions-policy-header-policy-declined.https.sub.html
new file mode 100644
index 0000000000..f02cdce7d1
--- /dev/null
+++ b/testing/web-platform/tests/permissions-policy/permissions-policy-header-policy-declined.https.sub.html
@@ -0,0 +1,74 @@
+<!DOCTYPE html>
+<body>
+ <script src=/resources/testharness.js></script>
+ <script src=/resources/testharnessreport.js></script>
+ <script src=/permissions-policy/resources/permissions-policy.js></script>
+ <!-- Permissions-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 = '/permissions-policy/resources/permissions-policy-allowedfeatures.html';
+ var cross_origin_src = cross_origin + same_origin_src;
+ var cross_origin_src1 = cross_origin1 + same_origin_src;
+ var header_policy = `Permissions-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 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/permissions-policy/permissions-policy-header-policy-declined.https.sub.html.sub.headers b/testing/web-platform/tests/permissions-policy/permissions-policy-header-policy-declined.https.sub.html.sub.headers
new file mode 100644
index 0000000000..175e5465d2
--- /dev/null
+++ b/testing/web-platform/tests/permissions-policy/permissions-policy-header-policy-declined.https.sub.html.sub.headers
@@ -0,0 +1 @@
+Permissions-Policy: fullscreen=("https://{{domains[www]}}:{{ports[https][0]}}" "https://www.example.com")
diff --git a/testing/web-platform/tests/permissions-policy/permissions-policy-header-policy-disallowed-for-all.https.sub.html b/testing/web-platform/tests/permissions-policy/permissions-policy-header-policy-disallowed-for-all.https.sub.html
new file mode 100644
index 0000000000..f787b7de12
--- /dev/null
+++ b/testing/web-platform/tests/permissions-policy/permissions-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=/permissions-policy/resources/permissions-policy.js></script>
+ <!-- Permissions-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 = '/permissions-policy/resources/permissions-policy-allowedfeatures.html';
+ var cross_origin_src = cross_origin + same_origin_src;
+ var header_policy = 'Permissions-Policy: fullscreen=()';
+
+ // Test that fullscreen's allowlist is []
+ test(function() {
+ assert_array_equals(
+ document.featurePolicy.getAllowlistForFeature('fullscreen'),
+ []);
+ assert_false(document.fullscreenEnabled, "fullscreenEnabled should reflect permissions 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/permissions-policy/permissions-policy-header-policy-disallowed-for-all.https.sub.html.sub.headers b/testing/web-platform/tests/permissions-policy/permissions-policy-header-policy-disallowed-for-all.https.sub.html.sub.headers
new file mode 100644
index 0000000000..bbc80c116a
--- /dev/null
+++ b/testing/web-platform/tests/permissions-policy/permissions-policy-header-policy-disallowed-for-all.https.sub.html.sub.headers
@@ -0,0 +1,2 @@
+Permissions-Policy: fullscreen=()
+
diff --git a/testing/web-platform/tests/permissions-policy/permissions-policy-javascript-url-frame-policy.https.html b/testing/web-platform/tests/permissions-policy/permissions-policy-javascript-url-frame-policy.https.html
new file mode 100644
index 0000000000..4838548580
--- /dev/null
+++ b/testing/web-platform/tests/permissions-policy/permissions-policy-javascript-url-frame-policy.https.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+
+<head>
+ <script src=/resources/testharness.js></script>
+ <script src=/resources/testharnessreport.js></script>
+</head>
+
+<body>
+ <script>
+ 'use strict';
+
+ const script = 'script';
+ const testPage = `
+ <${script}>
+ parent.postMessage(document.fullscreenEnabled, '*');
+ </${script}>
+ `;
+
+ function runTest(allow, expectation) {
+ return new Promise((resolve, reject) => {
+ window.onmessage = event => resolve(event.data);
+
+ const iframe = document.createElement("iframe");
+ iframe.allow = allow;
+ iframe.src = `javascript: \`${testPage}\``;
+ document.body.appendChild(iframe);
+
+ }).then(enabled => {
+ assert_equals(enabled, expectation);
+ });
+ }
+
+ promise_test(() => runTest('fullscreen *', true),
+ 'allow attribute(container policy) can enable feature on javascript generated document');
+
+ promise_test(() => runTest("fullscreen 'none'", false),
+ 'allow attribute(container policy) can disable feature on javascript generated document');
+
+ </script>
+</body>
diff --git a/testing/web-platform/tests/permissions-policy/permissions-policy-nested-header-policy-allowed-for-all.https.sub.html b/testing/web-platform/tests/permissions-policy/permissions-policy-nested-header-policy-allowed-for-all.https.sub.html
new file mode 100644
index 0000000000..46ca378d0b
--- /dev/null
+++ b/testing/web-platform/tests/permissions-policy/permissions-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=/permissions-policy/resources/permissions-policy.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 = '/permissions-policy/resources/permissions-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', '\\(\\)', 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=()"');
+
+ /* -------------------------------------------
+ | 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', '\\(\\)', 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=()"');
+ </script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/permissions-policy/permissions-policy-nested-header-policy-allowed-for-all.https.sub.html.sub.headers b/testing/web-platform/tests/permissions-policy/permissions-policy-nested-header-policy-allowed-for-all.https.sub.html.sub.headers
new file mode 100644
index 0000000000..2cbb8a82c6
--- /dev/null
+++ b/testing/web-platform/tests/permissions-policy/permissions-policy-nested-header-policy-allowed-for-all.https.sub.html.sub.headers
@@ -0,0 +1 @@
+Permissions-Policy: fullscreen=*
diff --git a/testing/web-platform/tests/permissions-policy/permissions-policy-nested-header-policy-allowed-for-self.https.sub.html b/testing/web-platform/tests/permissions-policy/permissions-policy-nested-header-policy-allowed-for-self.https.sub.html
new file mode 100644
index 0000000000..54c6e95b6e
--- /dev/null
+++ b/testing/web-platform/tests/permissions-policy/permissions-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=/permissions-policy/resources/permissions-policy.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 = '/permissions-policy/resources/permissions-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', '\\(\\)', 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=()"');
+
+ /* -------------------------------------------
+ | 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', '\\(\\)', 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=()"');
+ </script>
+</body>
diff --git a/testing/web-platform/tests/permissions-policy/permissions-policy-nested-header-policy-allowed-for-self.https.sub.html.sub.headers b/testing/web-platform/tests/permissions-policy/permissions-policy-nested-header-policy-allowed-for-self.https.sub.html.sub.headers
new file mode 100644
index 0000000000..ff7ae41353
--- /dev/null
+++ b/testing/web-platform/tests/permissions-policy/permissions-policy-nested-header-policy-allowed-for-self.https.sub.html.sub.headers
@@ -0,0 +1 @@
+Permissions-Policy: fullscreen=self
diff --git a/testing/web-platform/tests/permissions-policy/permissions-policy-nested-header-policy-disallowed-for-all.https.sub.html b/testing/web-platform/tests/permissions-policy/permissions-policy-nested-header-policy-disallowed-for-all.https.sub.html
new file mode 100644
index 0000000000..b6ae51b350
--- /dev/null
+++ b/testing/web-platform/tests/permissions-policy/permissions-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=/permissions-policy/resources/permissions-policy.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 = '/permissions-policy/resources/permissions-policy-nested-subframe-policy.https.sub.html';
+ const cross_origin_src = cross_origin + same_origin_src;
+ const policies = ['*', 'self', '\\(\\)'];
+
+ 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/permissions-policy/permissions-policy-nested-header-policy-disallowed-for-all.https.sub.html.sub.headers b/testing/web-platform/tests/permissions-policy/permissions-policy-nested-header-policy-disallowed-for-all.https.sub.html.sub.headers
new file mode 100644
index 0000000000..a65abd6e45
--- /dev/null
+++ b/testing/web-platform/tests/permissions-policy/permissions-policy-nested-header-policy-disallowed-for-all.https.sub.html.sub.headers
@@ -0,0 +1 @@
+Permissions-Policy: fullscreen=()
diff --git a/testing/web-platform/tests/permissions-policy/permissions-policy-opaque-origin-history.https.html b/testing/web-platform/tests/permissions-policy/permissions-policy-opaque-origin-history.https.html
new file mode 100644
index 0000000000..969ca369e1
--- /dev/null
+++ b/testing/web-platform/tests/permissions-policy/permissions-policy-opaque-origin-history.https.html
@@ -0,0 +1,44 @@
+<!DOCTYPE html>
+<body>
+ <script src=/resources/testharness.js></script>
+ <script src=/resources/testharnessreport.js></script>
+ <script>
+
+ function get_response() {
+ return new Promise(resolve => {
+ window.addEventListener('message', e => {
+ resolve(e.data);
+ }, { once: true });
+ });
+ }
+
+ promise_test(async () => {
+ // - opaque-origin-history1.html navigates itself to opaque-origin-history2.html.
+ // - opaque-origin-history2.html call window.history.back() to navigate
+ // back to opaque-origin-history1.html
+ // - opaque-origin-history1.html should still be able to access fullscreen
+ // feature after the history.back() navigation.
+ const iframe = document.createElement('iframe');
+ // sandbox iframe so that it has opaque origin.
+ iframe.sandbox = 'allow-scripts';
+ iframe.src = 'resources/opaque-origin-history1.sub.https.html';
+ iframe.allow = "fullscreen 'src'";
+ document.body.appendChild(iframe);
+
+
+ assert_equals(
+ await get_response(),
+ 'fullscreen enabled in opaque-origin-history1.html',
+ 'iframe should be able to access fullscreen.'
+ );
+
+ iframe.contentWindow.postMessage('redirect', '*');
+
+ assert_equals(
+ await get_response(),
+ 'fullscreen enabled in opaque-origin-history1.html',
+ 'iframe should still be able to access fullscreen after history.back() navigation.'
+ );
+ });
+ </script>
+</body>
diff --git a/testing/web-platform/tests/permissions-policy/permissions-policy-opaque-origin.https.html b/testing/web-platform/tests/permissions-policy/permissions-policy-opaque-origin.https.html
new file mode 100644
index 0000000000..edb6e11a95
--- /dev/null
+++ b/testing/web-platform/tests/permissions-policy/permissions-policy-opaque-origin.https.html
@@ -0,0 +1,23 @@
+<!DOCTYPE html>
+<body>
+ <script src=/resources/testharness.js></script>
+ <script src=/resources/testharnessreport.js></script>
+ <script>
+ async_test(t => {
+ // opaque-origin1.html navigates itself to opaque-origin2.html onload.
+ // The 'src' shorthand in allow="fullscreen 'src'" should only match
+ // the initial src of opaque-origin1.html. opaque-origin2.html
+ // should not get access to fullscreen.
+ const iframe = document.createElement('iframe');
+ iframe.sandbox = 'allow-scripts';
+ iframe.src = 'resources/opaque-origin1.sub.https.html';
+ iframe.allow = "fullscreen 'src'";
+
+ window.addEventListener('message', t.step_func_done(e => {
+ assert_equals(e.data, "fullscreen disabled in opaque-origin2.html");
+ }));
+
+ document.body.appendChild(iframe);
+ });
+ </script>
+</body>
diff --git a/testing/web-platform/tests/permissions-policy/picture-in-picture-allowed-by-permissions-policy-attribute-redirect-on-load.https.sub.html b/testing/web-platform/tests/permissions-policy/picture-in-picture-allowed-by-permissions-policy-attribute-redirect-on-load.https.sub.html
new file mode 100644
index 0000000000..d965fdc5fb
--- /dev/null
+++ b/testing/web-platform/tests/permissions-policy/picture-in-picture-allowed-by-permissions-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=/permissions-policy/resources/permissions-policy.js></script>
+ <script src=/permissions-policy/resources/picture-in-picture.js></script>
+ <script>
+ 'use strict';
+ const relative_path = '/permissions-policy/resources/permissions-policy-picture-in-picture.html';
+ const base_src = '/permissions-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 = 'permissions 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/permissions-policy/picture-in-picture-allowed-by-permissions-policy-attribute.https.sub.html b/testing/web-platform/tests/permissions-policy/picture-in-picture-allowed-by-permissions-policy-attribute.https.sub.html
new file mode 100644
index 0000000000..c8371ab29c
--- /dev/null
+++ b/testing/web-platform/tests/permissions-policy/picture-in-picture-allowed-by-permissions-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=/permissions-policy/resources/permissions-policy.js></script>
+ <script src=/permissions-policy/resources/picture-in-picture.js></script>
+ <script>
+ 'use strict';
+ const same_origin_src = '/permissions-policy/resources/permissions-policy-picture-in-picture.html';
+ const cross_origin_src = 'https://{{domains[www]}}:{{ports[https][0]}}' +
+ same_origin_src;
+ const feature_name = 'permissions 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/permissions-policy/picture-in-picture-allowed-by-permissions-policy.https.sub.html b/testing/web-platform/tests/permissions-policy/picture-in-picture-allowed-by-permissions-policy.https.sub.html
new file mode 100644
index 0000000000..dc85111ed4
--- /dev/null
+++ b/testing/web-platform/tests/permissions-policy/picture-in-picture-allowed-by-permissions-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=/permissions-policy/resources/permissions-policy.js></script>
+ <script src=/permissions-policy/resources/picture-in-picture.js></script>
+ <script>
+ 'use strict';
+ const same_origin_src = '/permissions-policy/resources/permissions-policy-picture-in-picture.html';
+ const cross_origin_src = 'https://{{domains[www]}}:{{ports[https][0]}}' +
+ same_origin_src;
+ const header = 'permissions 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/permissions-policy/picture-in-picture-allowed-by-permissions-policy.https.sub.html.headers b/testing/web-platform/tests/permissions-policy/picture-in-picture-allowed-by-permissions-policy.https.sub.html.headers
new file mode 100644
index 0000000000..a23f9332ce
--- /dev/null
+++ b/testing/web-platform/tests/permissions-policy/picture-in-picture-allowed-by-permissions-policy.https.sub.html.headers
@@ -0,0 +1 @@
+Permissions-Policy: picture-in-picture=*
diff --git a/testing/web-platform/tests/permissions-policy/picture-in-picture-default-permissions-policy.https.sub.html b/testing/web-platform/tests/permissions-policy/picture-in-picture-default-permissions-policy.https.sub.html
new file mode 100644
index 0000000000..17ab1fd2b5
--- /dev/null
+++ b/testing/web-platform/tests/permissions-policy/picture-in-picture-default-permissions-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=/permissions-policy/resources/permissions-policy.js></script>
+ <script src=/permissions-policy/resources/picture-in-picture.js></script>
+ <script>
+ 'use strict';
+ const same_origin_src = '/permissions-policy/resources/permissions-policy-picture-in-picture.html';
+ const cross_origin_src = 'https://{{domains[www]}}:{{ports[https][0]}}' +
+ same_origin_src;
+ const header = 'Default "picture-in-picture" permissions 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/permissions-policy/picture-in-picture-disabled-by-permissions-policy.https.sub.html b/testing/web-platform/tests/permissions-policy/picture-in-picture-disabled-by-permissions-policy.https.sub.html
new file mode 100644
index 0000000000..7140f686cb
--- /dev/null
+++ b/testing/web-platform/tests/permissions-policy/picture-in-picture-disabled-by-permissions-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=/permissions-policy/resources/permissions-policy.js></script>
+ <script src=/permissions-policy/resources/picture-in-picture.js></script>
+ <script>
+ 'use strict';
+ const same_origin_src = '/permissions-policy/resources/permissions-policy-picture-in-picture.html';
+ const cross_origin_src = 'https://{{domains[www]}}:{{ports[https][0]}}' +
+ same_origin_src;
+ const header = 'permissions 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/permissions-policy/picture-in-picture-disabled-by-permissions-policy.https.sub.html.headers b/testing/web-platform/tests/permissions-policy/picture-in-picture-disabled-by-permissions-policy.https.sub.html.headers
new file mode 100644
index 0000000000..12fc99b50b
--- /dev/null
+++ b/testing/web-platform/tests/permissions-policy/picture-in-picture-disabled-by-permissions-policy.https.sub.html.headers
@@ -0,0 +1 @@
+Permissions-Policy: picture-in-picture=()
diff --git a/testing/web-platform/tests/permissions-policy/picture-in-picture-supported-by-permissions-policy.html b/testing/web-platform/tests/permissions-policy/picture-in-picture-supported-by-permissions-policy.html
new file mode 100644
index 0000000000..387a878f3b
--- /dev/null
+++ b/testing/web-platform/tests/permissions-policy/picture-in-picture-supported-by-permissions-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-permissions-policy/#dom-permissions-policy-features">
+<link rel="help" href="https://wicg.github.io/picture-in-picture/#permissions-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/permissions-policy/policy-extends-to-sandbox.html b/testing/web-platform/tests/permissions-policy/policy-extends-to-sandbox.html
new file mode 100644
index 0000000000..50bf8a334c
--- /dev/null
+++ b/testing/web-platform/tests/permissions-policy/policy-extends-to-sandbox.html
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<title>permissions 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 = "/permissions-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/permissions-policy/reporting/bluetooth-report-only.https.html b/testing/web-platform/tests/permissions-policy/reporting/bluetooth-report-only.https.html
new file mode 100644
index 0000000000..9688baa5b0
--- /dev/null
+++ b/testing/web-platform/tests/permissions-policy/reporting/bluetooth-report-only.https.html
@@ -0,0 +1,67 @@
+<!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='/bluetooth/resources/bluetooth-test.js'></script>
+ </head>
+ <body>
+ <script>
+
+function check_report_format([reports, observer]) {
+ const report = reports[0];
+ assert_equals(report.type, "permissions-policy-violation");
+ assert_equals(report.body.featureId, "bluetooth");
+ assert_equals(report.body.disposition, "report");
+};
+
+bluetooth_test(() => {
+ promise_test(async t => {
+ const report = new Promise(resolve => {
+ new ReportingObserver((reports, observer) => resolve([reports, observer]),
+ {types: ['permissions-policy-violation']}).observe();
+ });
+
+ await navigator.bluetooth.getAvailability();
+ check_report_format(await report);
+ }, 'getAvailability in bluetooth report only mode');
+
+ promise_test(async t => {
+ const report = new Promise(resolve => {
+ new ReportingObserver((reports, observer) => resolve([reports, observer]),
+ {types: ['permissions-policy-violation']}).observe();
+ });
+
+ await navigator.bluetooth.getDevices();
+ check_report_format(await report);
+ }, 'getDevices in bluetooth report only mode');
+
+ 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 bluetooth.requestDevice');
+ await promise_rejects_js(t, TypeError, navigator.bluetooth.requestDevice({filters: []}), 'requestDevice() call should fail when no filters are selected.')
+
+ check_report_format(await report);
+ }, 'requestDevice in bluetooth report only mode');
+
+ 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 bluetooth.requestLEScan');
+ await promise_rejects_js(t, TypeError, navigator.bluetooth.requestLEScan({filters: []}), 'requestLEScan() call should fail when no filters are selected.');
+
+ check_report_format(await report);
+ }, 'requestLEScan in bluetooth report only mode');
+});
+ </script>
+ </body>
+</html> \ No newline at end of file
diff --git a/testing/web-platform/tests/permissions-policy/reporting/bluetooth-report-only.https.html.headers b/testing/web-platform/tests/permissions-policy/reporting/bluetooth-report-only.https.html.headers
new file mode 100644
index 0000000000..dfa8cf0090
--- /dev/null
+++ b/testing/web-platform/tests/permissions-policy/reporting/bluetooth-report-only.https.html.headers
@@ -0,0 +1 @@
+Permissions-Policy-Report-Only: bluetooth=() \ No newline at end of file
diff --git a/testing/web-platform/tests/permissions-policy/reporting/bluetooth-reporting.https.html b/testing/web-platform/tests/permissions-policy/reporting/bluetooth-reporting.https.html
new file mode 100644
index 0000000000..35b22122b9
--- /dev/null
+++ b/testing/web-platform/tests/permissions-policy/reporting/bluetooth-reporting.https.html
@@ -0,0 +1,83 @@
+<!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='/bluetooth/resources/bluetooth-test.js'></script>
+ <script src='../resources/permissions-policy-report-json.js'></script>
+ </head>
+ <body>
+ <script>
+
+function 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, "bluetooth");
+ 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);
+};
+
+bluetooth_test(() => {
+ promise_test(async t => {
+ const report = new Promise(resolve => {
+ new ReportingObserver((reports, observer) => resolve([reports, observer]),
+ {types: ['permissions-policy-violation']}).observe();
+ });
+
+ try {
+ await navigator.bluetooth.getAvailability().then(availability => {
+ assert_false(availability);
+ });
+ } catch(e) {
+ assert_unreached('getAvailability should return false when Bluetooth access is disallowed.');
+ }
+ check_report_format(await report);
+ }, 'getAvailability in bluetooth reporting mode');
+
+ 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.bluetooth.getDevices(),
+ 'Bluetooth access should not be allowed in this document.');
+ check_report_format(await report);
+ }, 'getDevices in bluetooth reporting mode');
+
+ 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 bluetooth.requestDevice');
+ await promise_rejects_dom(
+ t, 'SecurityError', navigator.bluetooth.requestDevice(),
+ 'Bluetooth access should not be allowed in this document.');
+ check_report_format(await report);
+ }, 'requestDevice in bluetooth reporting mode');
+
+ 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 bluetooth.requestLEScan');
+ await promise_rejects_dom(
+ t, 'SecurityError', navigator.bluetooth.requestLEScan(),
+ 'Bluetooth access should not be allowed in this document.');
+ check_report_format(await report);
+ }, 'requestLEScan in bluetooth reporting mode');
+});
+ </script>
+ </body>
+</html> \ No newline at end of file
diff --git a/testing/web-platform/tests/permissions-policy/reporting/bluetooth-reporting.https.html.headers b/testing/web-platform/tests/permissions-policy/reporting/bluetooth-reporting.https.html.headers
new file mode 100644
index 0000000000..10b94729dd
--- /dev/null
+++ b/testing/web-platform/tests/permissions-policy/reporting/bluetooth-reporting.https.html.headers
@@ -0,0 +1 @@
+Permissions-Policy: bluetooth=() \ No newline at end of file
diff --git a/testing/web-platform/tests/permissions-policy/reporting/camera-report-only.https.html b/testing/web-platform/tests/permissions-policy/reporting/camera-report-only.https.html
new file mode 100644
index 0000000000..2cf20c28a7
--- /dev/null
+++ b/testing/web-platform/tests/permissions-policy/reporting/camera-report-only.https.html
@@ -0,0 +1,31 @@
+<!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 check_report_format = ([reports, observer]) => {
+ let report = reports[0];
+ assert_equals(report.type, "permissions-policy-violation");
+ assert_equals(report.body.featureId, "camera");
+ assert_equals(report.body.disposition, "report");
+};
+
+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 user media');
+ await setMediaPermission("granted", ["camera"]);
+ await navigator.mediaDevices.getUserMedia({video: true});
+ check_report_format(await report);
+}, "Camera report only mode");
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/permissions-policy/reporting/camera-report-only.https.html.headers b/testing/web-platform/tests/permissions-policy/reporting/camera-report-only.https.html.headers
new file mode 100644
index 0000000000..71a1cca5be
--- /dev/null
+++ b/testing/web-platform/tests/permissions-policy/reporting/camera-report-only.https.html.headers
@@ -0,0 +1 @@
+Permissions-Policy-Report-Only: camera=()
diff --git a/testing/web-platform/tests/permissions-policy/reporting/camera-reporting.https.html b/testing/web-platform/tests/permissions-policy/reporting/camera-reporting.https.html
new file mode 100644
index 0000000000..9cf1b18002
--- /dev/null
+++ b/testing/web-platform/tests/permissions-policy/reporting/camera-reporting.https.html
@@ -0,0 +1,33 @@
+<!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/permissions-policy/reporting/camera-reporting.https.html.headers b/testing/web-platform/tests/permissions-policy/reporting/camera-reporting.https.html.headers
new file mode 100644
index 0000000000..6fcbae1419
--- /dev/null
+++ b/testing/web-platform/tests/permissions-policy/reporting/camera-reporting.https.html.headers
@@ -0,0 +1 @@
+Permissions-Policy: camera=()
diff --git a/testing/web-platform/tests/permissions-policy/reporting/encrypted-media-report-only.https.html b/testing/web-platform/tests/permissions-policy/reporting/encrypted-media-report-only.https.html
new file mode 100644
index 0000000000..72a1ca2368
--- /dev/null
+++ b/testing/web-platform/tests/permissions-policy/reporting/encrypted-media-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>
+var check_report_format = ([reports, observer]) => {
+ let report = reports[0];
+ assert_equals(report.type, "permissions-policy-violation");
+ assert_equals(report.body.featureId, "encrypted-media");
+ assert_equals(report.body.disposition, "report");
+};
+
+promise_test(async t => {
+ const report = new Promise(resolve => {
+ new ReportingObserver((reports, observer) => resolve([reports, observer]),
+ {types: ['permissions-policy-violation']}).observe();
+ });
+ await navigator.requestMediaKeySystemAccess("org.w3.clearkey",
+ [{
+ initDataTypes: ["webm"],
+ videoCapabilities: [{contentType: 'video/webm;codecs="vp8"'}],
+ }]);
+ check_report_format(await report);
+}, "Encrypted Media report only mode");
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/permissions-policy/reporting/encrypted-media-report-only.https.html.headers b/testing/web-platform/tests/permissions-policy/reporting/encrypted-media-report-only.https.html.headers
new file mode 100644
index 0000000000..0d5480e2a6
--- /dev/null
+++ b/testing/web-platform/tests/permissions-policy/reporting/encrypted-media-report-only.https.html.headers
@@ -0,0 +1 @@
+Permissions-Policy-Report-Only: encrypted-media=()
diff --git a/testing/web-platform/tests/permissions-policy/reporting/encrypted-media-reporting.https.html b/testing/web-platform/tests/permissions-policy/reporting/encrypted-media-reporting.https.html
new file mode 100644
index 0000000000..32a5a2cc48
--- /dev/null
+++ b/testing/web-platform/tests/permissions-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/permissions-policy/reporting/encrypted-media-reporting.https.html.headers b/testing/web-platform/tests/permissions-policy/reporting/encrypted-media-reporting.https.html.headers
new file mode 100644
index 0000000000..9505c02dae
--- /dev/null
+++ b/testing/web-platform/tests/permissions-policy/reporting/encrypted-media-reporting.https.html.headers
@@ -0,0 +1 @@
+Permissions-Policy: encrypted-media=()
diff --git a/testing/web-platform/tests/permissions-policy/reporting/fullscreen-report-only.html b/testing/web-platform/tests/permissions-policy/reporting/fullscreen-report-only.html
new file mode 100644
index 0000000000..caf31f6b79
--- /dev/null
+++ b/testing/web-platform/tests/permissions-policy/reporting/fullscreen-report-only.html
@@ -0,0 +1,31 @@
+<!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>
+ <div id='fs'></div>
+ <script>
+var check_report_format = ([reports, observer]) => {
+ let report = reports[0];
+ assert_equals(report.type, "permissions-policy-violation");
+ assert_equals(report.body.featureId, "fullscreen");
+ assert_equals(report.body.disposition, "report");
+};
+
+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 fullscreen');
+ await document.getElementById('fs').requestFullscreen();
+ check_report_format(await report);
+ document.exitFullscreen();
+}, "Fullscreen report only mode");
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/permissions-policy/reporting/fullscreen-report-only.html.headers b/testing/web-platform/tests/permissions-policy/reporting/fullscreen-report-only.html.headers
new file mode 100644
index 0000000000..384d778c9a
--- /dev/null
+++ b/testing/web-platform/tests/permissions-policy/reporting/fullscreen-report-only.html.headers
@@ -0,0 +1 @@
+Permissions-Policy-Report-Only: fullscreen=()
diff --git a/testing/web-platform/tests/permissions-policy/reporting/fullscreen-reporting.html b/testing/web-platform/tests/permissions-policy/reporting/fullscreen-reporting.html
new file mode 100644
index 0000000000..d7b905744d
--- /dev/null
+++ b/testing/web-platform/tests/permissions-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/permissions-policy/reporting/fullscreen-reporting.html.headers b/testing/web-platform/tests/permissions-policy/reporting/fullscreen-reporting.html.headers
new file mode 100644
index 0000000000..a65abd6e45
--- /dev/null
+++ b/testing/web-platform/tests/permissions-policy/reporting/fullscreen-reporting.html.headers
@@ -0,0 +1 @@
+Permissions-Policy: fullscreen=()
diff --git a/testing/web-platform/tests/permissions-policy/reporting/generic-sensor-report-only.https.html b/testing/web-platform/tests/permissions-policy/reporting/generic-sensor-report-only.https.html
new file mode 100644
index 0000000000..21fb850391
--- /dev/null
+++ b/testing/web-platform/tests/permissions-policy/reporting/generic-sensor-report-only.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, "report");
+
+ 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();
+ new Accelerometer();
+ new AmbientLightSensor();
+ new Gyroscope();
+ new Magnetometer();
+}, "Generic Sensor report only mode");
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/permissions-policy/reporting/generic-sensor-report-only.https.html.headers b/testing/web-platform/tests/permissions-policy/reporting/generic-sensor-report-only.https.html.headers
new file mode 100644
index 0000000000..2a51586203
--- /dev/null
+++ b/testing/web-platform/tests/permissions-policy/reporting/generic-sensor-report-only.https.html.headers
@@ -0,0 +1 @@
+Permissions-Policy-Report-Only: ambient-light-sensor=(), accelerometer=(), gyroscope=(), magnetometer=()
diff --git a/testing/web-platform/tests/permissions-policy/reporting/generic-sensor-reporting.https.html b/testing/web-platform/tests/permissions-policy/reporting/generic-sensor-reporting.https.html
new file mode 100644
index 0000000000..c29c069ee3
--- /dev/null
+++ b/testing/web-platform/tests/permissions-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/permissions-policy/reporting/generic-sensor-reporting.https.html.headers b/testing/web-platform/tests/permissions-policy/reporting/generic-sensor-reporting.https.html.headers
new file mode 100644
index 0000000000..b0ebb72e9c
--- /dev/null
+++ b/testing/web-platform/tests/permissions-policy/reporting/generic-sensor-reporting.https.html.headers
@@ -0,0 +1 @@
+Permissions-Policy: ambient-light-sensor=(),accelerometer=(),gyroscope=(),magnetometer=()
diff --git a/testing/web-platform/tests/permissions-policy/reporting/geolocation-report-only.https.html b/testing/web-platform/tests/permissions-policy/reporting/geolocation-report-only.https.html
new file mode 100644
index 0000000000..abffd62cfc
--- /dev/null
+++ b/testing/web-platform/tests/permissions-policy/reporting/geolocation-report-only.https.html
@@ -0,0 +1,36 @@
+<!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.body.featureId, "geolocation");
+ assert_equals(report.body.disposition, "report");
+};
+
+promise_test(async t => {
+ const report = new Promise(resolve => {
+ new ReportingObserver((reports, observer) => resolve([reports, observer]),
+ {types: ['permissions-policy-violation']}).observe();
+ });
+ try {
+ await new Promise((resolve, reject) => {
+ navigator.geolocation.getCurrentPosition(resolve, reject);
+ });
+ check_report_format(await report);
+ } catch (err) {
+ // In case the getCurrentPosition call was rejected due to user permissions,
+ // the report should be generated anyway. Wait for it and check the format
+ // before failing this test.
+ check_report_format(await report);
+ throw err;
+ }
+}, "Geolocation report only mode");
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/permissions-policy/reporting/geolocation-report-only.https.html.headers b/testing/web-platform/tests/permissions-policy/reporting/geolocation-report-only.https.html.headers
new file mode 100644
index 0000000000..c7e5f25bc5
--- /dev/null
+++ b/testing/web-platform/tests/permissions-policy/reporting/geolocation-report-only.https.html.headers
@@ -0,0 +1 @@
+Permissions-Policy-Report-Only: geolocation=()
diff --git a/testing/web-platform/tests/permissions-policy/reporting/geolocation-reporting.https.html b/testing/web-platform/tests/permissions-policy/reporting/geolocation-reporting.https.html
new file mode 100644
index 0000000000..e0eb275bcc
--- /dev/null
+++ b/testing/web-platform/tests/permissions-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/permissions-policy/reporting/geolocation-reporting.https.html.headers b/testing/web-platform/tests/permissions-policy/reporting/geolocation-reporting.https.html.headers
new file mode 100644
index 0000000000..26bfbc2496
--- /dev/null
+++ b/testing/web-platform/tests/permissions-policy/reporting/geolocation-reporting.https.html.headers
@@ -0,0 +1 @@
+Permissions-Policy: geolocation=()
diff --git a/testing/web-platform/tests/permissions-policy/reporting/image.bmp b/testing/web-platform/tests/permissions-policy/reporting/image.bmp
new file mode 100644
index 0000000000..f2b88690fc
--- /dev/null
+++ b/testing/web-platform/tests/permissions-policy/reporting/image.bmp
Binary files differ
diff --git a/testing/web-platform/tests/permissions-policy/reporting/microphone-report-only.https.html b/testing/web-platform/tests/permissions-policy/reporting/microphone-report-only.https.html
new file mode 100644
index 0000000000..1b9b87f442
--- /dev/null
+++ b/testing/web-platform/tests/permissions-policy/reporting/microphone-report-only.https.html
@@ -0,0 +1,31 @@
+<!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 check_report_format = ([reports, observer]) => {
+ let report = reports[0];
+ assert_equals(report.type, "permissions-policy-violation");
+ assert_equals(report.body.featureId, "microphone");
+ assert_equals(report.body.disposition, "report");
+};
+
+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 user media');
+ await setMediaPermission();
+ await navigator.mediaDevices.getUserMedia({audio: true});
+ check_report_format(await report);
+}, "Microphone report only mode");
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/permissions-policy/reporting/microphone-report-only.https.html.headers b/testing/web-platform/tests/permissions-policy/reporting/microphone-report-only.https.html.headers
new file mode 100644
index 0000000000..adcf95e9e2
--- /dev/null
+++ b/testing/web-platform/tests/permissions-policy/reporting/microphone-report-only.https.html.headers
@@ -0,0 +1 @@
+Permissions-Policy-Report-Only: microphone=()
diff --git a/testing/web-platform/tests/permissions-policy/reporting/microphone-reporting.https.html b/testing/web-platform/tests/permissions-policy/reporting/microphone-reporting.https.html
new file mode 100644
index 0000000000..5b4c2121e3
--- /dev/null
+++ b/testing/web-platform/tests/permissions-policy/reporting/microphone-reporting.https.html
@@ -0,0 +1,33 @@
+<!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/permissions-policy/reporting/microphone-reporting.https.html.headers b/testing/web-platform/tests/permissions-policy/reporting/microphone-reporting.https.html.headers
new file mode 100644
index 0000000000..ae65ea5c73
--- /dev/null
+++ b/testing/web-platform/tests/permissions-policy/reporting/microphone-reporting.https.html.headers
@@ -0,0 +1 @@
+Permissions-Policy: microphone=()
diff --git a/testing/web-platform/tests/permissions-policy/reporting/midi-report-only.https.html b/testing/web-platform/tests/permissions-policy/reporting/midi-report-only.https.html
new file mode 100644
index 0000000000..3aa12dcfb5
--- /dev/null
+++ b/testing/web-platform/tests/permissions-policy/reporting/midi-report-only.https.html
@@ -0,0 +1,34 @@
+<!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.body.featureId, "midi");
+ assert_equals(report.body.disposition, "report");
+};
+
+promise_test(async t => {
+ const report = new Promise(resolve => {
+ new ReportingObserver((reports, observer) => resolve([reports, observer]),
+ {types: ['permissions-policy-violation']}).observe();
+ });
+ try {
+ await navigator.requestMIDIAccess();
+ check_report_format(await report);
+ } catch (err) {
+ // In case the requestMIDIAccess call was rejected due to user permissions,
+ // the report should be generated anyway. Wait for it and check the format
+ // before failing this test.
+ check_report_format(await report);
+ throw err;
+ }
+}, "MIDI report only mode");
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/permissions-policy/reporting/midi-report-only.https.html.headers b/testing/web-platform/tests/permissions-policy/reporting/midi-report-only.https.html.headers
new file mode 100644
index 0000000000..10570f6044
--- /dev/null
+++ b/testing/web-platform/tests/permissions-policy/reporting/midi-report-only.https.html.headers
@@ -0,0 +1 @@
+Permissions-Policy-Report-Only: midi=()
diff --git a/testing/web-platform/tests/permissions-policy/reporting/midi-reporting.https.html b/testing/web-platform/tests/permissions-policy/reporting/midi-reporting.https.html
new file mode 100644
index 0000000000..6cc07f5371
--- /dev/null
+++ b/testing/web-platform/tests/permissions-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/permissions-policy/reporting/midi-reporting.https.html.headers b/testing/web-platform/tests/permissions-policy/reporting/midi-reporting.https.html.headers
new file mode 100644
index 0000000000..1fa88c3a59
--- /dev/null
+++ b/testing/web-platform/tests/permissions-policy/reporting/midi-reporting.https.html.headers
@@ -0,0 +1 @@
+Permissions-Policy: midi=()
diff --git a/testing/web-platform/tests/permissions-policy/reporting/payment-report-only.https.html b/testing/web-platform/tests/permissions-policy/reporting/payment-report-only.https.html
new file mode 100644
index 0000000000..f1b1b433f5
--- /dev/null
+++ b/testing/web-platform/tests/permissions-policy/reporting/payment-report-only.https.html
@@ -0,0 +1,40 @@
+<!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.body.featureId, "payment");
+ assert_equals(report.body.disposition, "report");
+};
+
+promise_test(async t => {
+ const report = new Promise(resolve => {
+ new ReportingObserver((reports, observer) => resolve([reports, observer]),
+ {types: ['permissions-policy-violation']}).observe();
+ });
+ try {
+ await test_driver.bless();
+ const request = new PaymentRequest(
+ [{ supportedMethods: 'https://example.com/pay' }],
+ { total: { label: 'Total', amount: { currency: 'USD', value: 0 }}},
+ {});
+ await request.show()
+ check_report_format(await report);
+ } catch (err) {
+ // In case the show call was rejected, the report should be generated
+ // anyway. Wait for it and check the format before failing this test.
+ check_report_format(await report);
+ throw err;
+ }
+}, "PaymentRequest report only mode");
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/permissions-policy/reporting/payment-report-only.https.html.headers b/testing/web-platform/tests/permissions-policy/reporting/payment-report-only.https.html.headers
new file mode 100644
index 0000000000..25be8168ab
--- /dev/null
+++ b/testing/web-platform/tests/permissions-policy/reporting/payment-report-only.https.html.headers
@@ -0,0 +1 @@
+Permissions-Policy-Report-Only: payment=()
diff --git a/testing/web-platform/tests/permissions-policy/reporting/payment-reporting.https.html b/testing/web-platform/tests/permissions-policy/reporting/payment-reporting.https.html
new file mode 100644
index 0000000000..f639c2b7c6
--- /dev/null
+++ b/testing/web-platform/tests/permissions-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/permissions-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/permissions-policy/reporting/payment-reporting.https.html.headers b/testing/web-platform/tests/permissions-policy/reporting/payment-reporting.https.html.headers
new file mode 100644
index 0000000000..49f799d138
--- /dev/null
+++ b/testing/web-platform/tests/permissions-policy/reporting/payment-reporting.https.html.headers
@@ -0,0 +1 @@
+Permissions-Policy: payment=()
diff --git a/testing/web-platform/tests/permissions-policy/reporting/picture-in-picture-report-only.html b/testing/web-platform/tests/permissions-policy/reporting/picture-in-picture-report-only.html
new file mode 100644
index 0000000000..0caa2ae953
--- /dev/null
+++ b/testing/web-platform/tests/permissions-policy/reporting/picture-in-picture-report-only.html
@@ -0,0 +1,40 @@
+<!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.body.featureId, "picture-in-picture");
+ assert_equals(report.body.disposition, "report");
+};
+
+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 videoElement.requestPictureInPicture();
+ check_report_format(await report);
+}, "Picture-in-Picture report only mode");
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/permissions-policy/reporting/picture-in-picture-report-only.html.headers b/testing/web-platform/tests/permissions-policy/reporting/picture-in-picture-report-only.html.headers
new file mode 100644
index 0000000000..0f73e39c58
--- /dev/null
+++ b/testing/web-platform/tests/permissions-policy/reporting/picture-in-picture-report-only.html.headers
@@ -0,0 +1 @@
+Permissions-Policy-Report-Only: picture-in-picture=()
diff --git a/testing/web-platform/tests/permissions-policy/reporting/picture-in-picture-reporting.html b/testing/web-platform/tests/permissions-policy/reporting/picture-in-picture-reporting.html
new file mode 100644
index 0000000000..177e4d5c02
--- /dev/null
+++ b/testing/web-platform/tests/permissions-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/permissions-policy/reporting/picture-in-picture-reporting.html.headers b/testing/web-platform/tests/permissions-policy/reporting/picture-in-picture-reporting.html.headers
new file mode 100644
index 0000000000..12fc99b50b
--- /dev/null
+++ b/testing/web-platform/tests/permissions-policy/reporting/picture-in-picture-reporting.html.headers
@@ -0,0 +1 @@
+Permissions-Policy: picture-in-picture=()
diff --git a/testing/web-platform/tests/permissions-policy/reporting/screen-wake-lock-reporting.https.html b/testing/web-platform/tests/permissions-policy/reporting/screen-wake-lock-reporting.https.html
new file mode 100644
index 0000000000..4ddfc69f86
--- /dev/null
+++ b/testing/web-platform/tests/permissions-policy/reporting/screen-wake-lock-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>
+ promise_test(async (t) => {
+ const reportPromise = new Promise(resolve => {
+ new ReportingObserver((reports, observer) => resolve(reports),
+ {types: ["permissions-policy-violation"]}).observe();
+ });
+ // Even though we do not explicitly allow screen wake lock requests in
+ // testdriver, per spec Permissions Policy checks should happen earlier.
+ await promise_rejects_dom(t, 'NotAllowedError', navigator.wakeLock.request("screen"),
+ "Screen Wake Lock should not be allowed in this document.");
+ const reports = await reportPromise;
+
+ assert_equals(reports.length, 1);
+ const report = reports[0];
+ assert_equals(report.type, "permissions-policy-violation");
+ assert_equals(report.url, document.location.href);
+ assert_equals(report.body.featureId, "screen-wake-lock");
+ 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");
+ }, "Screen Wake Lock Report Format");
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/permissions-policy/reporting/screen-wake-lock-reporting.https.html.headers b/testing/web-platform/tests/permissions-policy/reporting/screen-wake-lock-reporting.https.html.headers
new file mode 100644
index 0000000000..66f07f4c1c
--- /dev/null
+++ b/testing/web-platform/tests/permissions-policy/reporting/screen-wake-lock-reporting.https.html.headers
@@ -0,0 +1 @@
+Permissions-Policy: screen-wake-lock=()
diff --git a/testing/web-platform/tests/permissions-policy/reporting/serial-report-only.https.html b/testing/web-platform/tests/permissions-policy/reporting/serial-report-only.https.html
new file mode 100644
index 0000000000..3a410362de
--- /dev/null
+++ b/testing/web-platform/tests/permissions-policy/reporting/serial-report-only.https.html
@@ -0,0 +1,46 @@
+<!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>
+ <div id='fs'></div>
+ <script>
+var check_report_format = ([reports, observer]) => {
+ let report = reports[0];
+ assert_equals(report.type, "permissions-policy-violation");
+ assert_equals(report.body.featureId, "serial");
+ assert_equals(report.body.disposition, "report");
+};
+
+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('requestPort() call should fail when no port is selected.');
+ } catch (e) {
+ assert_equals(e.code, DOMException.NOT_FOUND_ERR);
+ }
+ check_report_format(await report);
+}, "requestPort in serial report only mode");
+
+promise_test(async t => {
+ const report = new Promise(resolve => {
+ new ReportingObserver((reports, observer) => resolve([reports, observer]),
+ {types: ['permissions-policy-violation']}).observe();
+ });
+
+ await navigator.serial.getPorts();
+ check_report_format(await report);
+}, "getPorts in serial report only mode");
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/permissions-policy/reporting/serial-report-only.https.html.headers b/testing/web-platform/tests/permissions-policy/reporting/serial-report-only.https.html.headers
new file mode 100644
index 0000000000..cee26394d3
--- /dev/null
+++ b/testing/web-platform/tests/permissions-policy/reporting/serial-report-only.https.html.headers
@@ -0,0 +1 @@
+Permissions-Policy-Report-Only: serial=()
diff --git a/testing/web-platform/tests/permissions-policy/reporting/serial-reporting.https.html b/testing/web-platform/tests/permissions-policy/reporting/serial-reporting.https.html
new file mode 100644
index 0000000000..c96d8f878a
--- /dev/null
+++ b/testing/web-platform/tests/permissions-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/permissions-policy/reporting/serial-reporting.https.html.headers b/testing/web-platform/tests/permissions-policy/reporting/serial-reporting.https.html.headers
new file mode 100644
index 0000000000..690b696751
--- /dev/null
+++ b/testing/web-platform/tests/permissions-policy/reporting/serial-reporting.https.html.headers
@@ -0,0 +1 @@
+Permissions-Policy: serial=()
diff --git a/testing/web-platform/tests/permissions-policy/reporting/sync-xhr-report-only.html b/testing/web-platform/tests/permissions-policy/reporting/sync-xhr-report-only.html
new file mode 100644
index 0000000000..29b1368a2e
--- /dev/null
+++ b/testing/web-platform/tests/permissions-policy/reporting/sync-xhr-report-only.html
@@ -0,0 +1,28 @@
+<!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, "permissions-policy-violation");
+ assert_equals(report.body.featureId, "sync-xhr");
+ assert_equals(report.body.disposition, "report");
+};
+
+promise_test(async t => {
+ const report = new Promise(resolve => {
+ new ReportingObserver((reports, observer) => resolve([reports, observer]),
+ {types: ['permissions-policy-violation']}).observe();
+ });
+ const xhr = new XMLHttpRequest();
+ xhr.open("GET", document.location.href, false);
+ xhr.send();
+ check_report_format(await report);
+}, "Sync-xhr report only mode");
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/permissions-policy/reporting/sync-xhr-report-only.html.headers b/testing/web-platform/tests/permissions-policy/reporting/sync-xhr-report-only.html.headers
new file mode 100644
index 0000000000..ce914048e1
--- /dev/null
+++ b/testing/web-platform/tests/permissions-policy/reporting/sync-xhr-report-only.html.headers
@@ -0,0 +1 @@
+Permissions-Policy-Report-Only: sync-xhr=()
diff --git a/testing/web-platform/tests/permissions-policy/reporting/sync-xhr-reporting.html b/testing/web-platform/tests/permissions-policy/reporting/sync-xhr-reporting.html
new file mode 100644
index 0000000000..a8fdb09ece
--- /dev/null
+++ b/testing/web-platform/tests/permissions-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/permissions-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/permissions-policy/reporting/sync-xhr-reporting.html.headers b/testing/web-platform/tests/permissions-policy/reporting/sync-xhr-reporting.html.headers
new file mode 100644
index 0000000000..7f375e0b75
--- /dev/null
+++ b/testing/web-platform/tests/permissions-policy/reporting/sync-xhr-reporting.html.headers
@@ -0,0 +1 @@
+Permissions-Policy: sync-xhr=()
diff --git a/testing/web-platform/tests/permissions-policy/reporting/usb-report-only.https.html b/testing/web-platform/tests/permissions-policy/reporting/usb-report-only.https.html
new file mode 100644
index 0000000000..5a96531115
--- /dev/null
+++ b/testing/web-platform/tests/permissions-policy/reporting/usb-report-only.https.html
@@ -0,0 +1,30 @@
+<!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>
+ <div id='fs'></div>
+ <script>
+var check_report_format = ([reports, observer]) => {
+ let report = reports[0];
+ assert_equals(report.type, "permissions-policy-violation");
+ assert_equals(report.body.featureId, "usb");
+ assert_equals(report.body.disposition, "report");
+};
+
+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 USB');
+ await navigator.usb.getDevices();
+ check_report_format(await report);
+}, "USB report only mode");
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/permissions-policy/reporting/usb-report-only.https.html.headers b/testing/web-platform/tests/permissions-policy/reporting/usb-report-only.https.html.headers
new file mode 100644
index 0000000000..f4b92eadd9
--- /dev/null
+++ b/testing/web-platform/tests/permissions-policy/reporting/usb-report-only.https.html.headers
@@ -0,0 +1 @@
+Permissions-Policy-Report-Only: usb=()
diff --git a/testing/web-platform/tests/permissions-policy/reporting/usb-reporting.https.html b/testing/web-platform/tests/permissions-policy/reporting/usb-reporting.https.html
new file mode 100644
index 0000000000..1ec5ba4370
--- /dev/null
+++ b/testing/web-platform/tests/permissions-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/permissions-policy/reporting/usb-reporting.https.html.headers b/testing/web-platform/tests/permissions-policy/reporting/usb-reporting.https.html.headers
new file mode 100644
index 0000000000..ff22d62f10
--- /dev/null
+++ b/testing/web-platform/tests/permissions-policy/reporting/usb-reporting.https.html.headers
@@ -0,0 +1 @@
+Permissions-Policy: usb=()
diff --git a/testing/web-platform/tests/permissions-policy/reporting/xr-report-only.https.html b/testing/web-platform/tests/permissions-policy/reporting/xr-report-only.https.html
new file mode 100644
index 0000000000..f39ecf3432
--- /dev/null
+++ b/testing/web-platform/tests/permissions-policy/reporting/xr-report-only.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>
+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, "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, "report");
+};
+
+promise_test(async t => {
+ const report = new Promise(resolve => {
+ new ReportingObserver((reports, observer) => resolve([reports, observer]),
+ {types: ['permissions-policy-violation']}).observe();
+ });
+ try {
+ let supported = await navigator.xr.isSessionSupported('immersive-vr');
+ } catch (err) {
+ // IsSessionSupported should only throw with a permissions policy violation;
+ // however, inline does not trigger the permissions policy,
+ // so immersive-vr must be used.
+ assert_unreached("isSessionSupported should not throw in ReportOnly mode");
+ }
+ check_report_format(await report);
+}, "XR report only mode");
+ </script>
+ </body>
+</html>
diff --git a/testing/web-platform/tests/permissions-policy/reporting/xr-report-only.https.html.headers b/testing/web-platform/tests/permissions-policy/reporting/xr-report-only.https.html.headers
new file mode 100644
index 0000000000..141deb4dfc
--- /dev/null
+++ b/testing/web-platform/tests/permissions-policy/reporting/xr-report-only.https.html.headers
@@ -0,0 +1 @@
+Permissions-Policy-Report-Only: xr-spatial-tracking=()
diff --git a/testing/web-platform/tests/permissions-policy/reporting/xr-reporting.https.html b/testing/web-platform/tests/permissions-policy/reporting/xr-reporting.https.html
new file mode 100644
index 0000000000..d87d4fb028
--- /dev/null
+++ b/testing/web-platform/tests/permissions-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/permissions-policy/reporting/xr-reporting.https.html.headers b/testing/web-platform/tests/permissions-policy/reporting/xr-reporting.https.html.headers
new file mode 100644
index 0000000000..56b8c11a5b
--- /dev/null
+++ b/testing/web-platform/tests/permissions-policy/reporting/xr-reporting.https.html.headers
@@ -0,0 +1 @@
+Permissions-Policy: xr-spatial-tracking=()
diff --git a/testing/web-platform/tests/permissions-policy/resources/autoplay.js b/testing/web-platform/tests/permissions-policy/resources/autoplay.js
new file mode 100644
index 0000000000..56780cf6dc
--- /dev/null
+++ b/testing/web-platform/tests/permissions-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/permissions-policy/resources/nested-sandbox.html b/testing/web-platform/tests/permissions-policy/resources/nested-sandbox.html
new file mode 100644
index 0000000000..4ba512140d
--- /dev/null
+++ b/testing/web-platform/tests/permissions-policy/resources/nested-sandbox.html
@@ -0,0 +1,8 @@
+<!DOCTYPE html>
+<title>Return fullscreen permissions policy state</title>
+<script>
+ "use strict";
+ window.onload = () => {
+ window.parent.postMessage(document.featurePolicy.allowedFeatures().includes("fullscreen"),"*");
+ };
+</script>
diff --git a/testing/web-platform/tests/permissions-policy/resources/opaque-origin-history1.sub.https.html b/testing/web-platform/tests/permissions-policy/resources/opaque-origin-history1.sub.https.html
new file mode 100644
index 0000000000..cb1f214f53
--- /dev/null
+++ b/testing/web-platform/tests/permissions-policy/resources/opaque-origin-history1.sub.https.html
@@ -0,0 +1,15 @@
+<script>
+ window.addEventListener('message', e => {
+ if (e.data == 'redirect') {
+ location.assign(
+ "https://{{domains[]}}:{{location[port]}}/permissions-policy/resources/opaque-origin-history2.https.html");
+ }
+ });
+
+ parent.postMessage(
+ document.fullscreenEnabled ?
+ 'fullscreen enabled in opaque-origin-history1.html' :
+ 'fullscreen disabled in opaque-origin-history1.html',
+ '*'
+ );
+</script> \ No newline at end of file
diff --git a/testing/web-platform/tests/permissions-policy/resources/opaque-origin-history2.https.html b/testing/web-platform/tests/permissions-policy/resources/opaque-origin-history2.https.html
new file mode 100644
index 0000000000..20e63cf48b
--- /dev/null
+++ b/testing/web-platform/tests/permissions-policy/resources/opaque-origin-history2.https.html
@@ -0,0 +1,3 @@
+<script>
+ history.back();
+</script> \ No newline at end of file
diff --git a/testing/web-platform/tests/permissions-policy/resources/opaque-origin1.sub.https.html b/testing/web-platform/tests/permissions-policy/resources/opaque-origin1.sub.https.html
new file mode 100644
index 0000000000..f8a8c9d1ad
--- /dev/null
+++ b/testing/web-platform/tests/permissions-policy/resources/opaque-origin1.sub.https.html
@@ -0,0 +1,4 @@
+<script>
+ location.assign(
+ "https://{{domains[]}}:{{location[port]}}/permissions-policy/resources/opaque-origin2.https.html");
+</script> \ No newline at end of file
diff --git a/testing/web-platform/tests/permissions-policy/resources/opaque-origin2.https.html b/testing/web-platform/tests/permissions-policy/resources/opaque-origin2.https.html
new file mode 100644
index 0000000000..73122ff7f6
--- /dev/null
+++ b/testing/web-platform/tests/permissions-policy/resources/opaque-origin2.https.html
@@ -0,0 +1,8 @@
+<script>
+ parent.postMessage(
+ document.fullscreenEnabled ?
+ 'fullscreen enabled in opaque-origin2.html' :
+ 'fullscreen disabled in opaque-origin2.html',
+ '*'
+ );
+</script> \ No newline at end of file
diff --git a/testing/web-platform/tests/permissions-policy/resources/permissions-policy-allowedfeatures.html b/testing/web-platform/tests/permissions-policy/resources/permissions-policy-allowedfeatures.html
new file mode 100644
index 0000000000..f4b020273f
--- /dev/null
+++ b/testing/web-platform/tests/permissions-policy/resources/permissions-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/permissions-policy/resources/permissions-policy-autoplay.html b/testing/web-platform/tests/permissions-policy/resources/permissions-policy-autoplay.html
new file mode 100644
index 0000000000..665d19bea2
--- /dev/null
+++ b/testing/web-platform/tests/permissions-policy/resources/permissions-policy-autoplay.html
@@ -0,0 +1,11 @@
+<script src="/common/media.js"></script>
+<script src=/permissions-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/permissions-policy/resources/permissions-policy-battery.html b/testing/web-platform/tests/permissions-policy/resources/permissions-policy-battery.html
new file mode 100644
index 0000000000..643d6a4ef5
--- /dev/null
+++ b/testing/web-platform/tests/permissions-policy/resources/permissions-policy-battery.html
@@ -0,0 +1,9 @@
+<script>
+'use strict';
+
+Promise.resolve().then(() => navigator.getBattery()).then(battery => {
+ window.parent.postMessage({ type: 'availability-result', enabled: true }, '*');
+}, error => {
+ window.parent.postMessage({ type: 'availability-result', enabled: false }, '*');
+});
+</script>
diff --git a/testing/web-platform/tests/permissions-policy/resources/permissions-policy-bluetooth.html b/testing/web-platform/tests/permissions-policy/resources/permissions-policy-bluetooth.html
new file mode 100644
index 0000000000..2265bd01d2
--- /dev/null
+++ b/testing/web-platform/tests/permissions-policy/resources/permissions-policy-bluetooth.html
@@ -0,0 +1,9 @@
+<script>
+ 'use strict';
+
+ navigator.bluetooth.getDevices().then(devices => {
+ window.parent.postMessage({ type: 'availability-result', enabled: true}, '*');
+ }, error => {
+ window.parent.postMessage({ type: 'availability-result', enabled: false }, '*');
+ });
+</script> \ No newline at end of file
diff --git a/testing/web-platform/tests/permissions-policy/resources/permissions-policy-clipboard-read.html b/testing/web-platform/tests/permissions-policy/resources/permissions-policy-clipboard-read.html
new file mode 100644
index 0000000000..10fc45fd93
--- /dev/null
+++ b/testing/web-platform/tests/permissions-policy/resources/permissions-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/permissions-policy/resources/permissions-policy-clipboard-write.html b/testing/web-platform/tests/permissions-policy/resources/permissions-policy-clipboard-write.html
new file mode 100644
index 0000000000..7eb96e3db0
--- /dev/null
+++ b/testing/web-platform/tests/permissions-policy/resources/permissions-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/permissions-policy/resources/permissions-policy-compute-pressure.html b/testing/web-platform/tests/permissions-policy/resources/permissions-policy-compute-pressure.html
new file mode 100644
index 0000000000..fafe71ee04
--- /dev/null
+++ b/testing/web-platform/tests/permissions-policy/resources/permissions-policy-compute-pressure.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<script>
+'use strict';
+
+window.onload = async function() {
+ let enabled = true;
+ try {
+ const observer = new PressureObserver(() => {});
+ await observer.observe("cpu");
+ observer.disconnect();
+ } catch (e) {
+ enabled = false;
+ }
+ parent.postMessage({ type: 'availability-result', enabled }, '*');
+}
+</script>
diff --git a/testing/web-platform/tests/permissions-policy/resources/permissions-policy-generic-sensor.html b/testing/web-platform/tests/permissions-policy/resources/permissions-policy-generic-sensor.html
new file mode 100644
index 0000000000..59652e2e7a
--- /dev/null
+++ b/testing/web-platform/tests/permissions-policy/resources/permissions-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/permissions-policy/resources/permissions-policy-geolocation.html b/testing/web-platform/tests/permissions-policy/resources/permissions-policy-geolocation.html
new file mode 100644
index 0000000000..b858a52392
--- /dev/null
+++ b/testing/web-platform/tests/permissions-policy/resources/permissions-policy-geolocation.html
@@ -0,0 +1,22 @@
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script>
+ "use strict";
+
+ Promise.resolve().then(async () => {
+ test_driver.set_test_context(window.parent);
+ await test_driver.set_permission(
+ { name: "geolocation" },
+ "granted"
+ );
+ let enabled = true;
+ try {
+ await new Promise((resolve, reject) => {
+ navigator.geolocation.getCurrentPosition(resolve, reject);
+ });
+ } catch (e) {
+ enabled = false;
+ }
+ window.parent.postMessage({ type: "availability-result", enabled }, "*");
+ });
+</script>
diff --git a/testing/web-platform/tests/permissions-policy/resources/permissions-policy-idle-detection-worker.html b/testing/web-platform/tests/permissions-policy/resources/permissions-policy-idle-detection-worker.html
new file mode 100644
index 0000000000..5bcc398039
--- /dev/null
+++ b/testing/web-platform/tests/permissions-policy/resources/permissions-policy-idle-detection-worker.html
@@ -0,0 +1,10 @@
+<script>
+'use strict';
+
+let worker = new Worker('permissions-policy-idle-detection-worker.js');
+
+worker.onmessage = event => {
+ window.parent.postMessage(event.data, '*');
+};
+worker.postMessage({ type: 'ready' });
+</script>
diff --git a/testing/web-platform/tests/permissions-policy/resources/permissions-policy-idle-detection-worker.js b/testing/web-platform/tests/permissions-policy/resources/permissions-policy-idle-detection-worker.js
new file mode 100644
index 0000000000..0d348c72ae
--- /dev/null
+++ b/testing/web-platform/tests/permissions-policy/resources/permissions-policy-idle-detection-worker.js
@@ -0,0 +1,16 @@
+'use strict';
+
+// Dedicated worker
+if (typeof postMessage === 'function') {
+ onmessage = event => {
+ switch(event.data.type) {
+ case 'ready':
+ new IdleDetector().start().then(() => {
+ postMessage({ type: 'availability-result', enabled: true });
+ }, error => {
+ postMessage ({ type: 'availability-result', enabled: false });
+ });
+ break;
+ }
+ };
+}
diff --git a/testing/web-platform/tests/permissions-policy/resources/permissions-policy-idle-detection.html b/testing/web-platform/tests/permissions-policy/resources/permissions-policy-idle-detection.html
new file mode 100644
index 0000000000..f21a3851d1
--- /dev/null
+++ b/testing/web-platform/tests/permissions-policy/resources/permissions-policy-idle-detection.html
@@ -0,0 +1,10 @@
+<script>
+'use strict';
+
+new IdleDetector().start().then(() => {
+ window.parent.postMessage({ type: 'availability-result', enabled: true }, '*');
+}, error => {
+ window.parent.postMessage({ type: 'availability-result', enabled: false }, '*');
+});
+
+</script>
diff --git a/testing/web-platform/tests/permissions-policy/resources/permissions-policy-local-fonts.html b/testing/web-platform/tests/permissions-policy/resources/permissions-policy-local-fonts.html
new file mode 100644
index 0000000000..1b965d9c7d
--- /dev/null
+++ b/testing/web-platform/tests/permissions-policy/resources/permissions-policy-local-fonts.html
@@ -0,0 +1,11 @@
+<script>
+'use strict';
+
+window.onload = function() {
+ self.queryLocalFonts().then(() => {
+ parent.postMessage({ type: 'availability-result', enabled: true }, '*');
+ }, error => {
+ parent.postMessage({ type: 'availability-result', enabled: false }, '*');
+ });
+}
+</script> \ No newline at end of file
diff --git a/testing/web-platform/tests/permissions-policy/resources/permissions-policy-nested-subframe-policy.https.sub.html b/testing/web-platform/tests/permissions-policy/resources/permissions-policy-nested-subframe-policy.https.sub.html
new file mode 100644
index 0000000000..4c012bbae6
--- /dev/null
+++ b/testing/web-platform/tests/permissions-policy/resources/permissions-policy-nested-subframe-policy.https.sub.html
@@ -0,0 +1,57 @@
+<!DOCTYPE html>
+<body>
+<script>
+'use strict';
+const same_origin_src = '/permissions-policy/resources/permissions-policy-allowedfeatures.html';
+const cross_origin_src = 'https://{{domains[www1]}}:{{ports[https][0]}}' + same_origin_src;
+const subframe_header_policy = '?pipe=header(Permissions-Policy,fullscreen=';
+const policy_all = '*';
+const policy_self = 'self';
+const policy_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/permissions-policy/resources/permissions-policy-payment-extension.html b/testing/web-platform/tests/permissions-policy/resources/permissions-policy-payment-extension.html
new file mode 100644
index 0000000000..86f0ea3e48
--- /dev/null
+++ b/testing/web-platform/tests/permissions-policy/resources/permissions-policy-payment-extension.html
@@ -0,0 +1,60 @@
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="/webauthn/helpers.js"></script>
+<script>
+'use strict';
+
+const textEncoder = new TextEncoder();
+let authenticatorArgs = {
+ protocol: 'ctap2_1',
+ transport: 'internal',
+ hasResidentKey: true,
+ hasUserVerification: true,
+ isUserVerified: true,
+};
+
+window.onload = async function() {
+ await window.test_driver.add_virtual_authenticator(authenticatorArgs);
+ let enabled = true;
+ let message = `OK`;
+ try {
+ const publicKey = {
+ rp: {
+ id: window.location.hostname,
+ name: 'Joe',
+ },
+ user: {
+ name: 'user@domain',
+ id: Uint8Array.from('id', c => c.charCodeAt(0)),
+ displayName: 'User',
+ },
+ challenge: textEncoder.encode('Enrollment challenge'),
+ pubKeyCredParams: [{
+ type: 'public-key',
+ alg: -7, // ECDSA, not supported on Windows.
+ }, {
+ type: 'public-key',
+ alg: -257, // RSA, supported on Windows.
+ }],
+ authenticatorSelection: {
+ userVerification: 'required',
+ residentKey: 'required',
+ authenticatorAttachment: 'platform',
+ },
+ extensions: {
+ payment: {
+ isPayment: true,
+ },
+ }
+ };
+ await window.test_driver.bless('user activation');
+ await navigator.credentials.create({
+ publicKey
+ });
+ } catch (e) {
+ enabled = false;
+ message = e.name + '#' + e.message;
+ }
+ parent.postMessage({ type: 'availability-result', enabled, message }, '*');
+}
+</script>
diff --git a/testing/web-platform/tests/permissions-policy/resources/permissions-policy-payment.html b/testing/web-platform/tests/permissions-policy/resources/permissions-policy-payment.html
new file mode 100644
index 0000000000..641a5e65b6
--- /dev/null
+++ b/testing/web-platform/tests/permissions-policy/resources/permissions-policy-payment.html
@@ -0,0 +1,17 @@
+<script>
+'use strict';
+
+window.onload = function() {
+ var supportedInstruments = [ { supportedMethods: [ 'visa' ] } ];
+ var details = {
+ total: { label: 'Test', amount: { currency: 'USD', value: '5.00' } }
+ };
+ let enabled = true;
+ try {
+ new PaymentRequest(supportedInstruments, details);
+ } catch (e) {
+ enabled = false;
+ }
+ parent.postMessage({ type: 'availability-result', enabled }, '*');
+}
+</script>
diff --git a/testing/web-platform/tests/permissions-policy/resources/permissions-policy-picture-in-picture.html b/testing/web-platform/tests/permissions-policy/resources/permissions-policy-picture-in-picture.html
new file mode 100644
index 0000000000..e512f17ff0
--- /dev/null
+++ b/testing/web-platform/tests/permissions-policy/resources/permissions-policy-picture-in-picture.html
@@ -0,0 +1,11 @@
+<script src=/common/media.js></script>
+<script src=/permissions-policy/resources/picture-in-picture.js></script>
+<script>
+'use strict';
+
+window.addEventListener('load', () => {
+ isPictureInPictureAllowed().then(enabled => {
+ window.parent.postMessage({ type: 'availability-result', enabled }, '*');
+ });
+}, { once: true });
+</script>
diff --git a/testing/web-platform/tests/permissions-policy/resources/permissions-policy-report-json.js b/testing/web-platform/tests/permissions-policy/resources/permissions-policy-report-json.js
new file mode 100644
index 0000000000..ad84ff9ce4
--- /dev/null
+++ b/testing/web-platform/tests/permissions-policy/resources/permissions-policy-report-json.js
@@ -0,0 +1,20 @@
+/**
+ * @fileoverview functions for ensuring permissions 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/permissions-policy/resources/permissions-policy-screen-wakelock.html b/testing/web-platform/tests/permissions-policy/resources/permissions-policy-screen-wakelock.html
new file mode 100644
index 0000000000..b186632251
--- /dev/null
+++ b/testing/web-platform/tests/permissions-policy/resources/permissions-policy-screen-wakelock.html
@@ -0,0 +1,18 @@
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script>
+"use strict";
+
+Promise.resolve().then(async () => {
+ try {
+ await test_driver.set_permission(
+ { name: 'screen-wake-lock' }, 'granted');
+
+ const wakeLock = await navigator.wakeLock.request("screen");
+ await wakeLock.release();
+ window.parent.postMessage({ type: 'availability-result', enabled: true }, "*");
+ } catch (e) {
+ window.parent.postMessage({ type: 'availability-result', enabled: false }, "*");
+ }
+});
+</script>
diff --git a/testing/web-platform/tests/permissions-policy/resources/permissions-policy-serial-worker.html b/testing/web-platform/tests/permissions-policy/resources/permissions-policy-serial-worker.html
new file mode 100644
index 0000000000..56bcfaede9
--- /dev/null
+++ b/testing/web-platform/tests/permissions-policy/resources/permissions-policy-serial-worker.html
@@ -0,0 +1,10 @@
+<script>
+'use strict';
+
+let worker = new Worker('permissions-policy-serial-worker.js');
+
+worker.onmessage = event => {
+ window.parent.postMessage(event.data, '*');
+};
+worker.postMessage({ type: 'ready' });
+</script>
diff --git a/testing/web-platform/tests/permissions-policy/resources/permissions-policy-serial-worker.js b/testing/web-platform/tests/permissions-policy/resources/permissions-policy-serial-worker.js
new file mode 100644
index 0000000000..59bb6787dd
--- /dev/null
+++ b/testing/web-platform/tests/permissions-policy/resources/permissions-policy-serial-worker.js
@@ -0,0 +1,14 @@
+'use strict';
+
+// Dedicated worker
+if (typeof postMessage === 'function') {
+ onmessage = event => {
+ switch(event.data.type) {
+ case 'ready':
+ navigator.serial.getPorts().then(
+ () => postMessage({ type: 'availability-result', enabled: true }),
+ error => postMessage ({ type: 'availability-result', enabled: false }));
+ break;
+ }
+ };
+}
diff --git a/testing/web-platform/tests/permissions-policy/resources/permissions-policy-serial.html b/testing/web-platform/tests/permissions-policy/resources/permissions-policy-serial.html
new file mode 100644
index 0000000000..fe25f03482
--- /dev/null
+++ b/testing/web-platform/tests/permissions-policy/resources/permissions-policy-serial.html
@@ -0,0 +1,9 @@
+<script>
+'use strict';
+
+navigator.serial.getPorts().then(ports => {
+ window.parent.postMessage({ type: 'availability-result', enabled: true }, '*');
+}, error => {
+ window.parent.postMessage({ type: 'availability-result', enabled: false }, '*');
+});
+</script>
diff --git a/testing/web-platform/tests/permissions-policy/resources/permissions-policy-usb-worker.html b/testing/web-platform/tests/permissions-policy/resources/permissions-policy-usb-worker.html
new file mode 100644
index 0000000000..e50ac55a27
--- /dev/null
+++ b/testing/web-platform/tests/permissions-policy/resources/permissions-policy-usb-worker.html
@@ -0,0 +1,10 @@
+<script>
+'use strict';
+
+let worker = new Worker('permissions-policy-usb-worker.js');
+
+worker.onmessage = event => {
+ window.parent.postMessage(event.data, '*');
+};
+worker.postMessage({ type: 'ready' });
+</script> \ No newline at end of file
diff --git a/testing/web-platform/tests/permissions-policy/resources/permissions-policy-usb-worker.js b/testing/web-platform/tests/permissions-policy/resources/permissions-policy-usb-worker.js
new file mode 100644
index 0000000000..97d96e7fb7
--- /dev/null
+++ b/testing/web-platform/tests/permissions-policy/resources/permissions-policy-usb-worker.js
@@ -0,0 +1,14 @@
+'use strict';
+
+// Dedicated worker
+if (typeof postMessage === 'function') {
+ onmessage = event => {
+ switch(event.data.type) {
+ case 'ready':
+ navigator.usb.getDevices().then(
+ () => postMessage({ type: 'availability-result', enabled: true }),
+ error => postMessage ({ type: 'availability-result', enabled: false }));
+ break;
+ }
+ };
+}
diff --git a/testing/web-platform/tests/permissions-policy/resources/permissions-policy-usb.html b/testing/web-platform/tests/permissions-policy/resources/permissions-policy-usb.html
new file mode 100644
index 0000000000..8812ca7db9
--- /dev/null
+++ b/testing/web-platform/tests/permissions-policy/resources/permissions-policy-usb.html
@@ -0,0 +1,9 @@
+<script>
+'use strict';
+
+Promise.resolve().then(() => navigator.usb.getDevices()).then(devices => {
+ window.parent.postMessage({ type: 'availability-result', enabled: true }, '*');
+}, error => {
+ window.parent.postMessage({ type: 'availability-result', enabled: false }, '*');
+});
+</script>
diff --git a/testing/web-platform/tests/permissions-policy/resources/permissions-policy.js b/testing/web-platform/tests/permissions-policy/resources/permissions-policy.js
new file mode 100644
index 0000000000..00c0bf2326
--- /dev/null
+++ b/testing/web-platform/tests/permissions-policy/resources/permissions-policy.js
@@ -0,0 +1,460 @@
+// Feature test to avoid timeouts
+function assert_permissions_policy_supported() {
+ assert_not_equals(document.featurePolicy, undefined,
+ 'permissions policy is supported');
+}
+// Tests whether a feature that is enabled/disabled by permissions 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:
+// "/permissions-policy/resources/permissions-policy-payment.html",
+// "/permissions-policy/resources/permissions-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 via
+// postMessage with type: 'availability-result'.
+// 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://w3c.github.io/webappsec-permissions-policy/#features).
+// See examples at:
+// https://github.com/w3c/webappsec-permissions-policy/blob/main/features.md
+// allow_attribute: Optional argument, only used for testing fullscreen or
+// payment: either "allowfullscreen" or "allowpaymentrequest" is passed.
+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(evt => {
+ if (evt.source === frame.contentWindow &&
+ evt.data.type === 'availability-result') {
+ expect_feature_available(evt.data, feature_description);
+ document.body.removeChild(frame);
+ 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) {
+ const test_result = ({ name, message }, feature_description) => {
+ assert_equals(name, expected_result, message + '.');
+ };
+ 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.
+async function test_feature_in_iframe(feature_name, feature_promise_factory) {
+ if (location.hash.endsWith(`#${feature_name}`)) {
+ let message = 'Available';
+ let name = '#OK';
+ try {
+ await feature_promise_factory();
+ } catch (e) {
+ ({ name, message } = e);
+ }
+ window.parent.postMessage(
+ { type: 'availability-result', name, message }, '*');
+ }
+}
+
+// Returns true if the URL for this page indicates that it is embedded in an
+// iframe.
+function page_loaded_in_iframe() {
+ return new URLSearchParams(location.search).get('in-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) {
+ // Add an "in-iframe" query parameter so that we can detect the iframe'd
+ // version of the page and testharness script loading can be disabled in
+ // that version, as required for use of testdriver in non-toplevel browsing
+ // contexts.
+ return location.pathname + '?in-iframe=yes#' + 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 permissions 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 succeeds when an allow attribute is specified on a
+// cross-origin iframe.
+// 5. Feature usage fails when an allow attribute is specified on a
+// same-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_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 +
+ '" permissions 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 +
+ '" permissions 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 +
+ '" permissions 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);
+ },
+ 'permissions policy "' + feature_name +
+ '" can be enabled 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'");
+ },
+ 'permissions policy "' + feature_name +
+ '" can be disabled in same-origin iframes using "allow" attribute.');
+}
+
+// This function runs all permissions 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'".
+// 5. Feature usage fails when an allow attribute is specified on a
+// same-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 +
+ '" permissions 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 +
+ '" permissions 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 +
+ '" permissions 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'");
+ },
+ 'permissions 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'");
+ },
+ 'permissions policy "' + feature_name +
+ '" can be disabled in same-origin iframes using "allow" attribute.');
+}
+
+// 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_permissions_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_permissions_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 \\(\\), defines the frame
+// document's header policy on |feature|.
+// '(' and ')' need to be escaped because of server end
+// header parameter syntax limitation.
+// 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 *,
+// self, or ().
+// 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_permissions_policy_supported()
+ frame.src = src + '?pipe=sub|header(Permissions-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 === '\\(\\)') {
+ 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 === '\\(\\)') {
+ 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.permissionsPolicy;
+ 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/permissions-policy/resources/picture-in-picture.js b/testing/web-platform/tests/permissions-policy/resources/picture-in-picture.js
new file mode 100644
index 0000000000..1bf3c1c12a
--- /dev/null
+++ b/testing/web-platform/tests/permissions-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/permissions-policy/resources/redirect-on-load.html b/testing/web-platform/tests/permissions-policy/resources/redirect-on-load.html
new file mode 100644
index 0000000000..54d3cf55b1
--- /dev/null
+++ b/testing/web-platform/tests/permissions-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:
+// "permissions-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/permissions-policy/resources/sandbox-self.html b/testing/web-platform/tests/permissions-policy/resources/sandbox-self.html
new file mode 100644
index 0000000000..8240de99c6
--- /dev/null
+++ b/testing/web-platform/tests/permissions-policy/resources/sandbox-self.html
@@ -0,0 +1,23 @@
+<!DOCTYPE html>
+<title>Return fullscreen permissions policy state from self and a sandboxed child frame</title>
+<script>
+ "use strict";
+ window.onload = () => {
+ let frame = document.createElement('iframe');
+ frame.src = "/permissions-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/permissions-policy/resources/sandbox-self.html.headers b/testing/web-platform/tests/permissions-policy/resources/sandbox-self.html.headers
new file mode 100644
index 0000000000..ff7ae41353
--- /dev/null
+++ b/testing/web-platform/tests/permissions-policy/resources/sandbox-self.html.headers
@@ -0,0 +1 @@
+Permissions-Policy: fullscreen=self