diff options
Diffstat (limited to 'testing/web-platform/tests/permissions-policy/resources')
37 files changed, 1063 insertions, 0 deletions
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-private-state-token-issuance.html b/testing/web-platform/tests/permissions-policy/resources/permissions-policy-private-state-token-issuance.html new file mode 100644 index 0000000000..f2bf31918e --- /dev/null +++ b/testing/web-platform/tests/permissions-policy/resources/permissions-policy-private-state-token-issuance.html @@ -0,0 +1,35 @@ +<!DOCTYPE html> +<script> +'use strict'; + +window.onload = function() { + // Check issuance operation availability for both Request and XMLHttpRequest. + // They are tied to the same permission policy. They should be both enabled or disabled. + let num_enabled = 2; + try { + const issue_request = new Request("https://issuer.example/", { + privateToken: { + version: 1, + operation: "token-request" + } + }); + } catch (e) { + num_enabled--; + } + + try { + const xhr = new XMLHttpRequest(); + xhr.open("GET", "https://issuer.example/"); + xhr.setPrivateToken({ + version: 1, + operation: "token-request" + }); + } catch (e) { + num_enabled--; + } + parent.postMessage({ + type: 'availability-result', + num_operations_enabled: num_enabled, + }, '*'); +} +</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..5c2562946b --- /dev/null +++ b/testing/web-platform/tests/permissions-policy/resources/permissions-policy-screen-wakelock.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: 'screen-wake-lock' }, 'granted'); + + try { + const wakeLock = await navigator.wakeLock.request("screen"); + window.parent.postMessage({ type: 'availability-result', enabled: true }, "*"); + await wakeLock.release(); + } catch (e) { + if (e instanceof DOMException && e.name === "NotAllowedError") { + window.parent.postMessage({ type: 'availability-result', enabled: false }, "*"); + } else { + throw e; + } + } +}); +</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..32fb4cfd4a --- /dev/null +++ b/testing/web-platform/tests/permissions-policy/resources/permissions-policy.js @@ -0,0 +1,472 @@ +// 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 +// by passing "allowfullscreen". +// is_promise_test: Optional argument, true if this call should return a +// promise. Used by test_feature_availability_with_post_message_result() +function test_feature_availability( + feature_description, test, src, expect_feature_available, feature_name, + allow_attribute, is_promise_test = false) { + 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); + } + + function expectFeatureAvailable(evt) { + if (evt.source === frame.contentWindow && + evt.data.type === 'availability-result') { + expect_feature_available(evt.data, feature_description); + document.body.removeChild(frame); + test.done(); + } + } + + if (!is_promise_test) { + window.addEventListener('message', test.step_func(expectFeatureAvailable)); + document.body.appendChild(frame); + return; + } + + const promise = new Promise((resolve) => { + window.addEventListener('message', resolve); + }).then(expectFeatureAvailable); + document.body.appendChild(frame); + return promise; +} + +// 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 + '.'); + }; + return test_feature_availability( + null, test, src, test_result, allow_attribute, undefined, true); +} + +// 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); + promise_test( + t => { + return 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); + promise_test( + t => { + return 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. + promise_test( + t => { + return 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'. + promise_test( + t => { + return 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); + promise_test( + t => { + return 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); + promise_test( + t => { + return 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'. + promise_test( + t => { + return 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'. + promise_test( + t => { + return 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 |