diff options
Diffstat (limited to 'testing/web-platform/tests/fenced-frame/resources')
205 files changed, 4214 insertions, 0 deletions
diff --git a/testing/web-platform/tests/fenced-frame/resources/ancestor-throttle-iframe-csp.https.html b/testing/web-platform/tests/fenced-frame/resources/ancestor-throttle-iframe-csp.https.html new file mode 100644 index 0000000000..28fadb296c --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/resources/ancestor-throttle-iframe-csp.https.html @@ -0,0 +1,18 @@ +<!DOCTYPE html> +<html> +<script src="utils.js"></script> +<title>Header Inheritance CSP Reporting Page</title> +<body> +<script> +// This file is embedded in an iframe by ancestor-throttle-inner.https.html +// which in turn has been embedded in a fenced frame by +// ancestor-throttle.https.html +async function init() { + const [ancestor_key] = parseKeylist(); + writeValueToServer(ancestor_key, "loaded"); +} + +init(); +</script> +</body> +</html> diff --git a/testing/web-platform/tests/fenced-frame/resources/ancestor-throttle-iframe-csp.https.html.headers b/testing/web-platform/tests/fenced-frame/resources/ancestor-throttle-iframe-csp.https.html.headers new file mode 100644 index 0000000000..bb76329b1d --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/resources/ancestor-throttle-iframe-csp.https.html.headers @@ -0,0 +1,2 @@ +Supports-Loading-Mode: fenced-frame +Content-Security-Policy: frame-ancestors 'self' diff --git a/testing/web-platform/tests/fenced-frame/resources/ancestor-throttle-iframe-xfo.https.html b/testing/web-platform/tests/fenced-frame/resources/ancestor-throttle-iframe-xfo.https.html new file mode 100644 index 0000000000..267aa076c0 --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/resources/ancestor-throttle-iframe-xfo.https.html @@ -0,0 +1,18 @@ +<!DOCTYPE html> +<html> +<script src="utils.js"></script> +<title>Header Inheritance XFO Reporting Page</title> +<body> +<script> +// This file is embedded in an iframe by ancestor-throttle-inner.https.html +// which in turn has been embedded in a fenced frame by +// ancestor-throttle.https.html +async function init() { + const [ancestor_key] = parseKeylist(); + writeValueToServer(ancestor_key, "loaded"); +} + +init(); +</script> +</body> +</html> diff --git a/testing/web-platform/tests/fenced-frame/resources/ancestor-throttle-iframe-xfo.https.html.headers b/testing/web-platform/tests/fenced-frame/resources/ancestor-throttle-iframe-xfo.https.html.headers new file mode 100644 index 0000000000..63d5019c35 --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/resources/ancestor-throttle-iframe-xfo.https.html.headers @@ -0,0 +1,2 @@ +Supports-Loading-Mode: fenced-frame +X-Frame-Options: SAMEORIGIN diff --git a/testing/web-platform/tests/fenced-frame/resources/ancestor-throttle-inner.https.html b/testing/web-platform/tests/fenced-frame/resources/ancestor-throttle-inner.https.html new file mode 100644 index 0000000000..e0977c73f0 --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/resources/ancestor-throttle-inner.https.html @@ -0,0 +1,33 @@ +<!DOCTYPE html> +<html> +<script src="utils.js"></script> +<script src="/common/get-host-info.sub.js"></script> +<title>Header Inheritance Inner Page</title> +<body> +<script> +// This file is embedded in a fenced frame by ancestor-throttle.https.html. +// This is an intermediate step that embeds another page in an iframe to check +// that the child page only checks up to this page's origin when deciding +// if it should load. +async function init() { + const [ancestor_key, embed_url, cross_origin_iframe] = + parseKeylist(); + // The URL will be ancestor-throttle-iframe-*.https.html + let iframe_url; + if (cross_origin_iframe == "true") { + iframe_url = generateURL(new URL(embed_url, + get_host_info().HTTPS_REMOTE_ORIGIN), parseKeylist()); + } else { + iframe_url = generateURL(new URL(embed_url, + get_host_info().HTTPS_ORIGIN), parseKeylist()); + } + + const iframe = document.createElement('iframe'); + iframe.src = iframe_url; + document.body.append(iframe); +} + +init(); +</script> +</body> +</html> diff --git a/testing/web-platform/tests/fenced-frame/resources/ancestor-throttle-inner.https.html.headers b/testing/web-platform/tests/fenced-frame/resources/ancestor-throttle-inner.https.html.headers new file mode 100644 index 0000000000..1b63235b7c --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/resources/ancestor-throttle-inner.https.html.headers @@ -0,0 +1 @@ +Supports-Loading-Mode: fenced-frame diff --git a/testing/web-platform/tests/fenced-frame/resources/ancestor-throttle-nested.https.html b/testing/web-platform/tests/fenced-frame/resources/ancestor-throttle-nested.https.html new file mode 100644 index 0000000000..a26b7bfdc2 --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/resources/ancestor-throttle-nested.https.html @@ -0,0 +1,22 @@ +<!DOCTYPE html> +<html> +<script src="utils.js"></script> +<title>Header Inheritance CSP Reporting Page</title> +<body> +<script> +// This file is embedded in an iframe by ancestor-throttle-inner.https.html +// which in turn has been embedded in a fenced frame by +// ancestor-throttle.https.html. This in turn will load a same-origin iframe. +async function init() { + const url = new URL(location.href); + const embed_url = generateURL(url.searchParams.get("nested_url"), + parseKeylist()); + const iframe = document.createElement('iframe'); + iframe.src = embed_url; + document.body.append(iframe); +} + +init(); +</script> +</body> +</html> diff --git a/testing/web-platform/tests/fenced-frame/resources/ancestor-throttle-nested.https.html.headers b/testing/web-platform/tests/fenced-frame/resources/ancestor-throttle-nested.https.html.headers new file mode 100644 index 0000000000..1b63235b7c --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/resources/ancestor-throttle-nested.https.html.headers @@ -0,0 +1 @@ +Supports-Loading-Mode: fenced-frame diff --git a/testing/web-platform/tests/fenced-frame/resources/automatic-beacon-helper.js b/testing/web-platform/tests/fenced-frame/resources/automatic-beacon-helper.js new file mode 100644 index 0000000000..d0a4133e84 --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/resources/automatic-beacon-helper.js @@ -0,0 +1,104 @@ +// This is a helper file used for the automatic-beacon-*.https.html tests. +// To use this, make sure you import these scripts: +// <script src="/resources/testharness.js"></script> +// <script src="/resources/testharnessreport.js"></script> +// <script src="/common/utils.js"></script> +// <script src="/common/dispatcher/dispatcher.js"></script> +// <script src="resources/utils.js"></script> +// <script src="/resources/testdriver.js"></script> +// <script src="/resources/testdriver-actions.js"></script> +// <script src="/resources/testdriver-vendor.js"></script> +// <script src="/common/get-host-info.sub.js"></script> + +const NavigationTrigger = { + Click: 0, + ClickOnce: 1, + CrossOriginClick: 2, + CrossOriginClickNoOptIn: 3 +}; + +// Registers an automatic beacon in a given remote context frame, and registers +// the navigation handler for the frame that will trigger the beacon. +// remote_context: The context for the fenced frame or URN iframe. +// beacon_events: An array of FenceEvents to register with the frame. +// navigation_url: The URL the frame will navigate to. +// navigation_trigger: How the navigation will be performed. Either through a +// click, a click with a `once` event, a click in a +// cross-origin subframe, or a click in a cross-origin +// subframe with no opt-in header. +// target: the target of the navigation. Either '_blank' or +// '_unfencedTop'. +async function setupAutomaticBeacon( + remote_context, beacon_events, navigation_url = 'resources/dummy.html', + navigation_trigger = NavigationTrigger.Click, target = '_blank') { + const full_url = new URL(navigation_url, location.href); + await remote_context.execute( + async ( + NavigationTrigger, beacon_events, navigation_trigger, full_url, + target) => { + switch (navigation_trigger) { + case NavigationTrigger.Click: + addEventListener('click', (event) => { + beacon_events.forEach((beacon_event) => { + window.fence.setReportEventDataForAutomaticBeacons( + beacon_event); + }); + window.open(full_url, target); + }); + break; + case NavigationTrigger.ClickOnce: + beacon_events.forEach((beacon_event) => { + window.fence.setReportEventDataForAutomaticBeacons(beacon_event); + }); + addEventListener('click', (event) => { + window.open(full_url, target); + }); + break; + case NavigationTrigger.CrossOriginClick: + case NavigationTrigger.CrossOriginClickNoOptIn: + beacon_events.forEach((beacon_event) => { + window.fence.setReportEventDataForAutomaticBeacons(beacon_event); + }); + // Add a cross-origin iframe that will perform the top-level + // navigation. Do not set the 'Allow-Fenced-Frame-Automatic-Beacons' + // header to true. + const iframe = await attachIFrameContext({ + origin: get_host_info().HTTPS_REMOTE_ORIGIN, + headers: [[ + 'Allow-Fenced-Frame-Automatic-Beacons', + navigation_trigger == NavigationTrigger.CrossOriginClick ? + 'true' : + 'false' + ]] + }); + await iframe.execute(async (full_url, target) => { + addEventListener('click', (event) => { + window.open(full_url, target); + }); + }, [full_url, target]); + break; + } + }, + [NavigationTrigger, beacon_events, navigation_trigger, full_url, target]); +} + +// Checks if an automatic beacon of type `event_type` with contents `event_data` +// was sent out or not. +// event_type: The automatic beacon type to check. +// event_data: The automatic beacon data to check. +// expected_success: Whether we expect the automatic beacon to be sent. +// t: The WPT's test object. Only required if +// expected_success = false. +async function verifyBeaconData( + event_type, event_data, expected_success = true, t) { + if (expected_success) { + const beacon_initiator_origin = + await nextAutomaticBeacon(event_type, event_data); + assert_equals(beacon_initiator_origin, get_host_info().HTTPS_ORIGIN); + } else { + const timeout = new Promise(r => t.step_timeout(r, 1000)); + const result = await Promise.race( + [nextAutomaticBeacon(event_type, event_data), timeout]); + assert_true(typeof result === 'undefined'); + } +} diff --git a/testing/web-platform/tests/fenced-frame/resources/automatic-beacon-store.py b/testing/web-platform/tests/fenced-frame/resources/automatic-beacon-store.py new file mode 100644 index 0000000000..ba1b73201b --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/resources/automatic-beacon-store.py @@ -0,0 +1,44 @@ +""" +Automatic beacon store server. + +- When a request body is specified, stores the data in the body and serves a 200 + response without body. +- When a request body is not specified, serves a 200 response whose body + contains the stored value from the automatic beacon. Since the data is stored + using a hash of the data as the key, it expects an `expected_body` query + parameter to know what key to look up. If the stored value doesn't exist, + serves a 200 response with an empty body. +""" +import uuid +import hashlib + +NO_DATA_STRING = b"<No data>" +NOT_SET_STRING = b"<Not set>" + +# The server stash requires a uuid to store data. Use a hash of the automatic +# beacon data as the uuid to store and retrieve the data. +def string_to_uuid(input): + hash_value = hashlib.md5(str(input).encode("UTF-8")).hexdigest() + return str(uuid.UUID(hex=hash_value)) + +def main(request, response): + stash = request.server.stash; + event_type = request.GET.first(b"type", NO_DATA_STRING) + + # The stash is accessed concurrently by many clients. A lock is used to + # avoid interleaved read/write from different clients. + with stash.lock: + # Requests with a body imply they were sent as an automatic beacon. Note + # that this only stores the most recent beacon that was sent. + if request.method == "POST": + request_body = request.body or NO_DATA_STRING + request_headers = request.headers.get("Origin") or NO_DATA_STRING + stash.put(string_to_uuid(event_type + request_body), + request_headers) + return (200, [], b"") + + # Requests without a body imply they were sent as the request from + # nextAutomaticBeacon(). + expected_body = request.GET.first(b"expected_body", NO_DATA_STRING) + data = stash.take(string_to_uuid(event_type + expected_body)) or NOT_SET_STRING + return(200, [], data) diff --git a/testing/web-platform/tests/fenced-frame/resources/automatic-beacon-unfenced-page.html b/testing/web-platform/tests/fenced-frame/resources/automatic-beacon-unfenced-page.html new file mode 100644 index 0000000000..4ce7e0d78a --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/resources/automatic-beacon-unfenced-page.html @@ -0,0 +1,21 @@ +<!DOCTYPE html> +<title>Page navigated to by an _unfencedTop navigation</title> + +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="utils.js"></script> +<script src="/common/get-host-info.sub.js"></script> + +<body> +<script> + promise_test(async(t) => { + // This page is navigated to from an '_unfencedTop' navigation by + // '../automatic-beacon-unfenced-top.https.html'. An automatic beacon will + // have been sent as a result of the navigation. + const beacon_data = "This is the beacon data!"; + const beacon_initiator_origin = await nextAutomaticBeacon( + "reserved.top_navigation_commit", beacon_data); + assert_equals(beacon_initiator_origin, get_host_info().HTTPS_ORIGIN); + }); +</script> +</body> diff --git a/testing/web-platform/tests/fenced-frame/resources/background-fetch-inner.https.html b/testing/web-platform/tests/fenced-frame/resources/background-fetch-inner.https.html new file mode 100644 index 0000000000..1bca25a957 --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/resources/background-fetch-inner.https.html @@ -0,0 +1,47 @@ +<!DOCTYPE html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="utils.js"></script> +<title>Fenced frame content to report the result of background fetch</title> + +<body> + <script> + (async function () { + const [background_fetch_register_key, method] = parseKeylist(); + const file = 'background-fetch-inner.https.html.headers'; + + navigator.serviceWorker.register("empty-worker.js", { scope: location.href }); + const registration = await navigator.serviceWorker.ready; + + const url = new URL(location.href); + + let promise; + switch (method) { + case 'fetch': + promise = registration.backgroundFetch.fetch('test-fetch', file); + break; + case 'get': + promise = registration.backgroundFetch.get('test-fetch'); + break; + case 'getIds': + promise = registration.backgroundFetch.getIds(); + break + default: + promise = Promise.resolve(); + } + + promise + .then(() => { + writeValueToServer(background_fetch_register_key, + `[backgroundFetch.${method}] Unexpectedly started`); + }) + .catch(() => { + writeValueToServer(background_fetch_register_key, + `[backgroundFetch.${method}] Failed inside fencedframe as expected`); + }) + .finally(() => { + registration.unregister(); + }); + })(); + </script> +</body> diff --git a/testing/web-platform/tests/fenced-frame/resources/background-fetch-inner.https.html.headers b/testing/web-platform/tests/fenced-frame/resources/background-fetch-inner.https.html.headers new file mode 100644 index 0000000000..6247f6d632 --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/resources/background-fetch-inner.https.html.headers @@ -0,0 +1 @@ +Supports-Loading-Mode: fenced-frame
\ No newline at end of file diff --git a/testing/web-platform/tests/fenced-frame/resources/background-fetch-sw-inner.https.html b/testing/web-platform/tests/fenced-frame/resources/background-fetch-sw-inner.https.html new file mode 100644 index 0000000000..78e58e5bbf --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/resources/background-fetch-sw-inner.https.html @@ -0,0 +1,38 @@ +<!DOCTYPE html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="utils.js"></script> +<title>Fenced frame content to report the result of background fetch in SW</title> + +<body> + <script type="module"> + const [background_fetch_register_key, method] = parseKeylist(); + + const getController = () => { + if (navigator.serviceWorker.controller) { + return navigator.serviceWorker.controller; + } + return new Promise(resolve => { + navigator.serviceWorker.addEventListener('controllerchange', () => { + resolve(navigator.serviceWorker.controller); + }); + }); + }; + + const sendMessageToServiceWorker = async () => { + const ctrl = await getController(); + return new Promise(resolve => { + ctrl.postMessage(method); + navigator.serviceWorker.onmessage = e => { + resolve(e.data); + } + }); + }; + + await navigator.serviceWorker.register( + "background-fetch-sw.js", { scope: location.href }); + const data = await sendMessageToServiceWorker(); + + writeValueToServer(background_fetch_register_key, data); + </script> +</body> diff --git a/testing/web-platform/tests/fenced-frame/resources/background-fetch-sw-inner.https.html.headers b/testing/web-platform/tests/fenced-frame/resources/background-fetch-sw-inner.https.html.headers new file mode 100644 index 0000000000..1b63235b7c --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/resources/background-fetch-sw-inner.https.html.headers @@ -0,0 +1 @@ +Supports-Loading-Mode: fenced-frame diff --git a/testing/web-platform/tests/fenced-frame/resources/background-fetch-sw.js b/testing/web-platform/tests/fenced-frame/resources/background-fetch-sw.js new file mode 100644 index 0000000000..44b7d087b5 --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/resources/background-fetch-sw.js @@ -0,0 +1,36 @@ +self.addEventListener('install', e => e.waitUntil(skipWaiting())); +self.addEventListener('activate', e => e.waitUntil(clients.claim())); + +self.addEventListener('message', async e => { + const method = e.data; + + let promise; + switch (method) { + case 'fetch': + promise = self.registration.backgroundFetch.fetch( + 'test-fetch', ['background-fetch-inner.https.html.headers'], + {title: 'Background Fetch'}); + break; + case 'get': + promise = self.registration.backgroundFetch.get('test-fetch') + break; + case 'getIds': + promise = registration.backgroundFetch.getIds(); + break; + default: + promise = Promise.resolve(); + break; + } + + const message = + await promise + .then(() => { + return `[backgroundFetch.${method}] Unexpectedly started`; + }) + .catch((e) => { + return `[backgroundFetch.${ + method}] Failed inside fencedframe as expected`; + }); + + e.source.postMessage(message); +}); diff --git a/testing/web-platform/tests/fenced-frame/resources/background-sync-helper.js b/testing/web-platform/tests/fenced-frame/resources/background-sync-helper.js new file mode 100644 index 0000000000..78b69f15de --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/resources/background-sync-helper.js @@ -0,0 +1,23 @@ +const getOneShotSyncPromise = (registration, method) => { + if (method === 'register') { + return registration.sync.register('fencedframe-oneshot'); + } else if (method === 'getTags') { + return registration.sync.getTags(); + } + return Promise.resolve(); +}; + +const getPeriodicSyncPromise = (registration, method) => { + if (method === 'register') { + return registration.periodicSync.register( + 'fencedframe-periodic', {minInterval: 1000}); + } else if (method === 'getTags') { + return registration.periodicSync.getTags(); + } else if (method === 'unregister') { + return registration.periodicSync.unregister('fencedframe-periodic'); + } else { + return Promise.resolve(); + } +}; + +export {getOneShotSyncPromise, getPeriodicSyncPromise} diff --git a/testing/web-platform/tests/fenced-frame/resources/background-sync-inner.https.html b/testing/web-platform/tests/fenced-frame/resources/background-sync-inner.https.html new file mode 100644 index 0000000000..81974c803a --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/resources/background-sync-inner.https.html @@ -0,0 +1,32 @@ +<!DOCTYPE html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="utils.js"></script> +<title>Fenced frame content to report the result of background sync's register</title> + +<body> +<script type="module"> + import {getOneShotSyncPromise, getPeriodicSyncPromise} from './background-sync-helper.js'; + + const [background_sync_register_key] = parseKeylist(); + const searchParams = new URL(location.href).searchParams; + const method = searchParams.get('method'); + const periodic = searchParams.get('periodic'); + + navigator.serviceWorker.register("empty-worker.js", { scope: location.href }); + const registration = await navigator.serviceWorker.ready; + + try { + if (periodic) { + await getPeriodicSyncPromise(registration, method); + } else { + await getOneShotSyncPromise(registration, method); + } + writeValueToServer(background_sync_register_key, "unexpectedly registered"); + } catch (e) { + writeValueToServer(background_sync_register_key, e.message); + } finally { + registration.unregister(); + } +</script> +</body> diff --git a/testing/web-platform/tests/fenced-frame/resources/background-sync-inner.https.html.headers b/testing/web-platform/tests/fenced-frame/resources/background-sync-inner.https.html.headers new file mode 100644 index 0000000000..6247f6d632 --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/resources/background-sync-inner.https.html.headers @@ -0,0 +1 @@ +Supports-Loading-Mode: fenced-frame
\ No newline at end of file diff --git a/testing/web-platform/tests/fenced-frame/resources/background-sync-sw-inner.https.html b/testing/web-platform/tests/fenced-frame/resources/background-sync-sw-inner.https.html new file mode 100644 index 0000000000..b9521a4e20 --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/resources/background-sync-sw-inner.https.html @@ -0,0 +1,41 @@ +<!DOCTYPE html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="utils.js"></script> +<title>Fenced frame content to report the result of background sync's register in SW</title> + +<body> +<script type="module"> + const [background_sync_register_key] = parseKeylist(); + const searchParams = new URL(location.href).searchParams; + const method = searchParams.get('method'); + const isPeriodic = searchParams.get('periodic'); + + const getController = () => { + if (navigator.serviceWorker.controller) { + return navigator.serviceWorker.controller; + } + return new Promise(resolve => { + navigator.serviceWorker.addEventListener('controllerchange', () => { + resolve(navigator.serviceWorker.controller); + }); + }); + }; + + const sendMessageToServiceWorker = async () => { + const ctrl = await getController(); + return new Promise(resolve => { + ctrl.postMessage({method, isPeriodic}); + navigator.serviceWorker.onmessage = e => { + resolve(e.data); + } + }); + }; + + await navigator.serviceWorker.register( + "background-sync-sw.js", { scope: location.href, type: "module" }); + const data = await sendMessageToServiceWorker(); + + writeValueToServer(background_sync_register_key, data); +</script> +</body> diff --git a/testing/web-platform/tests/fenced-frame/resources/background-sync-sw-inner.https.html.headers b/testing/web-platform/tests/fenced-frame/resources/background-sync-sw-inner.https.html.headers new file mode 100644 index 0000000000..6247f6d632 --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/resources/background-sync-sw-inner.https.html.headers @@ -0,0 +1 @@ +Supports-Loading-Mode: fenced-frame
\ No newline at end of file diff --git a/testing/web-platform/tests/fenced-frame/resources/background-sync-sw.js b/testing/web-platform/tests/fenced-frame/resources/background-sync-sw.js new file mode 100644 index 0000000000..5b0c791f0d --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/resources/background-sync-sw.js @@ -0,0 +1,21 @@ +import {getOneShotSyncPromise, getPeriodicSyncPromise} from './background-sync-helper.js'; + +self.addEventListener('install', e => e.waitUntil(skipWaiting())); +self.addEventListener('activate', e => e.waitUntil(clients.claim())); + +self.addEventListener('message', async e => { + const {method, isPeriodic} = e.data; + const promise = isPeriodic ? + getPeriodicSyncPromise(self.registration, method) : + getOneShotSyncPromise(self.registration, method); + const message = + await promise + .then(() => { + return `[background synnc ${method}] Unexpectedly started`; + }) + .catch((e) => { + return e.message; + }); + + e.source.postMessage(message); +}); diff --git a/testing/web-platform/tests/fenced-frame/resources/badging-sw.js b/testing/web-platform/tests/fenced-frame/resources/badging-sw.js new file mode 100644 index 0000000000..5bc3c9a190 --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/resources/badging-sw.js @@ -0,0 +1,23 @@ +self.addEventListener('install', e => e.waitUntil(skipWaiting())); +self.addEventListener('activate', e => e.waitUntil(clients.claim())); + +self.addEventListener('message', async e => { + const method = e.data; + + let promise; + if (method === 'setAppBadge') { + promise = self.navigator.setAppBadge(1); + } else if (method === 'clearAppBadge') { + promise = self.navigator.clearAppBadge(); + } else { + promise = Promise.resolve(); + } + + const error = await promise + .then(() => { + return `[Badging API ${method}] Unexpectedly started`; + }) + .catch((e) => e); + + e.source.postMessage(error); +}); diff --git a/testing/web-platform/tests/fenced-frame/resources/before-unload-inner.html b/testing/web-platform/tests/fenced-frame/resources/before-unload-inner.html new file mode 100644 index 0000000000..6d23cf88a3 --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/resources/before-unload-inner.html @@ -0,0 +1,27 @@ +<!DOCTYPE html> +<script src="utils.js"></script> +<title>Fenced frame content to report the beforeunload event is not fired</title> + +<body> +<script> +window.onload = () => { + const [before_unload_key] = parseKeylist(); + const url = new URL(location.href); + const next_url = url.searchParams.get('next_url'); + + if (next_url != null) { + writeValueToServer( + before_unload_key, 'Loaded the next url in a fenced frame'); + return; + } + + window.onbeforeunload = () => { + writeValueToServer( + before_unload_key, 'The beforeunload event is unexpectely fired.'); + }; + + location.href = + generateURL('before-unload-inner.html?next_url', [before_unload_key]); +}; +</script> +</body> diff --git a/testing/web-platform/tests/fenced-frame/resources/before-unload-inner.html.headers b/testing/web-platform/tests/fenced-frame/resources/before-unload-inner.html.headers new file mode 100644 index 0000000000..6247f6d632 --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/resources/before-unload-inner.html.headers @@ -0,0 +1 @@ +Supports-Loading-Mode: fenced-frame
\ No newline at end of file diff --git a/testing/web-platform/tests/fenced-frame/resources/check-header-referrer.py b/testing/web-platform/tests/fenced-frame/resources/check-header-referrer.py new file mode 100644 index 0000000000..b06fbc2704 --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/resources/check-header-referrer.py @@ -0,0 +1,16 @@ +import os + +from wptserve.utils import isomorphic_decode + + +def main(request, response): + response.headers.set(b"supports-loading-mode", b"fenced-frame") + + script = u""" + <script src="utils.js"></script> + <script> + const [referrer_key, _] = parseKeylist(); + writeValueToServer(referrer_key, "%s") + </script> + """ % (isomorphic_decode(request.headers.get(b"referer", b""))) + return (200, [], script) diff --git a/testing/web-platform/tests/fenced-frame/resources/check-header-sec-fetch-dest.py b/testing/web-platform/tests/fenced-frame/resources/check-header-sec-fetch-dest.py new file mode 100644 index 0000000000..98231079b3 --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/resources/check-header-sec-fetch-dest.py @@ -0,0 +1,14 @@ +import os + + +def main(request, response): + response.headers.set(b"supports-loading-mode", b"fenced-frame") + + script = u""" + <script src="utils.js"></script> + <script> + const [secfetch_key] = parseKeylist(); + writeValueToServer(secfetch_key, "%s") + </script> + """ % (request.headers.get(b"sec-fetch-dest", b"none")) + return (200, [], script) diff --git a/testing/web-platform/tests/fenced-frame/resources/client-hints-iframe-inner.sub.https.html b/testing/web-platform/tests/fenced-frame/resources/client-hints-iframe-inner.sub.https.html new file mode 100644 index 0000000000..d02abd6957 --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/resources/client-hints-iframe-inner.sub.https.html @@ -0,0 +1,11 @@ +<!DOCTYPE html> +<title>Client Hint Echoing Iframe</title> +<body> +<script> +window.parent.postMessage({'headers': { + 'sec-ch-viewport-width': '{{header_or_default(sec-ch-viewport-width, )}}', + 'sec-ch-ua-reduced': '{{header_or_default(sec-ch-ua-reduced, )}}', + 'sec-ch-ua-mobile': '{{header_or_default(sec-ch-ua-mobile, )}}', +}}, '*'); +</script> +</body> diff --git a/testing/web-platform/tests/fenced-frame/resources/client-hints-iframe-inner.sub.https.html.headers b/testing/web-platform/tests/fenced-frame/resources/client-hints-iframe-inner.sub.https.html.headers new file mode 100644 index 0000000000..f500a60ae8 --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/resources/client-hints-iframe-inner.sub.https.html.headers @@ -0,0 +1,4 @@ +Supports-Loading-Mode: fenced-frame +Accept-CH: sec-ch-viewport-width, sec-ch-ua-reduced +Feature-Policy: ch-viewport-width *, ch-ua-reduced * +Access-Control-Allow-Origin: * diff --git a/testing/web-platform/tests/fenced-frame/resources/client-hints-inner.sub.https.html b/testing/web-platform/tests/fenced-frame/resources/client-hints-inner.sub.https.html new file mode 100644 index 0000000000..0271d0290d --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/resources/client-hints-inner.sub.https.html @@ -0,0 +1,27 @@ +<!DOCTYPE html> +<script src="/resources/testharness.js"></script> +<script src="utils.js"></script> +<title>Client Hints Helper</title> +<body> +<script type="module"> +const [key] = parseKeylist(); +let iframe = document.createElement('iframe'); +let p = new Promise((resolve, reject) => { + window.addEventListener('message', e => { + resolve(e.data); + }); +}); +iframe.src = 'client-hints-iframe-inner.sub.https.html'; +document.body.appendChild(iframe); +const response = await p; +const result = { + 'root-fenced-frame-headers': { + 'sec-ch-viewport-width': '{{header_or_default(sec-ch-viewport-width, )}}', + 'sec-ch-ua-reduced': '{{header_or_default(sec-ch-ua-reduced, )}}', + 'sec-ch-ua-mobile': '{{header_or_default(sec-ch-ua-mobile, )}}', + }, + 'iframe-headers': response.headers, +}; +writeValueToServer(key, JSON.stringify(result)); +</script> +</body> diff --git a/testing/web-platform/tests/fenced-frame/resources/client-hints-inner.sub.https.html.headers b/testing/web-platform/tests/fenced-frame/resources/client-hints-inner.sub.https.html.headers new file mode 100644 index 0000000000..ea4cf59d16 --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/resources/client-hints-inner.sub.https.html.headers @@ -0,0 +1,5 @@ +Supports-Loading-Mode: fenced-frame +Accept-CH: sec-ch-viewport-width, sec-ch-ua-reduced +Feature-Policy: ch-viewport-width *, ch-ua-reduced * +Access-Control-Allow-Origin: * + diff --git a/testing/web-platform/tests/fenced-frame/resources/client-hints-meta-iframe-inner.sub.https.html b/testing/web-platform/tests/fenced-frame/resources/client-hints-meta-iframe-inner.sub.https.html new file mode 100644 index 0000000000..9afb5c6a85 --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/resources/client-hints-meta-iframe-inner.sub.https.html @@ -0,0 +1,15 @@ +<!DOCTYPE html> +<meta http-equiv="Accept-CH" + content="sec-ch-viewport-width, sec-ch-ua-reduced"/> +<meta http-equiv="Feature-Policy" + content="ch-viewport-width *, ch-ua-reduced *"/> +<title>Client Hint Echoing Iframe</title> +<body> +<script> +window.parent.postMessage({'headers': { + 'sec-ch-viewport-width': '{{header_or_default(sec-ch-viewport-width, )}}', + 'sec-ch-ua-reduced': '{{header_or_default(sec-ch-ua-reduced, )}}', + 'sec-ch-ua-mobile': '{{header_or_default(sec-ch-ua-mobile, )}}', +}}, '*'); +</script> +</body> diff --git a/testing/web-platform/tests/fenced-frame/resources/client-hints-meta-iframe-inner.sub.https.html.headers b/testing/web-platform/tests/fenced-frame/resources/client-hints-meta-iframe-inner.sub.https.html.headers new file mode 100644 index 0000000000..b7952e5d05 --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/resources/client-hints-meta-iframe-inner.sub.https.html.headers @@ -0,0 +1,2 @@ + +Supports-Loading-Mode: fenced-frame diff --git a/testing/web-platform/tests/fenced-frame/resources/client-hints-meta-inner.sub.https.html b/testing/web-platform/tests/fenced-frame/resources/client-hints-meta-inner.sub.https.html new file mode 100644 index 0000000000..b84f16ffd0 --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/resources/client-hints-meta-inner.sub.https.html @@ -0,0 +1,31 @@ +<!DOCTYPE html> +<meta http-equiv="Accept-CH" + content="sec-ch-viewport-width, sec-ch-ua-reduced"/> +<meta http-equiv="Feature-Policy" + content="ch-viewport-width *, ch-ua-reduced *"/> +<script src="/resources/testharness.js"></script> +<script src="utils.js"></script> +<title>Client Hints Helper</title> +<body> +<script type="module"> +const [key] = parseKeylist(); +let iframe = document.createElement('iframe'); +let p = new Promise((resolve, reject) => { + window.addEventListener('message', e => { + resolve(e.data); + }); +}); +iframe.src = 'client-hints-meta-iframe-inner.sub.https.html'; +document.body.appendChild(iframe); +const response = await p; +const result = { + 'root-fenced-frame-headers': { + 'sec-ch-viewport-width': '{{header_or_default(sec-ch-viewport-width, )}}', + 'sec-ch-ua-reduced': '{{header_or_default(sec-ch-ua-reduced, )}}', + 'sec-ch-ua-mobile': '{{header_or_default(sec-ch-ua-mobile, )}}', + }, + 'iframe-headers': response.headers, +}; +writeValueToServer(key, JSON.stringify(result)); +</script> +</body> diff --git a/testing/web-platform/tests/fenced-frame/resources/client-hints-meta-inner.sub.https.html.headers b/testing/web-platform/tests/fenced-frame/resources/client-hints-meta-inner.sub.https.html.headers new file mode 100644 index 0000000000..afe7b4f317 --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/resources/client-hints-meta-inner.sub.https.html.headers @@ -0,0 +1,2 @@ +Supports-Loading-Mode: fenced-frame +Access-Control-Allow-Origin: * diff --git a/testing/web-platform/tests/fenced-frame/resources/close.html b/testing/web-platform/tests/fenced-frame/resources/close.html new file mode 100644 index 0000000000..7fd946d6ff --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/resources/close.html @@ -0,0 +1,5 @@ +<!DOCTYPE html> +<title>This window will close when it loads</title> +<script> + window.close(); +</script>
\ No newline at end of file diff --git a/testing/web-platform/tests/fenced-frame/resources/config-cross-origin-apis-inner.https.html b/testing/web-platform/tests/fenced-frame/resources/config-cross-origin-apis-inner.https.html new file mode 100644 index 0000000000..211fe216c7 --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/resources/config-cross-origin-apis-inner.https.html @@ -0,0 +1,37 @@ +<!DOCTYPE html> +<script src="utils.js"></script> +<script src="/common/get-host-info.sub.js"></script> +<title>Fenced frame attribution reporting self navigation test</title> + +<body> +<script> +// This helper function will navigate a fenced frame to a remote origin page. +// It will then check to make sure that window.fence APIs are not allowed after +// the navigation. +const [key] = parseKeylist(); + +if (location.origin == get_host_info().ORIGIN) { + const configs = window.fence.getNestedConfigs(); + const next_url = getRemoteOriginURL(generateURL( + "config-cross-origin-apis-inner.https.html", [key])); + location.href = next_url; +} else { + const event = { + eventType: "reserved.top_navigation_commit", + eventData: "data!", + destination: ["buyer"], + } + + // These should gracefully fail without badmessaging the renderer. + window.fence.setReportEventDataForAutomaticBeacons(event); + window.fence.reportEvent(event); + + const configs = window.fence.getNestedConfigs(); + + // Report how many configs were obtained. Cross-origin pages should not + // obtain any nested configs. + writeValueToServer(key, configs.length); +} + +</script> +</body> diff --git a/testing/web-platform/tests/fenced-frame/resources/config-cross-origin-apis-inner.https.html.headers b/testing/web-platform/tests/fenced-frame/resources/config-cross-origin-apis-inner.https.html.headers new file mode 100644 index 0000000000..1b63235b7c --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/resources/config-cross-origin-apis-inner.https.html.headers @@ -0,0 +1 @@ +Supports-Loading-Mode: fenced-frame diff --git a/testing/web-platform/tests/fenced-frame/resources/config-cross-origin-iframe.https.html b/testing/web-platform/tests/fenced-frame/resources/config-cross-origin-iframe.https.html new file mode 100644 index 0000000000..17c8347720 --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/resources/config-cross-origin-iframe.https.html @@ -0,0 +1,30 @@ +<!DOCTYPE html> +<script src="utils.js"></script> +<script src="/common/get-host-info.sub.js"></script> +<title>Fenced frame attribution reporting self navigation test</title> + +<body> +<script> +// This helper function will navigate a child iframe to a remote origin page. +// It will then check to make sure that window.fence APIs are not allowed after +// the navigation. This code is meant to run in a fenced frame. +const [key] = parseKeylist(); + +const event = { + eventType: "reserved.top_navigation_commit", + eventData: "data!", + destination: ["buyer"], +} + +// These should gracefully fail without badmessaging the renderer. +window.fence.setReportEventDataForAutomaticBeacons(event); +window.fence.reportEvent(event); + +const configs = window.fence.getNestedConfigs(); + +// Report how many configs were obtained. Cross-origin pages should not +// obtain any nested configs. +writeValueToServer(key, configs.length); + +</script> +</body> diff --git a/testing/web-platform/tests/fenced-frame/resources/config-cross-origin-iframe.https.html.headers b/testing/web-platform/tests/fenced-frame/resources/config-cross-origin-iframe.https.html.headers new file mode 100644 index 0000000000..1b63235b7c --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/resources/config-cross-origin-iframe.https.html.headers @@ -0,0 +1 @@ +Supports-Loading-Mode: fenced-frame diff --git a/testing/web-platform/tests/fenced-frame/resources/config-embed-cross-origin-iframe.https.html b/testing/web-platform/tests/fenced-frame/resources/config-embed-cross-origin-iframe.https.html new file mode 100644 index 0000000000..f21afee011 --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/resources/config-embed-cross-origin-iframe.https.html @@ -0,0 +1,18 @@ +<!DOCTYPE html> +<script src="utils.js"></script> +<script src="/common/get-host-info.sub.js"></script> +<title>Fenced frame attribution reporting self navigation test</title> + +<body> +<script> +// This helper function will navigate a child iframe to a remote origin page. +// It will then check to make sure that window.fence APIs are not allowed after +// the navigation. This code is meant to run in a fenced frame. +const [key] = parseKeylist(); + +const configs = window.fence.getNestedConfigs(); +const next_url = getRemoteOriginURL(generateURL( + "config-cross-origin-iframe.https.html", [key])); +attachIFrame(next_url); +</script> +</body> diff --git a/testing/web-platform/tests/fenced-frame/resources/config-embed-cross-origin-iframe.https.html.headers b/testing/web-platform/tests/fenced-frame/resources/config-embed-cross-origin-iframe.https.html.headers new file mode 100644 index 0000000000..1b63235b7c --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/resources/config-embed-cross-origin-iframe.https.html.headers @@ -0,0 +1 @@ +Supports-Loading-Mode: fenced-frame diff --git a/testing/web-platform/tests/fenced-frame/resources/content-index-sw.js b/testing/web-platform/tests/fenced-frame/resources/content-index-sw.js new file mode 100644 index 0000000000..c2759d9630 --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/resources/content-index-sw.js @@ -0,0 +1,28 @@ +self.addEventListener('install', e => e.waitUntil(skipWaiting())); +self.addEventListener('activate', e => e.waitUntil(clients.claim())); + +self.addEventListener('message', async event => { + const method = event.data; + const {index} = self.registration; + const id = 'fenced-frame-id-sw'; + + let promise; + if (method === 'add') { + promise = index.add({ + id, + title: 'same title', + description: 'same description', + url: 'resources/' + }); + } else if (method === 'delete') { + promise = index.delete(id); + } else if (method === 'getAll') { + promise = index.getAll(); + } else { + promise = Promise.resolve(); + } + + const message = await promise.then(() => 'success').catch(e => e.message); + + event.source.postMessage(message); +}); diff --git a/testing/web-platform/tests/fenced-frame/resources/cookie-access.https.html b/testing/web-platform/tests/fenced-frame/resources/cookie-access.https.html new file mode 100644 index 0000000000..34e5681139 --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/resources/cookie-access.https.html @@ -0,0 +1,11 @@ +<!DOCTYPE html> +<script src="utils.js"></script> +<title>Page loaded in a frame in a fenced frame tree</title> +<script> + // This page is loaded either in an iframe or a fenced frame + // nested inside a root fenced frame. + document.cookie = 'G=nested_in_fenced_frame; SameSite=Lax'; + const [cookie_value_key] = parseKeylist() + const cookie_value = document.cookie; + writeValueToServer(cookie_value_key, cookie_value); +</script> diff --git a/testing/web-platform/tests/fenced-frame/resources/cookie-access.https.html.headers b/testing/web-platform/tests/fenced-frame/resources/cookie-access.https.html.headers new file mode 100644 index 0000000000..1b63235b7c --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/resources/cookie-access.https.html.headers @@ -0,0 +1 @@ +Supports-Loading-Mode: fenced-frame diff --git a/testing/web-platform/tests/fenced-frame/resources/create-credential-inner.https.html b/testing/web-platform/tests/fenced-frame/resources/create-credential-inner.https.html new file mode 100644 index 0000000000..5725177f21 --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/resources/create-credential-inner.https.html @@ -0,0 +1,38 @@ +<!DOCTYPE html> +<script src="/resources/testharness.js"></script> +<script src="/webauthn/helpers.js"></script> +<script src="/resources/testdriver.js"></script> +<script src="/resources/testdriver-vendor.js"></script> +<script src="/common/get-host-info.sub.js"></script> +<script src="utils.js"></script> +<title>Fenced frame content to report the result of navigator.credentials.create</title> + +<body> +<script> +function base_path() { + return location.pathname.replace(/\/[^\/]*$/, '/'); +} + +standardSetup(function() { + 'use strict'; + async function init() { + // This file is meant to be navigated to from a <fencedframe> element. It + // reports back to the page hosting the <fencedframe> whether or not + // `navigator.credentials.create` is allowed. + const [key] = parseKeylist(); + + // Report whether or not `credentials.create` is allowed. + createCredential().then( + () => { + writeValueToServer(key, 'createCredential passed'); + }, + () => { + writeValueToServer(key, 'createCredential failed'); + }, + ); + } + + init(); +}); +</script> +</body> diff --git a/testing/web-platform/tests/fenced-frame/resources/create-credential-inner.https.html.headers b/testing/web-platform/tests/fenced-frame/resources/create-credential-inner.https.html.headers new file mode 100644 index 0000000000..6247f6d632 --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/resources/create-credential-inner.https.html.headers @@ -0,0 +1 @@ +Supports-Loading-Mode: fenced-frame
\ No newline at end of file diff --git a/testing/web-platform/tests/fenced-frame/resources/create-popup.html b/testing/web-platform/tests/fenced-frame/resources/create-popup.html new file mode 100644 index 0000000000..a6cd81ec14 --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/resources/create-popup.html @@ -0,0 +1,18 @@ +<!DOCTYPE html> +<script src="utils.js"></script> +<title>Nested frames in a Fenced Frame tree creating popups</title> +<script> + // It is the document that `popup-noopener-inner.html` loads in a nested + // iframe/fenced frame. + // It's expected that the opener/openee references should be null, and + // window.name should be the empty string. + const [popup_noopener_key, popup_openee_key, popup_name_key] = parseKeylist(); + const src_popup = generateURL(`popup-noopener-destination.html`, + [popup_noopener_key, popup_name_key]); + const popup = window.open(src_popup, "foo"); + if (popup) { + writeValueToServer(popup_openee_key, "FAIL"); + } else { + writeValueToServer(popup_openee_key, "PASS"); + } +</script> diff --git a/testing/web-platform/tests/fenced-frame/resources/create-popup.html.headers b/testing/web-platform/tests/fenced-frame/resources/create-popup.html.headers new file mode 100644 index 0000000000..1b63235b7c --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/resources/create-popup.html.headers @@ -0,0 +1 @@ +Supports-Loading-Mode: fenced-frame diff --git a/testing/web-platform/tests/fenced-frame/resources/csp-fenced-frame-src-allowed-inner.html b/testing/web-platform/tests/fenced-frame/resources/csp-fenced-frame-src-allowed-inner.html new file mode 100644 index 0000000000..bdb448c347 --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/resources/csp-fenced-frame-src-allowed-inner.html @@ -0,0 +1,7 @@ +<!DOCTYPE html> +<script src="utils.js"></script> +<title>Page embedded as a fenced frame</title> +<script> + const [key] = parseKeylist(); + writeValueToServer(key, "loaded"); +</script>
\ No newline at end of file diff --git a/testing/web-platform/tests/fenced-frame/resources/csp-fenced-frame-src-allowed-inner.html.headers b/testing/web-platform/tests/fenced-frame/resources/csp-fenced-frame-src-allowed-inner.html.headers new file mode 100644 index 0000000000..6247f6d632 --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/resources/csp-fenced-frame-src-allowed-inner.html.headers @@ -0,0 +1 @@ +Supports-Loading-Mode: fenced-frame
\ No newline at end of file diff --git a/testing/web-platform/tests/fenced-frame/resources/csp-fenced-frame-src-blocked-inner.html b/testing/web-platform/tests/fenced-frame/resources/csp-fenced-frame-src-blocked-inner.html new file mode 100644 index 0000000000..990f5ee469 --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/resources/csp-fenced-frame-src-blocked-inner.html @@ -0,0 +1,10 @@ +<!DOCTYPE html> +<script src="utils.js"></script> +<title>Page embedded as a fenced frame</title> +<script> + // This file is expected to be unreachable from + // `csp-fenced-frame-src-blocked.html` in the parent directory because of CSP + // violation. + const [key] = parseKeylist(); + writeValueToServer(key, "loaded"); +</script>
\ No newline at end of file diff --git a/testing/web-platform/tests/fenced-frame/resources/csp-fenced-frame-src-blocked-inner.html.headers b/testing/web-platform/tests/fenced-frame/resources/csp-fenced-frame-src-blocked-inner.html.headers new file mode 100644 index 0000000000..6247f6d632 --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/resources/csp-fenced-frame-src-blocked-inner.html.headers @@ -0,0 +1 @@ +Supports-Loading-Mode: fenced-frame
\ No newline at end of file diff --git a/testing/web-platform/tests/fenced-frame/resources/csp-frame-src-allowed-inner.html b/testing/web-platform/tests/fenced-frame/resources/csp-frame-src-allowed-inner.html new file mode 100644 index 0000000000..bdb448c347 --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/resources/csp-frame-src-allowed-inner.html @@ -0,0 +1,7 @@ +<!DOCTYPE html> +<script src="utils.js"></script> +<title>Page embedded as a fenced frame</title> +<script> + const [key] = parseKeylist(); + writeValueToServer(key, "loaded"); +</script>
\ No newline at end of file diff --git a/testing/web-platform/tests/fenced-frame/resources/csp-frame-src-allowed-inner.html.headers b/testing/web-platform/tests/fenced-frame/resources/csp-frame-src-allowed-inner.html.headers new file mode 100644 index 0000000000..6247f6d632 --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/resources/csp-frame-src-allowed-inner.html.headers @@ -0,0 +1 @@ +Supports-Loading-Mode: fenced-frame
\ No newline at end of file diff --git a/testing/web-platform/tests/fenced-frame/resources/csp-frame-src-blocked-inner.html b/testing/web-platform/tests/fenced-frame/resources/csp-frame-src-blocked-inner.html new file mode 100644 index 0000000000..eb90bb94e9 --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/resources/csp-frame-src-blocked-inner.html @@ -0,0 +1,9 @@ +<!DOCTYPE html> +<script src="utils.js"></script> +<title>Page embedded as a fenced frame</title> +<script> + // This file is expected to be unreachable from `csp-frame-src-blocked.html` + // in the parent directory because of CSP violation. + const [key] = parseKeylist(); + writeValueToServer(key, "loaded"); +</script>
\ No newline at end of file diff --git a/testing/web-platform/tests/fenced-frame/resources/csp-frame-src-blocked-inner.html.headers b/testing/web-platform/tests/fenced-frame/resources/csp-frame-src-blocked-inner.html.headers new file mode 100644 index 0000000000..6247f6d632 --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/resources/csp-frame-src-blocked-inner.html.headers @@ -0,0 +1 @@ +Supports-Loading-Mode: fenced-frame
\ No newline at end of file diff --git a/testing/web-platform/tests/fenced-frame/resources/csp-inner.html b/testing/web-platform/tests/fenced-frame/resources/csp-inner.html new file mode 100644 index 0000000000..99df39fdc5 --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/resources/csp-inner.html @@ -0,0 +1,37 @@ +<!DOCTYPE html> + <script src="utils.js"></script> + + <style> + body {background-color: red;} + </style> + + <title>Fenced frame content to test Content Security Policies</title> + + <body> + <script> + const [csp_key] = parseKeylist(); + + function fail() { + writeValueToServer(csp_key, + "FAIL: img-src policy was not honored in fenced frame"); + } + + function pass() { + // The parent page is going to attempt to pass a + // style-src: 'none' CSP to the fenced frame. Make sure that + // the header is not honored. + const bgcolor = window.getComputedStyle(document.body, null) + .getPropertyValue('background-color'); + + if (bgcolor != "rgb(255, 0, 0)") { + writeValueToServer(csp_key, + "FAIL: style-src policy was passed to fenced frame"); + return; + } + + writeValueToServer(csp_key, "pass"); + } + </script> + <img src="csp.png" id="my_img" onload="fail();" onerror="pass();"> + </body> +</html>
\ No newline at end of file diff --git a/testing/web-platform/tests/fenced-frame/resources/csp-inner.html.headers b/testing/web-platform/tests/fenced-frame/resources/csp-inner.html.headers new file mode 100644 index 0000000000..e89be70a43 --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/resources/csp-inner.html.headers @@ -0,0 +1,2 @@ +Supports-Loading-Mode: fenced-frame +Content-Security-Policy: img-src 'none'
\ No newline at end of file diff --git a/testing/web-platform/tests/fenced-frame/resources/csp.png b/testing/web-platform/tests/fenced-frame/resources/csp.png Binary files differnew file mode 100644 index 0000000000..53a9748ae0 --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/resources/csp.png diff --git a/testing/web-platform/tests/fenced-frame/resources/dangling-markup-helper.js b/testing/web-platform/tests/fenced-frame/resources/dangling-markup-helper.js new file mode 100644 index 0000000000..9e0fff2301 --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/resources/dangling-markup-helper.js @@ -0,0 +1,15 @@ +// These are used in tests that rely on URLs containing dangling markup. See +// https://github.com/whatwg/fetch/pull/519. +const kDanglingMarkupSubstrings = [ + "blo\nck<ed", + "blo\rck<ed", + "blo\tck<ed", + "blo<ck\ned", + "blo<ck\red", + "blo<ck\ted", +]; + +function getTimeoutPromise(t) { + return new Promise(resolve => + t.step_timeout(() => resolve("NOT LOADED"), 1500)); +}
\ No newline at end of file diff --git a/testing/web-platform/tests/fenced-frame/resources/default-enabled-features-helper.js b/testing/web-platform/tests/fenced-frame/resources/default-enabled-features-helper.js new file mode 100644 index 0000000000..5b4c292622 --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/resources/default-enabled-features-helper.js @@ -0,0 +1,53 @@ +// This is a helper file used for the attribution-reporting-*.https.html tests. +// To use this, make sure you import these scripts: +// <script src="/resources/testharness.js"></script> +// <script src="/resources/testharnessreport.js"></script> +// <script src="/common/utils.js"></script> +// <script src="/common/dispatcher/dispatcher.js"></script> +// <script src="resources/utils.js"></script> +// <script src="/common/get-host-info.sub.js"></script> + +async function runDefaultEnabledFeaturesTest(t, should_load, fenced_origin, + generator_api="fledge", allow="") { + const fencedframe = await attachFencedFrameContext({ + generator_api: generator_api, + attributes: [["allow", allow]], + origin: fenced_origin}); + + if (!should_load) { + const fencedframe_blocked = new Promise(r => t.step_timeout(r, 1000)); + const fencedframe_loaded = fencedframe.execute(() => {}); + assert_equals(await Promise.any([ + fencedframe_blocked.then(() => "blocked"), + fencedframe_loaded.then(() => "loaded"), + ]), "blocked", "The fenced frame should not be loaded."); + return; + } + + await fencedframe.execute((generator_api) => { + assert_true( + document.featurePolicy.allowsFeature('attribution-reporting'), + "Attribution reporting should be allowed if the fenced " + + "frame loaded using FLEDGE or shared storage."); + + if (generator_api == "fledge") { + assert_true( + document.featurePolicy.allowsFeature('shared-storage'), + "Shared Storage should be allowed if the fenced " + + "frame loaded using FLEDGE."); + assert_true( + document.featurePolicy.allowsFeature('private-aggregation'), + "Private Aggregation should be allowed if the fenced " + + "frame loaded using FLEDGE."); + } else { + assert_true( + document.featurePolicy.allowsFeature('shared-storage'), + "Shared Storage should be allowed if the fenced " + + "frame loaded using Shared Storage."); + assert_false( + document.featurePolicy.allowsFeature('private-aggregation'), + "Private Aggregation should be disabled if the fenced " + + "frame loaded using Shared Storage."); + } + }, [generator_api]); +} diff --git a/testing/web-platform/tests/fenced-frame/resources/default-enabled-features-navigate.https.html b/testing/web-platform/tests/fenced-frame/resources/default-enabled-features-navigate.https.html new file mode 100644 index 0000000000..6bfb033400 --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/resources/default-enabled-features-navigate.https.html @@ -0,0 +1,25 @@ +<!DOCTYPE html> +<script src="utils.js"></script> +<script src="/common/get-host-info.sub.js"></script> +<title>Fenced frame attribution reporting self navigation test</title> + +<body> +<script> +// This helper function will navigate a fenced frame to a remote origin page. +// That redirect should succeed to load and the permissions from the previous page should be in +// place. +const [key1, key2] = parseKeylist(); + +const result_val = document.featurePolicy.allowsFeature('attribution-reporting'); +if (location.origin == get_host_info().ORIGIN) { + writeValueToServer(key1, result_val); + + const next_url = getRemoteOriginURL(generateURL( + "default-enabled-features-navigate.https.html", [key1, key2])); + location.href = next_url; +} else { + writeValueToServer(key2, result_val); +} + +</script> +</body> diff --git a/testing/web-platform/tests/fenced-frame/resources/default-enabled-features-navigate.https.html.headers b/testing/web-platform/tests/fenced-frame/resources/default-enabled-features-navigate.https.html.headers new file mode 100644 index 0000000000..1b63235b7c --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/resources/default-enabled-features-navigate.https.html.headers @@ -0,0 +1 @@ +Supports-Loading-Mode: fenced-frame diff --git a/testing/web-platform/tests/fenced-frame/resources/default-enabled-features-subframe-fencedframe.https.html b/testing/web-platform/tests/fenced-frame/resources/default-enabled-features-subframe-fencedframe.https.html new file mode 100644 index 0000000000..e098736528 --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/resources/default-enabled-features-subframe-fencedframe.https.html @@ -0,0 +1,21 @@ +<!DOCTYPE html> +<script src="utils.js"></script> +<script src="/common/get-host-info.sub.js"></script> + +<body> +<script> +// This page is loaded into a fenced frame. The document policies for this page +// disable shared storage. This then creates a child iframe to determine if +// document deliviered policies are reflected in the child frame. +const [key, should_restrict_select_url] = parseKeylist(); + +const iframe_url = generateURL( + 'default-enabled-features-subframe-iframe.https.html', [key]); +const iframe = document.createElement("iframe"); +iframe.src = iframe_url; +if (should_restrict_select_url == "true") { + iframe.allow = "shared-storage-select-url 'none';" +} +document.body.appendChild(iframe); +</script> +</body> diff --git a/testing/web-platform/tests/fenced-frame/resources/default-enabled-features-subframe-fencedframe.https.html.headers b/testing/web-platform/tests/fenced-frame/resources/default-enabled-features-subframe-fencedframe.https.html.headers new file mode 100644 index 0000000000..e52511f18a --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/resources/default-enabled-features-subframe-fencedframe.https.html.headers @@ -0,0 +1,2 @@ +Supports-Loading-Mode: fenced-frame +Permissions-Policy: shared-storage=() diff --git a/testing/web-platform/tests/fenced-frame/resources/default-enabled-features-subframe-iframe.https.html b/testing/web-platform/tests/fenced-frame/resources/default-enabled-features-subframe-iframe.https.html new file mode 100644 index 0000000000..a3ab056944 --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/resources/default-enabled-features-subframe-iframe.https.html @@ -0,0 +1,19 @@ +<!DOCTYPE html> +<script src="utils.js"></script> +<script src="/common/get-host-info.sub.js"></script> + +<body> +<script> +// This page is loaded into an iframe that is nested within a fenced frame tree. +// This is used to tell the test whether policies that are restricted by a +// fenced frame's document policies also are restricted in subframes. +const [key] = parseKeylist(); + +const allows_shared_storage = + document.featurePolicy.allowsFeature('shared-storage'); +const allows_select_url = + document.featurePolicy.allowsFeature('shared-storage-select-url'); + +writeValueToServer(key, allows_shared_storage + "," + allows_select_url); +</script> +</body> diff --git a/testing/web-platform/tests/fenced-frame/resources/default-enabled-features-subframe-iframe.https.html.headers b/testing/web-platform/tests/fenced-frame/resources/default-enabled-features-subframe-iframe.https.html.headers new file mode 100644 index 0000000000..6247f6d632 --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/resources/default-enabled-features-subframe-iframe.https.html.headers @@ -0,0 +1 @@ +Supports-Loading-Mode: fenced-frame
\ No newline at end of file diff --git a/testing/web-platform/tests/fenced-frame/resources/download-helper.js b/testing/web-platform/tests/fenced-frame/resources/download-helper.js new file mode 100644 index 0000000000..011d5c867f --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/resources/download-helper.js @@ -0,0 +1,29 @@ +function StreamDownloadFinishDelay() { + return 1000; +} + +function DownloadVerifyDelay() { + return 1000; +} + +async function VerifyDownload(test_obj, token) { + const verifyToken = async (token) => { + const url = `resources/download-stash.py?verify-token&token=${token}`; + const response = await fetch(url); + if (!response.ok) { + throw new Error('An error happened in the server'); + } + const message = await response.text(); + return message === 'TOKEN_SET'; + }; + + return new Promise((resolve) => { + test_obj.step_wait( + async () => { + const result = await verifyToken(token); + resolve(result); + }, + 'Check if the download has finished or not', + StreamDownloadFinishDelay() + DownloadVerifyDelay()); + }); +} diff --git a/testing/web-platform/tests/fenced-frame/resources/download-inner.html b/testing/web-platform/tests/fenced-frame/resources/download-inner.html new file mode 100644 index 0000000000..9bc816cbf3 --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/resources/download-inner.html @@ -0,0 +1,34 @@ +<!DOCTYPE html> +<html> + +<head> + <title>The page triggering download embedded as a Fenced Frame</title> + <script src="/resources/testdriver.js"></script> + <script src="/resources/testdriver-vendor.js"></script> + <script src="download-helper.js"></script> + <script src="utils.js"></script> + <script> + window.addEventListener('DOMContentLoaded', async () => { + const [download_key, download_ack_key] = parseKeylist(); + const type = new URL(location).searchParams.get('type'); + const href = `download-stash.py?token=${download_key}`; + + if (type == 'anchor') { + const anchor = document.querySelector('#download'); + anchor.href = href; + test_driver.click(anchor); + } else { + const delay = StreamDownloadFinishDelay(); + location.href = `${href}&finish-delay=${delay}` + } + + await writeValueToServer(download_ack_key, 'Triggered the action for download'); + }); + </script> +</head> + +<body> + <a id="download" download>Download</a> +</body> + +</html> diff --git a/testing/web-platform/tests/fenced-frame/resources/download-inner.html.headers b/testing/web-platform/tests/fenced-frame/resources/download-inner.html.headers new file mode 100644 index 0000000000..6247f6d632 --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/resources/download-inner.html.headers @@ -0,0 +1 @@ +Supports-Loading-Mode: fenced-frame
\ No newline at end of file diff --git a/testing/web-platform/tests/fenced-frame/resources/download-stash.py b/testing/web-platform/tests/fenced-frame/resources/download-stash.py new file mode 100644 index 0000000000..497f7cb018 --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/resources/download-stash.py @@ -0,0 +1,28 @@ +import time + + +def main(request, response): + token = request.GET[b"token"] + response.status = 200 + response.headers.append(b"Content-Type", b"text/html") + if b"verify-token" in request.GET: + if request.server.stash.take(token): + return u'TOKEN_SET' + return u'TOKEN_NOT_SET' + + if b"finish-delay" not in request.GET: + # <a download> + request.server.stash.put(token, True) + return + + # navigation to download + response.headers.append(b"Content-Disposition", b"attachment") + response.write_status_headers() + finish_delay = float(request.GET[b"finish-delay"]) / 1E3 + count = 10 + single_delay = finish_delay / count + for i in range(count): # pylint: disable=unused-variable + time.sleep(single_delay) + if not response.writer.write_content(b"\n"): + return + request.server.stash.put(token, True) diff --git a/testing/web-platform/tests/fenced-frame/resources/dummy.html b/testing/web-platform/tests/fenced-frame/resources/dummy.html new file mode 100644 index 0000000000..a0cf50713e --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/resources/dummy.html @@ -0,0 +1,2 @@ +<!DOCTYPE html> +<title>Dummy page</title> diff --git a/testing/web-platform/tests/fenced-frame/resources/embeddee.html b/testing/web-platform/tests/fenced-frame/resources/embeddee.html new file mode 100644 index 0000000000..3423be9aa4 --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/resources/embeddee.html @@ -0,0 +1,7 @@ +<!DOCTYPE html> +<script src="utils.js"></script> +<title>A page embedded as a Fenced Frame for COEP tests</title> +<script> +const [uuid] = parseKeylist(); +writeValueToServer(uuid, "PASS"); +</script> diff --git a/testing/web-platform/tests/fenced-frame/resources/embeddee.html.headers b/testing/web-platform/tests/fenced-frame/resources/embeddee.html.headers new file mode 100644 index 0000000000..6247f6d632 --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/resources/embeddee.html.headers @@ -0,0 +1 @@ +Supports-Loading-Mode: fenced-frame
\ No newline at end of file diff --git a/testing/web-platform/tests/fenced-frame/resources/embedder-policy.js b/testing/web-platform/tests/fenced-frame/resources/embedder-policy.js new file mode 100644 index 0000000000..8c96afafce --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/resources/embedder-policy.js @@ -0,0 +1,39 @@ +// This file should be loaded alongside with utils.js. +// +// This file is loaded by: +// - embedder-no-coep.https.html +// - embedder-require-corp.https.html + +// Make input list to be used as a wptserve pipe +// (https://web-platform-tests.org/writing-tests/server-pipes.html). +// e.g. +// args: ['content-type,text/plain','Age,0'] +// return: 'header(content-type,text/plain)|header(Age,0)' +function generateHeader(headers) { + return headers.map((h) => { + return 'header(' + h + ')'; + }).join('|'); +} + +// Setup a fenced frame for embedder-* WPTs. +async function setupTest(test_type, uuid, hostname='') { + let headers = ["Supports-Loading-Mode,fenced-frame"]; + switch (test_type) { + case "coep:require-corp": + headers.push("cross-origin-embedder-policy,require-corp"); + headers.push("cross-origin-resource-policy,same-origin"); + break; + case "no coep": + break; + default: + assert_unreachable("unknown test_type:" + test_type); + break; + } + const tmp_url = new URL('resources/embeddee.html', location.href); + if (hostname) { + tmp_url.hostname = hostname; + } + tmp_url.searchParams.append("pipe", generateHeader(headers)); + const url = generateURL(tmp_url.toString(), [uuid]); + return attachFencedFrame(url); +} diff --git a/testing/web-platform/tests/fenced-frame/resources/empty-worker.js b/testing/web-platform/tests/fenced-frame/resources/empty-worker.js new file mode 100644 index 0000000000..49ceb2648a --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/resources/empty-worker.js @@ -0,0 +1 @@ +// Do nothing. diff --git a/testing/web-platform/tests/fenced-frame/resources/fedcm-get-credential-inner.https.html b/testing/web-platform/tests/fenced-frame/resources/fedcm-get-credential-inner.https.html new file mode 100644 index 0000000000..f30cd77838 --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/resources/fedcm-get-credential-inner.https.html @@ -0,0 +1,45 @@ +<!DOCTYPE html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testdriver.js"></script> +<script src="/resources/testdriver-vendor.js"></script> +<script src="utils.js"></script> +<title>Fenced frame content to report the result of navigator.credentials.get</title> + +<body> +<script> +function isExpectedErrorMessage(e) { + return e.name === 'NotAllowedError' && + e.message === + 'The credential operation is not allowed in a fenced frame tree.'; +} + +// This file is meant to be navigated to from a <fencedframe> element. It +// reports back to the page hosting the <fencedframe> whether or not +// `navigator.credentials.get` is allowed. +const [key] = parseKeylist(); + +const test_options = { + federated: { + providers: [{ + configURL: 'https://idp.test/fedcm.json', + clientId: '1', + nonce: '2', + }] + } +}; +navigator.credentials.get(test_options) + .then( + () => { + writeValueToServer(key, 'unexpected passed'); + }, + (e) => { + if (isExpectedErrorMessage(e)) { + writeValueToServer(key, 'navigator.credentials.get failed'); + } else { + writeValueToServer( + key, 'navigator.credentials.get failed by unexpected reason'); + } + }, + ); +</script> +</body> diff --git a/testing/web-platform/tests/fenced-frame/resources/fedcm-get-credential-inner.https.html.headers b/testing/web-platform/tests/fenced-frame/resources/fedcm-get-credential-inner.https.html.headers new file mode 100644 index 0000000000..6247f6d632 --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/resources/fedcm-get-credential-inner.https.html.headers @@ -0,0 +1 @@ +Supports-Loading-Mode: fenced-frame
\ No newline at end of file diff --git a/testing/web-platform/tests/fenced-frame/resources/fence-api-inner.https.html b/testing/web-platform/tests/fenced-frame/resources/fence-api-inner.https.html new file mode 100644 index 0000000000..814ea78559 --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/resources/fence-api-inner.https.html @@ -0,0 +1,22 @@ +<!DOCTYPE html> +<script src="/resources/testharness.js"></script> +<script src="utils.js"></script> +<title>Fenced frame content to test window.fence object</title> + +<body> +<script> + + // Get the token for communication with the parent. + const [fence_api_token] = parseKeylist(); + + // Check that window.fence is visible inside fenced frames. + assert_true(window.fence != null, + "window.fence should be visible inside fenced frames"); + assert_true(fence != null, + "fence should be visible inside fenced frames"); + + // Tell the parent that the test succeeded. + writeValueToServer(fence_api_token, ""); + +</script> +</body> diff --git a/testing/web-platform/tests/fenced-frame/resources/fence-api-inner.https.html.headers b/testing/web-platform/tests/fenced-frame/resources/fence-api-inner.https.html.headers new file mode 100644 index 0000000000..6247f6d632 --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/resources/fence-api-inner.https.html.headers @@ -0,0 +1 @@ +Supports-Loading-Mode: fenced-frame
\ No newline at end of file diff --git a/testing/web-platform/tests/fenced-frame/resources/fenced-frame-loaded.html b/testing/web-platform/tests/fenced-frame/resources/fenced-frame-loaded.html new file mode 100644 index 0000000000..0054762783 --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/resources/fenced-frame-loaded.html @@ -0,0 +1,13 @@ +<!DOCTYPE html> +<html> +<script src="utils.js"></script> +<title>Fenced frame loaded</title> +<body> +<script> +(async function() { + const [parent_key] = parseKeylist(); + writeValueToServer(parent_key, "fenced frame loaded"); +})(); +</script> +</body> +</html> diff --git a/testing/web-platform/tests/fenced-frame/resources/fenced-frame-loaded.html.headers b/testing/web-platform/tests/fenced-frame/resources/fenced-frame-loaded.html.headers new file mode 100644 index 0000000000..1b63235b7c --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/resources/fenced-frame-loaded.html.headers @@ -0,0 +1 @@ +Supports-Loading-Mode: fenced-frame diff --git a/testing/web-platform/tests/fenced-frame/resources/fenced-frame-set-name-and-report-ready-for-outermost-document-to-navigate.html b/testing/web-platform/tests/fenced-frame/resources/fenced-frame-set-name-and-report-ready-for-outermost-document-to-navigate.html new file mode 100644 index 0000000000..9b67be775e --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/resources/fenced-frame-set-name-and-report-ready-for-outermost-document-to-navigate.html @@ -0,0 +1,16 @@ +<!DOCTYPE html> +<script src="utils.js"></script> +<title>Nested fenced frame named navigation helper</title> +<!-- This is a helper file. It is meant to be the document loaded inside a + nested fenced frame by `navigate-by-name-inner.html`. Once this document is + loaded and changes its `window.name` to `target_frame`, it reports to the + server so that the outermost document can attempt to navigate it by name. + (The navigation should not succeed - see the test expectations). +--> +<body> +<script> + const [ready_for_navigation_key] = parseKeylist(); + window.name = "target_frame"; + writeValueToServer(ready_for_navigation_key, "READY"); +</script> +</body> diff --git a/testing/web-platform/tests/fenced-frame/resources/fenced-frame-set-name-and-report-ready-for-outermost-document-to-navigate.html.headers b/testing/web-platform/tests/fenced-frame/resources/fenced-frame-set-name-and-report-ready-for-outermost-document-to-navigate.html.headers new file mode 100644 index 0000000000..6247f6d632 --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/resources/fenced-frame-set-name-and-report-ready-for-outermost-document-to-navigate.html.headers @@ -0,0 +1 @@ +Supports-Loading-Mode: fenced-frame
\ No newline at end of file diff --git a/testing/web-platform/tests/fenced-frame/resources/fledge-bidding-logic.py b/testing/web-platform/tests/fenced-frame/resources/fledge-bidding-logic.py new file mode 100644 index 0000000000..c91b31fd02 --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/resources/fledge-bidding-logic.py @@ -0,0 +1,116 @@ +# These functions are used by FLEDGE to determine the logic for the ad buyer. +# For our testing purposes, we only need the minimal amount of boilerplate +# code in place to allow them to be invoked properly and move the FLEDGE +# process along. The tests generally do usually not deal with reporting results, +# so we leave `reportWin` empty unless we need to call registerAdBeacon(). See +# `generateURNFromFledge` in "utils.js" to see how this file is used. + +from wptserve.utils import isomorphic_decode + +def main(request, response): + # Set up response headers. + headers = [ + ('Content-Type', 'Application/Javascript'), + ('Ad-Auction-Allowed', 'true') + ] + + # Parse URL params. + requested_size = request.GET.first(b"requested-size", None) + ad_with_size = request.GET.first(b"ad-with-size", None) + automatic_beacon = request.GET.first(b"automatic-beacon", None) + + # Use URL params to modify Javascript. + requested_size_check = '' + if requested_size is not None: + # request.GET stores URL keys and values in iso-8859-1 binary encoding. We + # have to decode the values back to a string to parse width/height. Don't + # bother sanitizing the size, because it is sanitized before auction logic + # runs already. + width, height = isomorphic_decode(requested_size).split('-') + + requested_size_check = ( + f''' + if (!(browserSignals.requestedSize.width === '{width}') && + (browserSignals.requestedSize.height === '{height}')) {{ + throw new Error('requestedSize missing/incorrect in browserSignals'); + }} + ''' + ) + + render_obj = 'ad.renderURL' + if ad_with_size is not None: + render_obj = '{ url: ad.renderURL, width: "100px", height: "50px" }' + + component_render_obj = 'component.renderURL' + if ad_with_size is not None: + component_render_obj = ( + '''{ + url: component.renderURL, + width: "100px", + height: "50px" + } + ''' + ) + + register_ad_beacon = '' + if automatic_beacon is not None: + register_ad_beacon = ( + '''registerAdBeacon({ + 'reserved.top_navigation_start': + browserSignals.interestGroupOwner + + '/fenced-frame/resources/automatic-beacon-store.py?type=reserved.top_navigation_start', + 'reserved.top_navigation_commit': + browserSignals.interestGroupOwner + + '/fenced-frame/resources/automatic-beacon-store.py?type=reserved.top_navigation_commit', + }); + ''' + ) + + # Generate Javascript. + # Note: Python fstrings use double-brackets ( {{, }} ) to insert bracket + # literals instead of substitution sequences. + generate_bid = ( + f'''function generateBid( + interestGroup, + auctionSignals, + perBuyerSignals, + trustedBiddingSignals, + browserSignals) {{ + {requested_size_check} + const ad = interestGroup.ads[0]; + + // `auctionSignals` controls whether or not component auctions are + // allowed. + let allowComponentAuction = (typeof auctionSignals === 'string' && + auctionSignals.includes('bidderAllowsComponentAuction')); + + let result = {{ + 'ad': ad, + 'bid': 1, + 'render': {render_obj}, + 'allowComponentAuction': allowComponentAuction + }}; + if (interestGroup.adComponents && interestGroup.adComponents.length > 0) + result.adComponents = interestGroup.adComponents.map((component) => {{ + return {component_render_obj}; + }}); + return result; + }} + ''' + ) + + report_win = ( + f'''function reportWin( + auctionSignals, + perBuyerSignals, + sellerSignals, + browserSignals) {{ + {register_ad_beacon} + return; + }} + ''' + ) + + content = f'{generate_bid}\n{report_win}' + + return (headers, content) diff --git a/testing/web-platform/tests/fenced-frame/resources/fledge-decision-logic.py b/testing/web-platform/tests/fenced-frame/resources/fledge-decision-logic.py new file mode 100644 index 0000000000..63b544552c --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/resources/fledge-decision-logic.py @@ -0,0 +1,66 @@ +# These functions are used by FLEDGE to determine the logic for the ad seller. +# For our testing purposes, we only need the minimal amount of boilerplate +# code in place to allow them to be invoked properly and move the FLEDGE +# process along. The tests do not deal with reporting results, so we leave +# `reportResult` empty. See `generateURNFromFledge` in "utils.js" to see how +# this file is used. + +from wptserve.utils import isomorphic_decode + +def main(request, response): + # Set up response headers. + headers = [ + ('Content-Type', 'Application/Javascript'), + ('Ad-Auction-Allowed', 'true') + ] + + # Parse URL params. + requested_size = request.GET.first(b"requested-size", None) + + # Use URL params to modify Javascript. + requested_size_check = '' + if requested_size is not None: + # request.GET stores URL keys and values in iso-8859-1 binary encoding. We + # have to decode the values back to a string to parse width/height. Don't + # bother sanitizing the size, because it is sanitized before auction logic + # runs already. + width, height = isomorphic_decode(requested_size).split('-') + + requested_size_check = ( + f''' + if (!(auctionConfig.requestedSize.width === '{width}') && + (auctionConfig.requestedSize.height === '{height}')) {{ + throw new Error('requestedSize missing/incorrect in auctionConfig'); + }} + ''' + ) + + # Generate Javascript. + # Note: Python fstrings use double-brackets ( {{, }} ) to insert bracket + # literals instead of substitution sequences. + score_ad = ( + f'''function scoreAd( + adMetadata, + bid, + auctionConfig, + trustedScoringSignals, + browserSignals) {{ + {requested_size_check} + return 2*bid; + }} + ''' + ) + + report_result = ( + f'''function reportResult( + auctionConfig, + browserSignals) {{ + {requested_size_check} + return; + }} + ''' + ) + + content = f'{score_ad}\n{report_result}' + + return (headers, content) diff --git a/testing/web-platform/tests/fenced-frame/resources/frame-navigation-inner-create-nested.https.html b/testing/web-platform/tests/fenced-frame/resources/frame-navigation-inner-create-nested.https.html new file mode 100644 index 0000000000..9a56a3d9fb --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/resources/frame-navigation-inner-create-nested.https.html @@ -0,0 +1,48 @@ +<!DOCTYPE html> +<script src="/resources/testharness.js"></script> +<script src="/common/get-host-info.sub.js"></script> +<script src="utils.js"></script> +<title>Test nested fenced frame navigation (by a parent frame setting its src).</title> + +<body> + <script> + async function init() { // Needed in order to use top-level await. + // This file is meant to run in a <fencedframe>. It communicates with + // the embedder to confirm that nested fenced frames can be navigated. + + const [navigation_key, navigation_ack_key] = parseKeylist(); + + // Create URL prefixes to simulate different origins. + // (www1 and www2 are different origins) + const simple_url = generateURL("frame-navigation-inner-simple.https.html", + [navigation_key, navigation_ack_key]); + + const origin1_simple_url = getRemoteOriginURL(simple_url); + const origin2_simple_url = getRemoteOriginURL(simple_url) + .toString().replace("www1", "www2"); + + const url_prefix = location.href + "/../"; + + // Tell the embedder that this frame has loaded. + writeValueToServer(navigation_key, "create-nested"); + await nextValueFromServer(navigation_ack_key); + + // Create an inner frame. + inner_frame = attachFencedFrame(origin1_simple_url); + // Wait for our parent to tell us they're done communicating. + await nextValueFromServer(navigation_ack_key); + + // Navigate (cross-origin) and wait. + inner_frame.config = new FencedFrameConfig( + generateURL(origin2_simple_url, [])); + await nextValueFromServer(navigation_ack_key); + + // Navigate (same-origin) and wait. + inner_frame.config = new FencedFrameConfig( + generateURL(origin2_simple_url, [])); + await nextValueFromServer(navigation_ack_key); + } + + init(); + </script> +</body> diff --git a/testing/web-platform/tests/fenced-frame/resources/frame-navigation-inner-create-nested.https.html.headers b/testing/web-platform/tests/fenced-frame/resources/frame-navigation-inner-create-nested.https.html.headers new file mode 100644 index 0000000000..1b63235b7c --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/resources/frame-navigation-inner-create-nested.https.html.headers @@ -0,0 +1 @@ +Supports-Loading-Mode: fenced-frame diff --git a/testing/web-platform/tests/fenced-frame/resources/frame-navigation-inner-simple.https.html b/testing/web-platform/tests/fenced-frame/resources/frame-navigation-inner-simple.https.html new file mode 100644 index 0000000000..643ea48a76 --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/resources/frame-navigation-inner-simple.https.html @@ -0,0 +1,13 @@ +<!DOCTYPE html> +<script src="/resources/testharness.js"></script> +<script src="utils.js"></script> +<title>Test that a fenced frame successfully loaded.</title> + +<body> + <script> + // This file is meant to run in a <fencedframe>. It reports back to the + // outermost page to confirm that loading succeeded. + const [navigation_key, navigation_ack_key] = parseKeylist(); + writeValueToServer(navigation_key, "pass"); + </script> +</body> diff --git a/testing/web-platform/tests/fenced-frame/resources/frame-navigation-inner-simple.https.html.headers b/testing/web-platform/tests/fenced-frame/resources/frame-navigation-inner-simple.https.html.headers new file mode 100644 index 0000000000..1b63235b7c --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/resources/frame-navigation-inner-simple.https.html.headers @@ -0,0 +1 @@ +Supports-Loading-Mode: fenced-frame diff --git a/testing/web-platform/tests/fenced-frame/resources/frame-with-intersection-observer.html b/testing/web-platform/tests/fenced-frame/resources/frame-with-intersection-observer.html new file mode 100644 index 0000000000..dd36b20399 --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/resources/frame-with-intersection-observer.html @@ -0,0 +1,29 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<script src="/resources/testharness.js"></script> +<script src="utils.js"></script> +<div id="target" style="width: 100px; height: 100px; position: fixed; top: 0px; left: 0px"></div> +<script> +let next_token = 0; +function init() { + const tokens = parseKeylist(); + let observer = new IntersectionObserver((entries) => { + assert_equals(entries.length, 1); + let rect = entries[0].intersectionRect.x + "," + + entries[0].intersectionRect.y + "," + + entries[0].intersectionRect.width + "," + + entries[0].intersectionRect.height + "," + + entries[0].isVisible; + writeValueToServer(tokens[next_token], rect); + next_token = next_token + 1; + + if (next_token == tokens.length) { + observer.disconnect(); + } + }, {trackVisibility: true, delay: 100, threshold: [0.6, 0.75]}); + observer.observe(document.getElementById("target")); +} + +init(); +</script> +</html> diff --git a/testing/web-platform/tests/fenced-frame/resources/frame-with-intersection-observer.html.headers b/testing/web-platform/tests/fenced-frame/resources/frame-with-intersection-observer.html.headers new file mode 100644 index 0000000000..1b63235b7c --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/resources/frame-with-intersection-observer.html.headers @@ -0,0 +1 @@ +Supports-Loading-Mode: fenced-frame diff --git a/testing/web-platform/tests/fenced-frame/resources/gamepad-inner.html b/testing/web-platform/tests/fenced-frame/resources/gamepad-inner.html new file mode 100644 index 0000000000..3e253e4915 --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/resources/gamepad-inner.html @@ -0,0 +1,13 @@ +<!DOCTYPE html> +<script src="utils.js"></script> +<title>getGamepads should throw an error in a fenced frame</title> +<script> +const [key] = parseKeylist(); +try { + navigator.getGamepads(); + writeValueToServer(key, 'Expected exception but successed'); +} catch (e) { + writeValueToServer(key, e.name); +} +</script> +</html> diff --git a/testing/web-platform/tests/fenced-frame/resources/gamepad-inner.html.headers b/testing/web-platform/tests/fenced-frame/resources/gamepad-inner.html.headers new file mode 100644 index 0000000000..6247f6d632 --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/resources/gamepad-inner.html.headers @@ -0,0 +1 @@ +Supports-Loading-Mode: fenced-frame
\ No newline at end of file diff --git a/testing/web-platform/tests/fenced-frame/resources/get-nested-configs-inner.html b/testing/web-platform/tests/fenced-frame/resources/get-nested-configs-inner.html new file mode 100644 index 0000000000..122debfe27 --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/resources/get-nested-configs-inner.html @@ -0,0 +1,10 @@ +<!DOCTYPE html> +<script src="utils.js"></script> +<title>Send the results of getNestedConfigs() to the embedder</title> +<script> +const [key] = parseKeylist(); +const configs = window.fence.getNestedConfigs(); +const data_to_send = [configs.length]; +writeValueToServer(key, data_to_send.join(",")); +</script> +</html> diff --git a/testing/web-platform/tests/fenced-frame/resources/get-nested-configs-inner.html.headers b/testing/web-platform/tests/fenced-frame/resources/get-nested-configs-inner.html.headers new file mode 100644 index 0000000000..6247f6d632 --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/resources/get-nested-configs-inner.html.headers @@ -0,0 +1 @@ +Supports-Loading-Mode: fenced-frame
\ No newline at end of file diff --git a/testing/web-platform/tests/fenced-frame/resources/get-nested-configs-nested-iframe.html b/testing/web-platform/tests/fenced-frame/resources/get-nested-configs-nested-iframe.html new file mode 100644 index 0000000000..9bd5d9f492 --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/resources/get-nested-configs-nested-iframe.html @@ -0,0 +1,10 @@ +<!DOCTYPE html> +<script src="utils.js"></script> +<title>getGamepads should throw an error in a fenced frame</title> +<body> + <script> + const [key] = parseKeylist(); + attachIFrame(generateURL("get-nested-configs-inner.html", [key])); + </script> +</body> +</html> diff --git a/testing/web-platform/tests/fenced-frame/resources/get-nested-configs-nested-iframe.html.headers b/testing/web-platform/tests/fenced-frame/resources/get-nested-configs-nested-iframe.html.headers new file mode 100644 index 0000000000..6247f6d632 --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/resources/get-nested-configs-nested-iframe.html.headers @@ -0,0 +1 @@ +Supports-Loading-Mode: fenced-frame
\ No newline at end of file diff --git a/testing/web-platform/tests/fenced-frame/resources/get_battery.html b/testing/web-platform/tests/fenced-frame/resources/get_battery.html new file mode 100644 index 0000000000..0532deca4b --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/resources/get_battery.html @@ -0,0 +1,17 @@ +<!DOCTYPE html> +<script src="utils.js"></script> +<title>getBattery should fail in a fenced frame</title> +<script> +async function init() { // Needed in order to use top-level await. + const [uuid] = parseKeylist(); + try { + await navigator.getBattery(); + writeValueToServer(uuid, 'Expected an exception but the call succeeded'); + } catch (err) { + writeValueToServer(uuid, err.name); + } +} + +init(); +</script> +</html> diff --git a/testing/web-platform/tests/fenced-frame/resources/get_battery.html.headers b/testing/web-platform/tests/fenced-frame/resources/get_battery.html.headers new file mode 100644 index 0000000000..1b63235b7c --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/resources/get_battery.html.headers @@ -0,0 +1 @@ +Supports-Loading-Mode: fenced-frame diff --git a/testing/web-platform/tests/fenced-frame/resources/header-referrer-inner.html b/testing/web-platform/tests/fenced-frame/resources/header-referrer-inner.html new file mode 100644 index 0000000000..2940dbac8e --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/resources/header-referrer-inner.html @@ -0,0 +1,40 @@ +<!DOCTYPE html> +<script src="/resources/testharness.js"></script> +<script src="utils.js"></script> +<title>Fenced frame content to report the value of header.referrer</title> + +<body> +<script> +async function init() { // Needed in order to use top-level await. + // This file is meant to run in a <fencedframe>. It reports back to the + // outermost page the value of `referer` in the request header: + // 1.) Nested iframes inside a fenced frame + // 2.) Nested fenced frames + // 3.) Top-level fenced frames (aka this frame) after initial navigation + const [referrer_key, referrer_ack_key] = parseKeylist(); + + const referrer_url = generateURL("check-header-referrer.py", + [referrer_key, referrer_ack_key]); + + const iframe = document.createElement('iframe'); + iframe.src = referrer_url; + document.body.append(iframe); + + // Wait for ACK, so we know that the outer page has read the last value from + // the `referrer_key` stash that the iframe above wrote to, and we can write + // to it again. + await nextValueFromServer(referrer_ack_key); + + attachFencedFrame(referrer_url); + + // Wait for ACK, so we know that the outer page has read the last value from + // the `referrer_key` stash that the nested fenced frame wrote to, and we can + // can write to it again. + await nextValueFromServer(referrer_ack_key); + + location.href = referrer_url; +} + +init(); +</script> +</body> diff --git a/testing/web-platform/tests/fenced-frame/resources/header-referrer-inner.html.headers b/testing/web-platform/tests/fenced-frame/resources/header-referrer-inner.html.headers new file mode 100644 index 0000000000..6247f6d632 --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/resources/header-referrer-inner.html.headers @@ -0,0 +1 @@ +Supports-Loading-Mode: fenced-frame
\ No newline at end of file diff --git a/testing/web-platform/tests/fenced-frame/resources/header-secFetchDest-inner.html b/testing/web-platform/tests/fenced-frame/resources/header-secFetchDest-inner.html new file mode 100644 index 0000000000..aa3fe9e34c --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/resources/header-secFetchDest-inner.html @@ -0,0 +1,26 @@ +<!DOCTYPE html> +<script src="/resources/testharness.js"></script> +<script src="/common/get-host-info.sub.js"></script> +<script src="utils.js"></script> +<title>Fenced frame content to report the value of `Sec-Fetch-Dest` header</title> + +<body> +<script> +(() => { + // This file is meant to run in a <fencedframe>. It reports back to the + // outermost page the value of `Sec-Fetch-Dest` in the request header for + // nested iframes inside a fenced frame. + const [sec_fetch_dest_value_key] = parseKeylist(); + const https_origin = get_host_info().HTTPS_REMOTE_ORIGIN; + const https_origin_url = + getRemoteOriginURL( + generateURL( + 'check-header-sec-fetch-dest.py', + [sec_fetch_dest_value_key])); + + const iframe = document.createElement('iframe'); + iframe.src = https_origin_url; + document.body.append(iframe); +})(); +</script> +</body> diff --git a/testing/web-platform/tests/fenced-frame/resources/header-secFetchDest-inner.html.headers b/testing/web-platform/tests/fenced-frame/resources/header-secFetchDest-inner.html.headers new file mode 100644 index 0000000000..6247f6d632 --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/resources/header-secFetchDest-inner.html.headers @@ -0,0 +1 @@ +Supports-Loading-Mode: fenced-frame
\ No newline at end of file diff --git a/testing/web-platform/tests/fenced-frame/resources/history-back-and-forward-should-not-work-in-fenced-tree-inner.html b/testing/web-platform/tests/fenced-frame/resources/history-back-and-forward-should-not-work-in-fenced-tree-inner.html new file mode 100644 index 0000000000..9620249d76 --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/resources/history-back-and-forward-should-not-work-in-fenced-tree-inner.html @@ -0,0 +1,45 @@ +<!DOCTYPE html> +<script src="/resources/testharness.js"></script> +<script src="utils.js"></script> +<title>history-back-and-forward-should-not-work-in-fenced-tree-inner</title> + +<body> + <script> + // This is a helper file that will serve as the document loaded inside + // a fenced frame by 'history-back-and-forward-should-not-work-in-fenced + // -tree' Once loaded, it will sequentially perform the back and forward + // history navigations to observe whether these methods were successfuly + // restricted for the fenced tree. + + const [history_navigation_performed_key, outer_page_ready_key, + embed_scope] = parseKeylist(); + + (async function () { + const url = new URL(location.href); + const test = url.searchParams.get("test"); + + writeValueToServer(history_navigation_performed_key, "yes"); + + // Execute history.back() within fenced frame and iframe. + await nextValueFromServer(outer_page_ready_key); + window.history.back(); + writeValueToServer(history_navigation_performed_key, "yes"); + + // Execute history.forward() within fenced frame and iframe. + await nextValueFromServer(outer_page_ready_key); + window.history.forward(); + writeValueToServer(history_navigation_performed_key, "yes"); + + if (embed_scope === "outerPage::fencedFrame::iframe") return; + + const iframe = document.createElement('iframe'); + const iframe_embed_scope = "outerPage::fencedFrame::iframe"; + iframe.src = generateURL( + "history-back-and-forward-should-not-work-in-fenced-tree-" + + "inner.html", + [history_navigation_performed_key, outer_page_ready_key, + iframe_embed_scope]); + document.body.append(iframe); + })(); + </script> +</body> diff --git a/testing/web-platform/tests/fenced-frame/resources/history-back-and-forward-should-not-work-in-fenced-tree-inner.html.headers b/testing/web-platform/tests/fenced-frame/resources/history-back-and-forward-should-not-work-in-fenced-tree-inner.html.headers new file mode 100644 index 0000000000..1b63235b7c --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/resources/history-back-and-forward-should-not-work-in-fenced-tree-inner.html.headers @@ -0,0 +1 @@ +Supports-Loading-Mode: fenced-frame diff --git a/testing/web-platform/tests/fenced-frame/resources/history-length-fenced-navigations-replace-do-not-contribute-to-joint-inner.html b/testing/web-platform/tests/fenced-frame/resources/history-length-fenced-navigations-replace-do-not-contribute-to-joint-inner.html new file mode 100644 index 0000000000..726fafd65b --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/resources/history-length-fenced-navigations-replace-do-not-contribute-to-joint-inner.html @@ -0,0 +1,115 @@ +<!DOCTYPE html> +<script src="/resources/testharness.js"></script> +<script src="utils.js"></script> +<title>Nested fenced frame named navigation helper</title> + +<body> +<script> +(async () => { + // We need to wait for the window's `load` event to fire, because client-side + // redirect navigations that take place before a document is "completely + // loaded" [1] are carried out with replacement, as specified in [2]. Just + // waiting for `load` is not enough though! After the `load` event is fired + // (but before a document is marked "completely loaded"), a microtask + // checkpoint is performed, which is where the below `Promise`'s `then` + // handler is invoked (i.e., the rest of the script). So if we just resolve + // the promise and continue, the whole script continues in the next immediate + // microtask before the document is completely loaded. So we have to queue + // another task so that we only continue executing once the document is + // considered completely loaded, and then `location.href` assignments will not + // be made with replacement history handling. + // + // [1]: https://html.spec.whatwg.org/C#the-end:completely-finish-loading + // [2]: https://html.spec.whatwg.org/#the-location-interface:completely-loaded + await new Promise(resolve => { + window.onload = e => { + setTimeout(resolve, 0); + }; + }); + + const kNavigationLimit = 5; + // This is a helper file meant to be loaded inside a fenced frame. It performs + // various navigations inside of the "fence" defined by this document, and + // ensures that they are all done in a replace-only fashion [1]. + // Once we ensure that they are all done with replacement, we report back to + // the outermost page via the server stash, and it ensures that there was no + // impact on the joint session history as observed from beyond the fence. + // + // [1]: https://html.spec.whatwg.org/C/#hh-replace + + // See documentation in the outer page. + const [fenced_navigation_complete_key, + outer_page_ready_for_next_fenced_navigation_key, + level] = parseKeylist(); + + const url = new URL(location.href); + const is_top_level_fenced_frame = (level == "top-level-fenced-frame"); + + ////////////// Navigation code that may impact `history.length` should go here + // The code in this block performs navigations that will run inside: + // - The top-level fenced frame + // - The nested fenced frame + // - The nested iframe + + // First, perform some real navigations to this same page. Normally this would + // increase `history.length`. + if (url.searchParams.get("navigationNumber") == null) + url.searchParams.append("navigationNumber", 0); + + let navigationNumber = parseInt(url.searchParams.get("navigationNumber")); + + if (navigationNumber <= kNavigationLimit) { + url.searchParams.set('navigationNumber', navigationNumber + 1); + location.href = url; + return; + } + + // At this point we're done performing 5 subsequent navigations... + + // Next, perform `history.pushState()`s. + history.pushState({} , ""); + history.pushState({} , ""); + history.pushState({} , ""); + ////////////// END + + // Finally observe `history.length` from within the fenced frame, and report + // the results back to the outermost page. + if (history.length == 1) { + writeValueToServer(fenced_navigation_complete_key, "PASS > " + + level); + } else { + writeValueToServer(fenced_navigation_complete_key, + "FAIL > " + level + " history.length: " + + history.length); + } + + // We're only testing fenced frames, nested fenced frames, and iframes nested + // within fenced frames. The below code adds a nested fenced frame and a + // nested iframe, so it should only be reached by the top-level fenced frame. + if (level != "top-level-fenced-frame") + return; + + // Only top-level fenced frames will attach a nested fenced frame and run the + // same tests there. + await nextValueFromServer(outer_page_ready_for_next_fenced_navigation_key); + const nested_fenced_frame_level = "nested-fenced-frame"; + attachFencedFrame(generateURL( + "history-length-fenced-navigations-replace-do-not-" + + "contribute-to-joint-inner.html", + [fenced_navigation_complete_key, + outer_page_ready_for_next_fenced_navigation_key, + nested_fenced_frame_level])); + + await nextValueFromServer(outer_page_ready_for_next_fenced_navigation_key); + const iframe = document.createElement('iframe'); + const nested_iframe_level = "nested-iframe"; + iframe.src = generateURL( + "history-length-fenced-navigations-replace-do-not-contribute-to-joint-" + + "inner.html", + [fenced_navigation_complete_key, + outer_page_ready_for_next_fenced_navigation_key, + nested_iframe_level]); + document.body.append(iframe); +})(); +</script> +</body> diff --git a/testing/web-platform/tests/fenced-frame/resources/history-length-fenced-navigations-replace-do-not-contribute-to-joint-inner.html.headers b/testing/web-platform/tests/fenced-frame/resources/history-length-fenced-navigations-replace-do-not-contribute-to-joint-inner.html.headers new file mode 100644 index 0000000000..1b63235b7c --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/resources/history-length-fenced-navigations-replace-do-not-contribute-to-joint-inner.html.headers @@ -0,0 +1 @@ +Supports-Loading-Mode: fenced-frame diff --git a/testing/web-platform/tests/fenced-frame/resources/history-length-outer-page-navigation-not-reflected-in-fenced-inner.html b/testing/web-platform/tests/fenced-frame/resources/history-length-outer-page-navigation-not-reflected-in-fenced-inner.html new file mode 100644 index 0000000000..2bdb90ab64 --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/resources/history-length-outer-page-navigation-not-reflected-in-fenced-inner.html @@ -0,0 +1,72 @@ +<!DOCTYPE html> +<script src="utils.js"></script> +<title>history-length-outer-page-navigation-not-reflected-in-fenced-inner</title> + +<body> +<script> +(async () => { + // This is a helper file that will servec as the document loaded inside + // a fenced frame b 'history-length-outer-page-navigation-not-reflected-in- + // fenced' Once loaded, it reports to the observed value of history.length to + // the server so that the outer document can assert the value is 1. + + const [fenced_history_length_key, outer_page_ready_for_next_navigation_key, + embed_scope, embed_scope_reporting] = + parseKeylist(); + + const url = new URL(location.href); + + if (embed_scope == "outer_page::iframe"){ + ////////////// BEGIN NAVIGATIONS + // This block performs a sequence of 'kNavigationLimit' navigations in: + // -- the iframe + const kNavigationLimit = 5 + + const url = new URL(location.href); + + // First, perform some real navigations as well as history.pushState to + // this same page. Normally this would increase `history.length`. + if (url.searchParams.get("navigationCount") == null) + url.searchParams.append("navigationCount", 1); + + let navigationCount = parseInt(url.searchParams.get("navigationCount")); + + if (navigationCount <= kNavigationLimit) { + url.searchParams.set('navigationCount', ++navigationCount); + location.href = url; + history.pushState({} , ""); + return; + } + ////////////// END + writeValueToServer(outer_page_ready_for_next_navigation_key, "READY"); + return + } + + if (embed_scope == embed_scope_reporting) { + // Observe 'history.length' from within the 'embed_scope_reporting', + // and report the results back to the outer page. + if (history.length == 1) { + writeValueToServer(fenced_history_length_key, + "PASS > " + " history.length: " + history.length); + } else { + writeValueToServer(fenced_history_length_key, + "FAIL > " + " history.length: " + history.length); + } + return + } + + if (embed_scope_reporting == "outer_page::fenced_frame::iframe") { + // Append an iframe to the 'outer_page::fenced_frame' to report + // history.length to the outer_page from within the iframe + const iframe = document.createElement('iframe'); + const embed_scope = "outer_page::fenced_frame::iframe"; + iframe.src = generateURL( + "history-length-outer-page-navigation-not-reflected-in-fenced-inner.html", + [fenced_history_length_key, outer_page_ready_for_next_navigation_key, + embed_scope, embed_scope_reporting]); + document.body.append(iframe); + return + } +})(); +</script> +</body>
\ No newline at end of file diff --git a/testing/web-platform/tests/fenced-frame/resources/history-length-outer-page-navigation-not-reflected-in-fenced-inner.html.headers b/testing/web-platform/tests/fenced-frame/resources/history-length-outer-page-navigation-not-reflected-in-fenced-inner.html.headers new file mode 100644 index 0000000000..1b63235b7c --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/resources/history-length-outer-page-navigation-not-reflected-in-fenced-inner.html.headers @@ -0,0 +1 @@ +Supports-Loading-Mode: fenced-frame diff --git a/testing/web-platform/tests/fenced-frame/resources/ignore-child-fenced-frame-onload-event-inner.html b/testing/web-platform/tests/fenced-frame/resources/ignore-child-fenced-frame-onload-event-inner.html new file mode 100644 index 0000000000..4fe496f29c --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/resources/ignore-child-fenced-frame-onload-event-inner.html @@ -0,0 +1,41 @@ +<!DOCTYPE html> +<title>child frame with delayed onload event</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="utils.js"></script> + +<body> +</body> +<script> + (function () { + const [element_type, toplevel_loaded_key, result_key] = parseKeylist(); + + // Delays the onload event of the iframe for 2 sec. + if (element_type == "iframe") { + const img = document.createElement("img"); + img.src = "/common/square.png?pipe=trickle(d2)"; + document.body.appendChild(img); + return; + } + + const iframe = document.createElement('iframe'); + iframe.src = generateURL("ignore-child-fenced-frame-onload-event-inner." + + "html", ["iframe"]); + document.body.append(iframe); + + let iframe_loaded = false; + let result = "passed"; + window.onload = async function () { + const toplevel_loaded = await readValueFromServer(toplevel_loaded_key); + if (!toplevel_loaded.status || !iframe_loaded) + result = "failed"; + writeValueToServer(result_key, result); + } + + iframe.onload = function () { + iframe_loaded = true; + } + })(); +</script> + +</html> diff --git a/testing/web-platform/tests/fenced-frame/resources/ignore-child-fenced-frame-onload-event-inner.html.headers b/testing/web-platform/tests/fenced-frame/resources/ignore-child-fenced-frame-onload-event-inner.html.headers new file mode 100644 index 0000000000..6247f6d632 --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/resources/ignore-child-fenced-frame-onload-event-inner.html.headers @@ -0,0 +1 @@ +Supports-Loading-Mode: fenced-frame
\ No newline at end of file diff --git a/testing/web-platform/tests/fenced-frame/resources/key-value-store.py b/testing/web-platform/tests/fenced-frame/resources/key-value-store.py new file mode 100644 index 0000000000..c9fd81b2a3 --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/resources/key-value-store.py @@ -0,0 +1,46 @@ +"""Key-Value store server. + +The request takes "key=" and "value=" URL parameters. The key must be UUID +generated by token(). + +- When only the "key=" is specified, serves a 200 response whose body contains + the stored value specified by the key. If the stored value doesn't exist, + serves a 200 response with an empty body. +- When both the "key=" and "value=" are specified, stores the pair and serves + a 200 response without body. +""" + + +def main(request, response): + key = request.GET.get(b"key") + value = request.GET.get(b"value") + + # Store the value. + # We have two ways to check the truthiness of `value`: + # 1. `if value` + # 2. `if value != None` + # While (1) is the most intuitive, we actually need (2), which is a looser + # check. We need the looser check so that if the URL contains `&value=` to + # set the value equal to the empty string (a case we need to support), this + # condition still evaluates to true and we enter this branch. + if value != None: + # We opted for (2) above which is the looser of the truthiness tests + # that lets empty strings into this branch. So you might think that when + # the URL contains `&value=`, then the `value` variable here would be + # equal `""`, but instead it equals the empty byte string. If you just + # store that empty byte string into the stash and retrieve it later, you + # actually get <Not set> because it doesn't recognize the empty byte + # string as a real value, so we instead have to override it to the empty + # normal string, and then we can store it for later use. This is + # because we have to support storage and retrieval of empty strings. + if type(value) is bytes and value == b'': + value = "" + + request.server.stash.put(key, value) + return (200, [], b"") + + # Get the value. + data = request.server.stash.take(key) + if not data and data != "": + return (200, [], b"<Not set>") + return (200, [], data) diff --git a/testing/web-platform/tests/fenced-frame/resources/location-ancestorOrigins-inner.https.html b/testing/web-platform/tests/fenced-frame/resources/location-ancestorOrigins-inner.https.html new file mode 100644 index 0000000000..3c9411c520 --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/resources/location-ancestorOrigins-inner.https.html @@ -0,0 +1,53 @@ +<!DOCTYPE html> +<script src="/resources/testharness.js"></script> +<script src="utils.js"></script> +<title>Fenced frame content to report the value of location.ancestorOrigins</title> + +<body> +<script> +async function init() { // Needed in order to use top-level await. + // This file is meant to run in a <fencedframe>. It reports back to the + // outermost page the value of `location.ancestorOrigins` correct for: + // 1.) Top-level fenced frames + // 2.) Nested iframes inside a fenced frame + // 3.) Nested fenced frames + const url = new URL(location.href); + + const [location_ao_key, location_ao_ack_key, nested] = parseKeylist(); + + const is_nested_fenced_frame = nested == "nested"; + + // Report `location.ancestorOrigins`. + writeValueToServer(location_ao_key, Array.from(location.ancestorOrigins).join()); + + // If this page is a nested fenced frame, all we need to do is report the + // top-level value. + if (is_nested_fenced_frame) + return; + + // Wait for ACK, so we know that the outer page has read the last value from + // the `location_ao_key` stash and we can write to it again. + await nextValueFromServer(location_ao_ack_key); + + const nested_url = generateURL("location-ancestorOrigins-inner.https.html", + [location_ao_key, location_ao_ack_key, "nested"]); + + // Send `location.ancestorOrigins` from an iframe. + const iframe = document.createElement('iframe'); + iframe.src = nested_url; + const load_promise = new Promise((resolve, reject) => { + iframe.onload = resolve; + iframe.onerror = reject; + }); + document.body.append(iframe); + + // Wait for ACK, so we know that the outer page has read the ancestorOrigins + // from the iframe. + await nextValueFromServer(location_ao_ack_key); + + attachFencedFrame(nested_url); +} + +init(); +</script> +</body> diff --git a/testing/web-platform/tests/fenced-frame/resources/location-ancestorOrigins-inner.https.html.headers b/testing/web-platform/tests/fenced-frame/resources/location-ancestorOrigins-inner.https.html.headers new file mode 100644 index 0000000000..6247f6d632 --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/resources/location-ancestorOrigins-inner.https.html.headers @@ -0,0 +1 @@ +Supports-Loading-Mode: fenced-frame
\ No newline at end of file diff --git a/testing/web-platform/tests/fenced-frame/resources/navigate-ancestor-destination.https.html b/testing/web-platform/tests/fenced-frame/resources/navigate-ancestor-destination.https.html new file mode 100644 index 0000000000..f12849c8ec --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/resources/navigate-ancestor-destination.https.html @@ -0,0 +1,17 @@ +<!DOCTYPE html> +<script src="utils.js"></script> +<title>Nested fenced frame named navigation helper</title> +<!-- See `navigate-ancestor-from-nested-{fencedframe,iframe}.https.html` for + more documentation --> +<script> + // This is a helper file. It is the document that + // `navigate-ancestor-from-nested{fencedframe,iframe}-helper.https.html` + // explicitly navigates the "correct" ancestor frame to, for any test run by + // `navigate-ancestor-test-runner.https.html`. The test itself is responsible + // for verifying that this document loaded in the correct frame. We just + // simply report that we successfully wound up in this document, to indicate + // that we're finished. + const [navigate_ancestor_key] = parseKeylist(); + writeValueToServer(navigate_ancestor_key, + "navigate-ancestor-destination.https.html"); +</script> diff --git a/testing/web-platform/tests/fenced-frame/resources/navigate-ancestor-destination.https.html.headers b/testing/web-platform/tests/fenced-frame/resources/navigate-ancestor-destination.https.html.headers new file mode 100644 index 0000000000..6247f6d632 --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/resources/navigate-ancestor-destination.https.html.headers @@ -0,0 +1 @@ +Supports-Loading-Mode: fenced-frame
\ No newline at end of file diff --git a/testing/web-platform/tests/fenced-frame/resources/navigate-ancestor-from-nested-fenced-frame.https.html b/testing/web-platform/tests/fenced-frame/resources/navigate-ancestor-from-nested-fenced-frame.https.html new file mode 100644 index 0000000000..74800b969f --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/resources/navigate-ancestor-from-nested-fenced-frame.https.html @@ -0,0 +1,39 @@ +<!DOCTYPE html> +<title>Navigate ancestor helper</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="utils.js"></script> + +<body> +<script> +(async () => { + // This document is loaded into a fenced frame by + // `navigate-ancestor-test-runner.https.html`. It creates a nested fenced + // frame and navigates it to `navigate-ancestor-helper.https.html`. + + // - navigate_ancestor_key: + // Sent by `navigate-ancestor-destination.https.html`. We listen to it, and + // report back to our embedder that it loaded correctly and did not navigate + // *this* frame. + // - navigate_ancestor_from_nested_key: + // Sent by us to our embedder to indicate (depending on the message) either: + // - PASS: The nested fenced frame was navigated correctly when it tried to + // navigate its ancestor (parent or top) frame + // - FAIL: When the nested fenced frame tried to navigate its ancestor + // frame, it actually navigated *this frame*, which is wrong + const [navigate_ancestor_key, navigate_ancestor_from_nested_key, + ancestor_type] = parseKeylist(); + + window.onbeforeunload = e => { + writeValueToServer(navigate_ancestor_from_nested_key, + `FAIL nested fenced frame ${ancestor_type}`); + } + + attachFencedFrame(generateURL(`navigate-ancestor-helper.https.html`, + [navigate_ancestor_key, ancestor_type])); + await nextValueFromServer(navigate_ancestor_key); + window.onbeforeunload = null; + writeValueToServer(navigate_ancestor_from_nested_key, "PASS"); +})(); +</script> +</body> diff --git a/testing/web-platform/tests/fenced-frame/resources/navigate-ancestor-from-nested-fenced-frame.https.html.headers b/testing/web-platform/tests/fenced-frame/resources/navigate-ancestor-from-nested-fenced-frame.https.html.headers new file mode 100644 index 0000000000..6247f6d632 --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/resources/navigate-ancestor-from-nested-fenced-frame.https.html.headers @@ -0,0 +1 @@ +Supports-Loading-Mode: fenced-frame
\ No newline at end of file diff --git a/testing/web-platform/tests/fenced-frame/resources/navigate-ancestor-from-nested-iframe.https.html b/testing/web-platform/tests/fenced-frame/resources/navigate-ancestor-from-nested-iframe.https.html new file mode 100644 index 0000000000..63a0cca8b4 --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/resources/navigate-ancestor-from-nested-iframe.https.html @@ -0,0 +1,38 @@ +<!DOCTYPE html> +<title>Navigate ancestor helper from nested fenced frame</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="utils.js"></script> + +<body> +<script> +(async () => { + // This document is loaded into a fenced frame by + // `navigate-ancestor-test-runner.https.html`. It creates a nested iframe and + // navigates it to `navigate-ancestor-helper.https.html`. + + // navigate_ancestor_from_nested_key sent by us to our embedder to + // indicate that an message was sent from the nested iframe when it failed to + // navigate the ancestor (this) frame. + const [navigate_ancestor_key, navigate_ancestor_from_nested_key, + ancestor_type] = parseKeylist(); + + // An message should be sent from the iframe. + window.addEventListener('message', (e) => { + window.onbeforeunload = null; + writeValueToServer( + navigate_ancestor_from_nested_key, + `PASS: [${ancestor_type}] ${e.data}`); + }); + + // When the iframe tries to navigate its ancestor frame, it should not + // navigate *this* frame, because the sandboxed navigation browsing context + // flag [1] must be set in fenced frame trees. + // [1] https://html.spec.whatwg.org/multipage/origin.html#sandboxed-navigation-browsing-context-flag + const iframe = document.createElement('iframe'); + iframe.src = generateURL(`navigate-ancestor-helper.https.html`, + [navigate_ancestor_key, ancestor_type]); + document.body.append(iframe); +})(); +</script> +</body> diff --git a/testing/web-platform/tests/fenced-frame/resources/navigate-ancestor-from-nested-iframe.https.html.headers b/testing/web-platform/tests/fenced-frame/resources/navigate-ancestor-from-nested-iframe.https.html.headers new file mode 100644 index 0000000000..6247f6d632 --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/resources/navigate-ancestor-from-nested-iframe.https.html.headers @@ -0,0 +1 @@ +Supports-Loading-Mode: fenced-frame
\ No newline at end of file diff --git a/testing/web-platform/tests/fenced-frame/resources/navigate-ancestor-helper.https.html b/testing/web-platform/tests/fenced-frame/resources/navigate-ancestor-helper.https.html new file mode 100644 index 0000000000..2cd8fcf786 --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/resources/navigate-ancestor-helper.https.html @@ -0,0 +1,33 @@ +<!DOCTYPE html> +<title>Navigate ancestor helper</title> + +<script src="/resources/testdriver.js"></script> +<script src="/resources/testdriver-actions.js"></script> +<script src="/resources/testdriver-vendor.js"></script> +<script src="utils.js"></script> +<body> +<script> +(async () => { + // This document is loaded into either a top-level fenced frame, a nested + // fenced frame, or an iframe in a top-level fenced frame. In any case, this + // document is always the inner-most document in any test. It navigates an + // ancestor frame by clicking the anchor above via script. When this document + // is loaded in a fenced frame, the frame that should actually navigate is + // this one, since fenced frames are top-level browsing contexts. + // When this document is loaded into a top-level fenced frame or a nested + // fenced frame, we test that the right frame is navigated in + // `navigate-ancestor-test-runner.https.html`. When this document is loaded + // into an iframe in a top-level fenced frame, we test that the navigation is + // blocked due to the sandbox behavior of fenced frame trees. + const [navigate_ancestor_key, ancestor_type] = parseKeylist(); + const url = generateURL(`navigate-ancestor-destination.https.html`, + [navigate_ancestor_key]); + await simulateGesture(); + try { + window[ancestor_type].location = url; + } catch (e) { + window[ancestor_type].postMessage('location change failed.'); + } +})(); +</script> +</body> diff --git a/testing/web-platform/tests/fenced-frame/resources/navigate-ancestor-helper.https.html.headers b/testing/web-platform/tests/fenced-frame/resources/navigate-ancestor-helper.https.html.headers new file mode 100644 index 0000000000..6247f6d632 --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/resources/navigate-ancestor-helper.https.html.headers @@ -0,0 +1 @@ +Supports-Loading-Mode: fenced-frame
\ No newline at end of file diff --git a/testing/web-platform/tests/fenced-frame/resources/navigate-ancestor-helper.js b/testing/web-platform/tests/fenced-frame/resources/navigate-ancestor-helper.js new file mode 100644 index 0000000000..ade17c69f2 --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/resources/navigate-ancestor-helper.js @@ -0,0 +1,28 @@ + +async function runNavigateAncestorTest(test_type, ancestor_type) { + // See documentation in `resources/navigate-ancestor-test-runner.https.html`. + // For each test type here, this document opens a new auxiliary window that + // runs the actual test. The tests in some way or another, direct a frame + // *inside* a fenced frame to navigate an ancestor frame via an + // <a target="_parent|_top"></a>. We need to run the real test in a new window + // so that if that window ends up navigating unexpectedly (because the fenced + // frame can accidentally navigated its embedder, for example) we can detect + // it from ths page, which never navigates away. + const navigate_ancestor_key = token(); + const navigate_ancestor_from_nested_key = token(); + + const win = window.open(generateURL( + "resources/navigate-ancestor-test-runner.https.html", + [navigate_ancestor_key, navigate_ancestor_from_nested_key])); + await new Promise(resolve => { + win.onload = resolve; + }); + + const pagehidePromise = new Promise(resolve => { + win.onpagehide = resolve; + }); + + await win.runTest(test_type, ancestor_type); + win.close(); + await pagehidePromise; +} diff --git a/testing/web-platform/tests/fenced-frame/resources/navigate-ancestor-test-runner.https.html b/testing/web-platform/tests/fenced-frame/resources/navigate-ancestor-test-runner.https.html new file mode 100644 index 0000000000..d0f2e8d694 --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/resources/navigate-ancestor-test-runner.https.html @@ -0,0 +1,76 @@ +<!DOCTYPE html> +<title>Test navigating an ancestor frame from within a fenced frame</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="utils.js"></script> + +<body> +<script> +// This function is called by `window.opener`, which is a same-origin window. +window.runTest = function(test_type, ancestor_type) { + // Messages by this key are sent from + // `navigate-ancestor-destination.https.html` to let us know if the "_parent" + // navigations performed inside fenced frames landed on the right page. + // If somehow *this document* gets navigated unexpectedly, the test will fail + // given `beforeunloadPromise` below. + // For "nested" tests, this document hosts a top-level fenced frame navigated + // to `navigate-ancestor-from-nested-{fenced-frame,iframe}.https.html`, + // which itself hosts a nested fenced frame or iframe. The top-level fenced + // frame will wait for the right confirmation that the nested document has + // operated correctly, and report back to *us* that everything is OK via this + // key below. + const [navigate_ancestor_key, navigate_ancestor_from_nested_key] = + parseKeylist(); + + const beforeunloadPromise = new Promise((resolve, reject) => { + window.onbeforeunload = e => { + reject(`The top-level test runner document does not navigate when a ` + + `${test_type} navigates ${ancestor_type}`); + } + }); + + let test_promise = null; + switch (test_type) { + case 'top-level fenced frame': + // This fenced frame will attempt to navigate its parent to + // `navigate-ancestor-destination.https.html`. It should end up navigating + // *itself* since it is a top-level browsing context. Just in case it + // accidentally navigates *this* frame, we have an `onbeforeunload` + // handler that will automatically fail the test before. + attachFencedFrame(generateURL( + `navigate-ancestor-helper.https.html`, + [navigate_ancestor_key, ancestor_type])); + test_promise = nextValueFromServer(navigate_ancestor_key); + break; + case 'nested fenced frame': + attachFencedFrame(generateURL( + `navigate-ancestor-from-nested-fenced-frame.https.html`, + [navigate_ancestor_key, navigate_ancestor_from_nested_key, + ancestor_type])); + test_promise = nextValueFromServer(navigate_ancestor_from_nested_key) + .then(message => { + if (message != "PASS") { + throw message; + } + }); + break; + case 'nested iframe': + attachFencedFrame(generateURL( + `navigate-ancestor-from-nested-iframe.https.html`, + [navigate_ancestor_key, navigate_ancestor_from_nested_key, + ancestor_type])); + test_promise = nextValueFromServer(navigate_ancestor_from_nested_key) + .then(message => { + if (message != `PASS: [${ancestor_type}] location change failed.`) { + throw message; + } + }); + + break; + } + + return Promise.race([test_promise, beforeunloadPromise]); +} +</script> + +</body> diff --git a/testing/web-platform/tests/fenced-frame/resources/navigate-by-name-inner.html b/testing/web-platform/tests/fenced-frame/resources/navigate-by-name-inner.html new file mode 100644 index 0000000000..c7d7d6f278 --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/resources/navigate-by-name-inner.html @@ -0,0 +1,45 @@ +<!DOCTYPE html> +<script src="/resources/testharness.js"></script> +<script src="utils.js"></script> +<title>Fenced frame hosting named frames</title> + +<body> +<script> +async function init() { + // This file is meant to run in a <fencedframe>. It sets up multiple frames + // all with the name `target_frame` in the following arrangements: + // 1.) A top-level fenced frame + // 2.) An iframe within a fenced frame + // 3.) A nested fenced frame + // Navigations to all of the above should fail, and thus should open a new + // top-level popup window instead of navigating these frames. + + const [ready_for_navigation_key, test_type] = parseKeylist(); + + switch (test_type) { + case "top-level fenced frame": + // Set up the named frame and report to the outer document that we're ready + // for it to try and navigate the named frame. + window.name = "target_frame"; + writeValueToServer(ready_for_navigation_key, "READY"); + break; + case "nested iframe": + const iframe = document.createElement('iframe'); + iframe.name = "target_frame"; + document.body.append(iframe); + writeValueToServer(ready_for_navigation_key, "READY"); + break; + case "nested fenced frame": + // This fenced frame will report to the outermost document when it is ready. + const ff = + attachFencedFrame(generateURL( + "fenced-frame-set-name-and-report-ready-for-" + + "outermost-document-to-navigate.html", + [ready_for_navigation_key])); + break; + } +} + +init(); +</script> +</body> diff --git a/testing/web-platform/tests/fenced-frame/resources/navigate-by-name-inner.html.headers b/testing/web-platform/tests/fenced-frame/resources/navigate-by-name-inner.html.headers new file mode 100644 index 0000000000..6247f6d632 --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/resources/navigate-by-name-inner.html.headers @@ -0,0 +1 @@ +Supports-Loading-Mode: fenced-frame
\ No newline at end of file diff --git a/testing/web-platform/tests/fenced-frame/resources/navigate-by-name-reporting-helper.html b/testing/web-platform/tests/fenced-frame/resources/navigate-by-name-reporting-helper.html new file mode 100644 index 0000000000..d3bd955697 --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/resources/navigate-by-name-reporting-helper.html @@ -0,0 +1,34 @@ +<!DOCTYPE html> +<script src="utils.js"></script> +<title>Navigate reporting helper</title> +<!-- This document is used as a helper by `../navigate-by-name.html`. That test + attempts to navigate various frames all named `target_frame`, to this + document. All of these navigations should fail, due to the frames being + unreachable to the initiator (because of the "fence" of the fenced frame). + As a result, this document should always load in a new top-level + "outermost" pop-up window. +--> + +<script> +const [navigation_success_key] = parseKeylist(); + +// We're currently using `window.opener` as a proxy for "did this load in a new +// outermost popup window?". Note that if we try and test navigations initiated +// from inside a fenced frame and they open up in a new outermost popup, there +// will be no opener by default (crbug.com/1250694) so using `window.opener` as +// a signal will be insufficient. In order to test anchor navigations to this +// document from within a fenced frame, we'll need a better signal for +// outermost-ness. We should consider adding a value to `document.loadingMode` +// for fenced frames: +// https://wicg.github.io/nav-speculation/prerendering.htmlprerendering.html#browsing-context-loading-mode. +// +// Alternatively if we really want to detect if this loaded inside a fenced +// frame, we could just remove the opt-in headers and then implementations that +// support fenced frame opt-ins would timeout if they somehow don't honor the +// fence on named frame navigations, but that's not a very good outcome. +if (window.opener) { + writeValueToServer(navigation_success_key, "PASS"); +} else { + writeValueToServer(navigation_success_key, "FAIL"); +} +</script> diff --git a/testing/web-platform/tests/fenced-frame/resources/navigate-by-name-reporting-helper.html.headers b/testing/web-platform/tests/fenced-frame/resources/navigate-by-name-reporting-helper.html.headers new file mode 100644 index 0000000000..6247f6d632 --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/resources/navigate-by-name-reporting-helper.html.headers @@ -0,0 +1 @@ +Supports-Loading-Mode: fenced-frame
\ No newline at end of file diff --git a/testing/web-platform/tests/fenced-frame/resources/navigate-nested-config.html b/testing/web-platform/tests/fenced-frame/resources/navigate-nested-config.html new file mode 100644 index 0000000000..85c5194c6c --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/resources/navigate-nested-config.html @@ -0,0 +1,14 @@ +<!DOCTYPE html> +<script src="/resources/testharness.js"></script> +<script src="utils.js"></script> +<title>Navigate a fenced frame to a nested config</title> +<body> +<script> + const [key] = parseKeylist(); + const configs = window.fence.getNestedConfigs(); + const ff = document.createElement("fencedframe"); + ff.config = configs[0] + document.body.appendChild(ff); +</script> +</body> +</html> diff --git a/testing/web-platform/tests/fenced-frame/resources/navigate-nested-config.html.headers b/testing/web-platform/tests/fenced-frame/resources/navigate-nested-config.html.headers new file mode 100644 index 0000000000..6247f6d632 --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/resources/navigate-nested-config.html.headers @@ -0,0 +1 @@ +Supports-Loading-Mode: fenced-frame
\ No newline at end of file diff --git a/testing/web-platform/tests/fenced-frame/resources/navigator-keyboard-layout-map-inner.html b/testing/web-platform/tests/fenced-frame/resources/navigator-keyboard-layout-map-inner.html new file mode 100644 index 0000000000..59170c7512 --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/resources/navigator-keyboard-layout-map-inner.html @@ -0,0 +1,21 @@ +<!DOCTYPE html> +<script src="utils.js"></script> +<title>Fenced frame content to report the value of navigator.keyboard.getLayoutMap</title> + +<body> +<script> +async function init() { // Needed in order to use top-level await. + // This file is meant to be navigated to from a <fencedframe> element. It + // reports back to the page hosting the <fencedframe> whether or not + // `keyboard.getLayoutMap` is allowed. + const keyboard_layout_key = parseKeylist(); + // Report whether or not `navigator.keyboard.getLayoutMap()` is allowed. + navigator.keyboard.getLayoutMap().then( + () => { writeValueToServer(keyboard_layout_key, "resolved"); }, + () => { writeValueToServer(keyboard_layout_key, "rejected");}, + ); +} + +init(); +</script> +</body> diff --git a/testing/web-platform/tests/fenced-frame/resources/navigator-keyboard-layout-map-inner.html.headers b/testing/web-platform/tests/fenced-frame/resources/navigator-keyboard-layout-map-inner.html.headers new file mode 100644 index 0000000000..6247f6d632 --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/resources/navigator-keyboard-layout-map-inner.html.headers @@ -0,0 +1 @@ +Supports-Loading-Mode: fenced-frame
\ No newline at end of file diff --git a/testing/web-platform/tests/fenced-frame/resources/navigator-keyboard-lock-inner.html b/testing/web-platform/tests/fenced-frame/resources/navigator-keyboard-lock-inner.html new file mode 100644 index 0000000000..105166c7ad --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/resources/navigator-keyboard-lock-inner.html @@ -0,0 +1,21 @@ +<!DOCTYPE html> +<script src="utils.js"></script> +<title>Fenced frame content to report the value of navigator.keyboard.lock</title> + +<body> +<script> +async function init() { // Needed in order to use top-level await. + // This file is meant to be navigated to from a <fencedframe> element. It + // reports back to the page hosting the <fencedframe> whether or not + // `keyboard.lock` is allowed. + const [keyboard_lock_key] = parseKeylist(); + // Report whether or not `navigator.keyboard.lock()` is allowed. + navigator.keyboard.lock().then( + () => { writeValueToServer(keyboard_lock_key, "resolved"); }, + () => { writeValueToServer(keyboard_lock_key, "rejected");}, + ); +} + +init(); +</script> +</body> diff --git a/testing/web-platform/tests/fenced-frame/resources/navigator-keyboard-lock-inner.html.headers b/testing/web-platform/tests/fenced-frame/resources/navigator-keyboard-lock-inner.html.headers new file mode 100644 index 0000000000..6247f6d632 --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/resources/navigator-keyboard-lock-inner.html.headers @@ -0,0 +1 @@ +Supports-Loading-Mode: fenced-frame
\ No newline at end of file diff --git a/testing/web-platform/tests/fenced-frame/resources/notification-sw.js b/testing/web-platform/tests/fenced-frame/resources/notification-sw.js new file mode 100644 index 0000000000..e9b1e2b9dd --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/resources/notification-sw.js @@ -0,0 +1,20 @@ +self.addEventListener('install', e => e.waitUntil(skipWaiting())); +self.addEventListener('activate', e => e.waitUntil(clients.claim())); + +self.addEventListener('message', async event => { + const method = event.data; + + if (method === 'constructor') { + try { + new Notification('test'); + } catch (e) { + event.source.postMessage(e.message); + } + } else if (method === 'showNotification') { + try { + await self.registration.showNotification('test', {body: 'test'}); + } catch (e) { + event.source.postMessage(e.message); + } + } +}); diff --git a/testing/web-platform/tests/fenced-frame/resources/opaque-ad-sizes-utils.js b/testing/web-platform/tests/fenced-frame/resources/opaque-ad-sizes-utils.js new file mode 100644 index 0000000000..edf8640f20 --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/resources/opaque-ad-sizes-utils.js @@ -0,0 +1,47 @@ +async function runOpaqueAdSizesTest(input_width, input_height, output_width, output_height) { + // Attach a FLEDGE fenced frame whose outer container has dimensions + // `input_width` by `input_height`. + const frame = await attachFencedFrameContext({ + generator_api: "fledge", resolve_to_config: true, attributes: [ + ["width", input_width], ["height", input_height]]}); + + const assert_dimensions = + (label, input_width, input_height, output_width, output_height) => { + assert_equals(getComputedStyle(document.documentElement).width, + output_width+"px", + label + " the computed width coerces to " + output_width); + assert_equals(window.innerWidth, output_width, + label + " the innerWidth " + input_width + " coerces to " + output_width); + assert_equals(window.innerHeight, output_height, + label + " the innerHeight " + input_height + " coerces to " + output_height); + } + + // Assert that the fenced frame sees its dimensions rounded to the nearest + // ad size. + await frame.execute(assert_dimensions, + ["After navigation", input_width, input_height, output_width, output_height]); + + // Assert that the embedder sees the fenced frame's original dimensions. + assert_equals(frame.width, input_width.toString(), + "The outer container width is the requested width."); + assert_equals(frame.height, input_height.toString(), + "The outer container height is the requested height."); + + // Resize the fenced frame's outer container. + const new_size_x = 320; + const new_size_y = 50; + frame.width = new_size_x; + frame.height = new_size_y; + + // Refresh the fenced frame. + await frame.execute(() => { + window.executor.suspend(() => { + location.href = location.href; + }); + }); + + // Observe that navigations after the first don't change the fenced frame's + // inner dimensions. + await frame.execute(assert_dimensions, + ["After resizing", input_width, input_height, output_width, output_height]); +} diff --git a/testing/web-platform/tests/fenced-frame/resources/payment-handler-sw.js b/testing/web-platform/tests/fenced-frame/resources/payment-handler-sw.js new file mode 100644 index 0000000000..8b5e83cddf --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/resources/payment-handler-sw.js @@ -0,0 +1,10 @@ +self.addEventListener('install', e => e.waitUntil(skipWaiting())); +self.addEventListener('activate', e => e.waitUntil(clients.claim())); + +self.addEventListener('message', event => { + try { + self.registration.paymentManager; + } catch (e) { + event.source.postMessage(e); + } +}); diff --git a/testing/web-platform/tests/fenced-frame/resources/permission-api-denied-inner.html b/testing/web-platform/tests/fenced-frame/resources/permission-api-denied-inner.html new file mode 100644 index 0000000000..06724ac061 --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/resources/permission-api-denied-inner.html @@ -0,0 +1,15 @@ +<!DOCTYPE html> +<script src="utils.js"></script> +<title>Fenced frame content to report the result of navigator.permissions.query</title> + +<body> + <script> + (async () => { + const [permission_key, permission_name] = parseKeylist(); + // Push permission without userVisibleOnly:true is not supported. + let user_visible_only = permission_name === 'push' ? true : false; + const result = await navigator.permissions.query({ name: permission_name, userVisibleOnly: user_visible_only }); + writeValueToServer(permission_key, `result: ${result.state}`); + })(); + </script> +</body> diff --git a/testing/web-platform/tests/fenced-frame/resources/permission-api-denied-inner.html.headers b/testing/web-platform/tests/fenced-frame/resources/permission-api-denied-inner.html.headers new file mode 100644 index 0000000000..1b63235b7c --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/resources/permission-api-denied-inner.html.headers @@ -0,0 +1 @@ +Supports-Loading-Mode: fenced-frame diff --git a/testing/web-platform/tests/fenced-frame/resources/permission-geolocation-inner.html b/testing/web-platform/tests/fenced-frame/resources/permission-geolocation-inner.html new file mode 100644 index 0000000000..07c3e662bf --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/resources/permission-geolocation-inner.html @@ -0,0 +1,16 @@ +<!DOCTYPE html> +<script src="utils.js"></script> +<title>Fenced frame content to report the result of navigator.geolocation.getCurrentPosition()</title> + +<body> +<script> +(async () => { + const [permission_geolocation_key] = parseKeylist(); + const result = await new Promise(resolve => { + navigator.geolocation.getCurrentPosition( + () => resolve('granted'), () => resolve('denied')); + }); + writeValueToServer(permission_geolocation_key, `result: ${result}`); +})(); +</script> +</body>
\ No newline at end of file diff --git a/testing/web-platform/tests/fenced-frame/resources/permission-geolocation-inner.html.headers b/testing/web-platform/tests/fenced-frame/resources/permission-geolocation-inner.html.headers new file mode 100644 index 0000000000..1b63235b7c --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/resources/permission-geolocation-inner.html.headers @@ -0,0 +1 @@ +Supports-Loading-Mode: fenced-frame diff --git a/testing/web-platform/tests/fenced-frame/resources/permission-geolocation-test-runner.html b/testing/web-platform/tests/fenced-frame/resources/permission-geolocation-test-runner.html new file mode 100644 index 0000000000..724a35ce9a --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/resources/permission-geolocation-test-runner.html @@ -0,0 +1,24 @@ +<!DOCTYPE html> +<title>Fenced frame content to report the result of navigator.geolocation.getCurrentPosition()</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/resources/testdriver.js"></script> +<script src="/resources/testdriver-vendor.js"></script> +<script src="utils.js"></script> + +<body> +<script> + +window.runTest = async (fenced_frame_url) => { + const [permission_geolocation_key] = parseKeylist(); + await test_driver.set_permission({name: 'geolocation'}, 'granted', true); + + attachFencedFrame(generateURL(fenced_frame_url, [permission_geolocation_key])); + const actual_result = await nextValueFromServer(permission_geolocation_key); + + assert_equals( + actual_result, 'result: denied', + 'geolocation permission is not permitted for fenced frames.'); +}; +</script> +</body> diff --git a/testing/web-platform/tests/fenced-frame/resources/permission-notification-inner.html b/testing/web-platform/tests/fenced-frame/resources/permission-notification-inner.html new file mode 100644 index 0000000000..d01d10034c --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/resources/permission-notification-inner.html @@ -0,0 +1,13 @@ +<!DOCTYPE html> +<script src="utils.js"></script> +<title>Fenced frame content to report the result of Notification.requestPermission</title> + +<body> +<script> +(async () => { + const [permission_notification_key] = parseKeylist(); + const result = await Notification.requestPermission(); + writeValueToServer(permission_notification_key, `result: ${result}`); +})(); +</script> +</body> diff --git a/testing/web-platform/tests/fenced-frame/resources/permission-notification-inner.html.headers b/testing/web-platform/tests/fenced-frame/resources/permission-notification-inner.html.headers new file mode 100644 index 0000000000..1b63235b7c --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/resources/permission-notification-inner.html.headers @@ -0,0 +1 @@ +Supports-Loading-Mode: fenced-frame diff --git a/testing/web-platform/tests/fenced-frame/resources/popup-noopener-destination.html b/testing/web-platform/tests/fenced-frame/resources/popup-noopener-destination.html new file mode 100644 index 0000000000..30cc21f22c --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/resources/popup-noopener-destination.html @@ -0,0 +1,20 @@ +<!DOCTYPE html> +<script src="utils.js"></script> +<title>Destination page opened by a frame in a Fenced Frame tree</title> +<script> + // It is the document that `popup-noopener-inner.html` loads in a new + // window/tab from a root fenced frame, an iframe in a fenced frame and from + // a nested fenced frame. It's expected that any popup opened from a Fenced + // Frame tree cannot reach the opener. + const [popup_noopener_key, popup_name_key] = parseKeylist(); + if (window.opener) { + writeValueToServer(popup_noopener_key, "FAIL: window.opener is not null"); + } else { + writeValueToServer(popup_noopener_key, "PASS"); + } + if (window.name) { + writeValueToServer(popup_name_key, "FAIL: window.name is not empty"); + } else { + writeValueToServer(popup_name_key, "PASS"); + } +</script> diff --git a/testing/web-platform/tests/fenced-frame/resources/popup-noopener-inner.html b/testing/web-platform/tests/fenced-frame/resources/popup-noopener-inner.html new file mode 100644 index 0000000000..6a79fd21b2 --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/resources/popup-noopener-inner.html @@ -0,0 +1,48 @@ +<!DOCTYPE html> +<script src="/resources/testharness.js"></script> +<script src="utils.js"></script> +<title>Fenced frame creating popups</title> + +<body> +<script> +async function init() { + // This file is meant to run in a <fencedframe>. It sets up multiple frames + // in the following arrangements: + // 1.) A top-level fenced frame + // 2.) An iframe within a fenced frame + // 3.) A nested fenced frame + // All of the above frames create a popup which should not return a reference + // to the new window. + + const [popup_noopener_key, popup_openee_key, popup_name_key, test_type] = + parseKeylist(); + + switch (test_type) { + case "top-level fenced frame": + src_popup = generateURL(`popup-noopener-destination.html`, + [popup_noopener_key, popup_name_key]); + const popup = window.open(src_popup, "foo"); + if (popup) { + writeValueToServer(popup_openee_key, "FAIL"); + } else { + writeValueToServer(popup_openee_key, "PASS"); + } + break; + case "nested iframe": + const iframe = document.createElement('iframe'); + document.body.append(iframe); + iframe.src = generateURL(`create-popup.html`, + [popup_noopener_key, popup_openee_key, popup_name_key]); + break; + case "nested fenced frame": + const ff = + attachFencedFrame(generateURL(`create-popup.html`, + [popup_noopener_key, popup_openee_key, popup_name_key])); + break; + } + +} + +init(); +</script> +</body> diff --git a/testing/web-platform/tests/fenced-frame/resources/popup-noopener-inner.html.headers b/testing/web-platform/tests/fenced-frame/resources/popup-noopener-inner.html.headers new file mode 100644 index 0000000000..1b63235b7c --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/resources/popup-noopener-inner.html.headers @@ -0,0 +1 @@ +Supports-Loading-Mode: fenced-frame diff --git a/testing/web-platform/tests/fenced-frame/resources/postmessage-config.html b/testing/web-platform/tests/fenced-frame/resources/postmessage-config.html new file mode 100644 index 0000000000..3c20190420 --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/resources/postmessage-config.html @@ -0,0 +1,14 @@ +<!DOCTYPE html> +<script src="utils.js"></script> +<title>A page opened in a pop-up that sends a FencedFrameConfig</title> +<script> + async function init() { + const [key] = parseKeylist(); + + const config = await generateURNFromFledge( + "embeddee.html", [key], [], true); + + window.opener.postMessage(config, "*"); + } + init(); +</script> diff --git a/testing/web-platform/tests/fenced-frame/resources/prerender-inner.html b/testing/web-platform/tests/fenced-frame/resources/prerender-inner.html new file mode 100644 index 0000000000..a523ef31c1 --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/resources/prerender-inner.html @@ -0,0 +1,20 @@ +<!DOCTYPE html> +<script src="utils.js"></script> +<title>Fenced frame content to report the result of prerendering</title> + +<body> +<script> +(() => { + [prerender_ready_key, prerender_loaded_key, prerender_activated_key] = + parseKeylist(); + document.addEventListener('prerenderingchange', () => { + writeValueToServer(prerender_activated_key, 'activated'); + }); + if (document.prerendering) { + writeValueToServer(prerender_ready_key, 'ready'); + } else { + writeValueToServer(prerender_loaded_key, 'loaded'); + } +})(); +</script> +</body> diff --git a/testing/web-platform/tests/fenced-frame/resources/prerender-inner.html.headers b/testing/web-platform/tests/fenced-frame/resources/prerender-inner.html.headers new file mode 100644 index 0000000000..1b63235b7c --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/resources/prerender-inner.html.headers @@ -0,0 +1 @@ +Supports-Loading-Mode: fenced-frame diff --git a/testing/web-platform/tests/fenced-frame/resources/presentation-receiver-inner.html b/testing/web-platform/tests/fenced-frame/resources/presentation-receiver-inner.html new file mode 100644 index 0000000000..2e170dd91b --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/resources/presentation-receiver-inner.html @@ -0,0 +1,17 @@ +<!DOCTYPE html> +<script src="utils.js"></script> +<title>Fenced frame content to report the result of navigator.presentation.receiver</title> + +<body> +<script> +(async () => { + const [presentation_receiver_key] = parseKeylist(); + const result = await navigator.presentation.receiver; + if (result == null) { + writeValueToServer(presentation_receiver_key, "denied"); + } else { + writeValueToServer(presentation_receiver_key, "allowed"); + } +})(); +</script> +</body> diff --git a/testing/web-platform/tests/fenced-frame/resources/presentation-receiver-inner.html.headers b/testing/web-platform/tests/fenced-frame/resources/presentation-receiver-inner.html.headers new file mode 100644 index 0000000000..1b63235b7c --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/resources/presentation-receiver-inner.html.headers @@ -0,0 +1 @@ +Supports-Loading-Mode: fenced-frame diff --git a/testing/web-platform/tests/fenced-frame/resources/remote-context-executor.https.html b/testing/web-platform/tests/fenced-frame/resources/remote-context-executor.https.html new file mode 100644 index 0000000000..6b2f5ccc00 --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/resources/remote-context-executor.https.html @@ -0,0 +1,29 @@ +<!DOCTYPE html> +<script src="/resources/testharness.js"></script> +<script src="utils.js"></script> +<script src="/common/dispatcher/dispatcher.js"></script> +<script src="/common/get-host-info.sub.js"></script> +<script src="/common/utils.js"></script> +<script src="/resources/testdriver.js"></script> +<script src="/resources/testdriver-actions.js"></script> +<script src="/resources/testdriver-vendor.js"></script> +<title>Script to wait for instructions from RemoteContext.</title> + +<body> +<script> +window.addEventListener("load", async () => { + // Find the uuid to communicate with the parent. + const uuid = new URLSearchParams(window.location.search).get('uuid'); + + // Wait for the window to have its size computed and become visible, + // so that simulated user gestures will be handled properly. + while (window.innerWidth == 0) { + await new Promise(resolve => requestAnimationFrame(resolve)); + } + + // Create a RemoteContext Executor, which will wait in the background + // for scripts to execute. + window.executor = new Executor(uuid); +}); +</script> +</body> diff --git a/testing/web-platform/tests/fenced-frame/resources/report-url.html b/testing/web-platform/tests/fenced-frame/resources/report-url.html new file mode 100644 index 0000000000..e0b7d0982a --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/resources/report-url.html @@ -0,0 +1,7 @@ +<!DOCTYPE html> +<script src="utils.js"></script> +<title>A page embedded as a fenced frame that reports the document URL</title> +<script> +const [uuid] = parseKeylist(); +writeValueToServer(uuid, location.href); +</script> diff --git a/testing/web-platform/tests/fenced-frame/resources/report-url.html.headers b/testing/web-platform/tests/fenced-frame/resources/report-url.html.headers new file mode 100644 index 0000000000..6247f6d632 --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/resources/report-url.html.headers @@ -0,0 +1 @@ +Supports-Loading-Mode: fenced-frame
\ No newline at end of file diff --git a/testing/web-platform/tests/fenced-frame/resources/resize-lock-inner.html b/testing/web-platform/tests/fenced-frame/resources/resize-lock-inner.html new file mode 100644 index 0000000000..fbaf436330 --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/resources/resize-lock-inner.html @@ -0,0 +1,23 @@ +<!DOCTYPE html> + <script src="utils.js"></script> + <title>Fenced frame content to report any changes in inner dimensions</title> + + <body> + <script> + async function init() { + const [resize_lock_inner_page_is_ready_key, + resize_lock_resize_is_done_key, + resize_lock_report_inner_dimensions_key] = parseKeylist(); + + writeValueToServer(resize_lock_inner_page_is_ready_key, "ready"); + + await nextValueFromServer(resize_lock_resize_is_done_key); + + const response = window.innerWidth + "x" + window.innerHeight; + writeValueToServer(resize_lock_report_inner_dimensions_key, response); + } + + init(); + </script> + </body> +</html>
\ No newline at end of file diff --git a/testing/web-platform/tests/fenced-frame/resources/resize-lock-inner.html.headers b/testing/web-platform/tests/fenced-frame/resources/resize-lock-inner.html.headers new file mode 100644 index 0000000000..6247f6d632 --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/resources/resize-lock-inner.html.headers @@ -0,0 +1 @@ +Supports-Loading-Mode: fenced-frame
\ No newline at end of file diff --git a/testing/web-platform/tests/fenced-frame/resources/response-204.py b/testing/web-platform/tests/fenced-frame/resources/response-204.py new file mode 100644 index 0000000000..e6cf8d4ac9 --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/resources/response-204.py @@ -0,0 +1,4 @@ +def main(request, response): + response_headers = [] + body = "No content" + return (204, response_headers, body)
\ No newline at end of file diff --git a/testing/web-platform/tests/fenced-frame/resources/sandbox-mandatory-flags-iframe.sub.html b/testing/web-platform/tests/fenced-frame/resources/sandbox-mandatory-flags-iframe.sub.html new file mode 100644 index 0000000000..7ee8b7d98f --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/resources/sandbox-mandatory-flags-iframe.sub.html @@ -0,0 +1,13 @@ +<!DOCTYPE html> +<title>Iframe content to load a fenced frame and report a value to the server</title> +<script src="utils.js"></script> + +<body> +<script> + const fencedframe = document.createElement("fencedframe"); + fencedframe.config = new FencedFrameConfig( + generateURL("sandbox-mandatory-flags-inner.sub.html?key={{GET[key]}}" + + "&value={{GET[value]}}", [])); + document.body.appendChild(fencedframe); +</script> +</body> diff --git a/testing/web-platform/tests/fenced-frame/resources/sandbox-mandatory-flags-inner.sub.html b/testing/web-platform/tests/fenced-frame/resources/sandbox-mandatory-flags-inner.sub.html new file mode 100644 index 0000000000..5f400b5bde --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/resources/sandbox-mandatory-flags-inner.sub.html @@ -0,0 +1,6 @@ +<!DOCTYPE html> +<title>Fenced frame content to report a value to the server</title> + +<body> +<img src="key-value-store.py?key={{GET[key]}}&value={{GET[value]}}"> +</body> diff --git a/testing/web-platform/tests/fenced-frame/resources/sandbox-mandatory-flags-inner.sub.html.headers b/testing/web-platform/tests/fenced-frame/resources/sandbox-mandatory-flags-inner.sub.html.headers new file mode 100644 index 0000000000..1b63235b7c --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/resources/sandbox-mandatory-flags-inner.sub.html.headers @@ -0,0 +1 @@ +Supports-Loading-Mode: fenced-frame diff --git a/testing/web-platform/tests/fenced-frame/resources/sandbox-mandatory-flags-looser-restriction.sub.html b/testing/web-platform/tests/fenced-frame/resources/sandbox-mandatory-flags-looser-restriction.sub.html new file mode 100644 index 0000000000..0ad64c1a5c --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/resources/sandbox-mandatory-flags-looser-restriction.sub.html @@ -0,0 +1,14 @@ +<!DOCTYPE html> +<title>Iframe content to load a nested sandboxed iframe with all mandatory allow-* flags</title> + +<body> +<iframe + src="sandbox-mandatory-flags-iframe.sub.html?key={{GET[key]}}&value={{GET[value]}}" + sandbox="allow-same-origin + allow-forms + allow-scripts + allow-popups + allow-popups-to-escape-sandbox + allow-top-navigation-by-user-activation"> +</iframe> +</body> diff --git a/testing/web-platform/tests/fenced-frame/resources/sandboxed-features-inner.sub.html b/testing/web-platform/tests/fenced-frame/resources/sandboxed-features-inner.sub.html new file mode 100644 index 0000000000..f3bcbc8ba1 --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/resources/sandboxed-features-inner.sub.html @@ -0,0 +1,20 @@ +<!DOCTYPE html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testdriver.js"></script> +<script src="/resources/testdriver-actions.js"></script> +<script src="/resources/testdriver-vendor.js"></script> +<script src="utils.js"></script> +<script src="sandboxed-features.js"></script> +<body> +<script> +(async () => { + try { + await {{GET[test_func]}}(); + } catch (e) { + writeValueToServer('{{GET[key]}}', e.message); + return; + } + writeValueToServer('{{GET[key]}}', 'done'); +})() +</script> +</body>
\ No newline at end of file diff --git a/testing/web-platform/tests/fenced-frame/resources/sandboxed-features-inner.sub.html.headers b/testing/web-platform/tests/fenced-frame/resources/sandboxed-features-inner.sub.html.headers new file mode 100644 index 0000000000..6247f6d632 --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/resources/sandboxed-features-inner.sub.html.headers @@ -0,0 +1 @@ +Supports-Loading-Mode: fenced-frame
\ No newline at end of file diff --git a/testing/web-platform/tests/fenced-frame/resources/sandboxed-features-looser-restriction.sub.html b/testing/web-platform/tests/fenced-frame/resources/sandboxed-features-looser-restriction.sub.html new file mode 100644 index 0000000000..44584440e1 --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/resources/sandboxed-features-looser-restriction.sub.html @@ -0,0 +1,19 @@ +<!DOCTYPE html> +<body> +<iframe + src="sandboxed-features-inner.sub.html?key={{GET[key]}}&test_func={{GET[test_func]}}" + sandbox="allow-forms + allow-modals + allow-orientation-lock + allow-pointer-lock + allow-popups + allow-popups-to-escape-sandbox + allow-presentation + allow-same-origin + allow-scripts + allow-top-navigation + allow-top-navigation-by-user-activation + allow-downloads"> +</iframe> + +</body>
\ No newline at end of file diff --git a/testing/web-platform/tests/fenced-frame/resources/sandboxed-features-looser-restriction.sub.html.headers b/testing/web-platform/tests/fenced-frame/resources/sandboxed-features-looser-restriction.sub.html.headers new file mode 100644 index 0000000000..6247f6d632 --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/resources/sandboxed-features-looser-restriction.sub.html.headers @@ -0,0 +1 @@ +Supports-Loading-Mode: fenced-frame
\ No newline at end of file diff --git a/testing/web-platform/tests/fenced-frame/resources/sandboxed-features.js b/testing/web-platform/tests/fenced-frame/resources/sandboxed-features.js new file mode 100644 index 0000000000..1cbd4a48f3 --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/resources/sandboxed-features.js @@ -0,0 +1,126 @@ +const run_in_fenced_frame = (func_name, description, is_nested) => { + promise_test(async test => { + const key = token(); + const url = is_nested ? + 'resources/sandboxed-features-looser-restriction.sub.html?' : + 'resources/sandboxed-features-inner.sub.html?'; + let params = new URLSearchParams(); + params.set('key', key); + params.set('test_func', func_name); + const frame = document.createElement('fencedframe'); + const frame_url = 'resources/sandboxed-features-inner.sub.html?' + + params.toString(); + const config = new FencedFrameConfig(generateURL(frame_url, [])); + frame.config = config; + test.add_cleanup(() => { + frame.remove(); + }); + document.body.appendChild(frame); + assert_equals(await nextValueFromServer(key), 'done'); + }, description); +}; + +const run_sanboxed_feature_test = (func_name, description) => { + run_in_fenced_frame(func_name, description, false); + run_in_fenced_frame(func_name, description + '[looser sandboxed]', true); +}; + +async function test_prompt() { + assert_equals( + window.prompt('Test prompt'), + null, + 'window.prompt() must synchronously return null in a fenced frame without' + + ' blocking on user input.'); +} + +async function test_alert() { + assert_equals( + window.alert('Test alert'), + undefined, + 'window.alert() must synchronously return undefined in a fenced frame' + + ' without blocking on user input.'); +} + +async function test_confirm() { + assert_equals( + window.confirm('Test confirm'), + false, + 'window.confirm() must synchronously return false in a fenced frame' + + ' without blocking on user input.'); +} + +async function test_print() { + assert_equals( + window.print(), + undefined, + 'window.print() must synchronously return undefined in a fenced frame' + + ' without blocking on user input.'); + + assert_equals( + document.execCommand('print', false, null), + false, + 'execCommand(\'print\') must synchronously return false in a fenced frame' + + ' without blocking on user input.'); +} + +async function test_document_domain() { + assert_throws_dom('SecurityError', () => { + document.domain = 'example.test'; + }); + assert_throws_dom('SecurityError', () => { + document.domain = document.domain; + }); + assert_throws_dom('SecurityError', () => { + (new Document).domain = document.domain; + }); + assert_throws_dom('SecurityError', () => { + document.implementation.createHTMLDocument().domain = document.domain; + }); + assert_throws_dom('SecurityError', () => { + document.implementation.createDocument(null, '').domain = document.domain; + }); + assert_throws_dom('SecurityError', () => { + document.createElement('template').content.ownerDocument.domain = + document.domain; + }); +} + +async function test_presentation_request() { + assert_throws_dom('SecurityError', () => { + new PresentationRequest([location.href]); + }); +} + +async function test_screen_orientation_lock() { + try { + await screen.orientation.lock('portrait'); + } catch (e) { + assert_equals( + e.name, + 'SecurityError', + 'orientation.lock() must throw a SecurityError in a fenced frame.'); + return; + } + assert_unreached('orientation.lock() must throw an error'); +} + +async function test_pointer_lock() { + await simulateGesture(); + + const canvas = document.createElement('canvas'); + document.body.appendChild(canvas); + const pointerlockerror_promise = new Promise(resolve => { + document.addEventListener('pointerlockerror', resolve); + }); + try { + await canvas.requestPointerLock(); + } catch (e) { + assert_equals( + e.name, + 'SecurityError', + 'orientation.lock() must throws a SecurityError in a fenced frame.'); + await pointerlockerror_promise; + return; + } + assert_unreached('requestPointerLock() must fail in a fenced frame'); +} diff --git a/testing/web-platform/tests/fenced-frame/resources/serviceWorker-dedicated-worker-inner.html b/testing/web-platform/tests/fenced-frame/resources/serviceWorker-dedicated-worker-inner.html new file mode 100644 index 0000000000..02f28bd82e --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/resources/serviceWorker-dedicated-worker-inner.html @@ -0,0 +1,28 @@ +<!DOCTYPE html> +<script src="utils.js"></script> + +<body> + <script type="module"> + // Ask the worker to do a fetch request that will be handled by the service + // worker via postMessage. + const checkIfServiceWorkerCanControlWebWorker = async () => { + const dedicated_worker = new Worker('serviceWorker-dedicated-worker.js'); + return new Promise((resolve, reject) => { + dedicated_worker.addEventListener('message', e => { + resolve(e.data) + }); + dedicated_worker.postMessage('fetch'); + }) + } + + const [key] = parseKeylist(); + const url = new URL(location.href); + if (url.searchParams.get('useServiceWorkerInFencedFrame')) { + await navigator.serviceWorker.register('serviceWorker-dedicated-worker-sw.js'); + await navigator.serviceWorker.ready; + } + + const result = await checkIfServiceWorkerCanControlWebWorker(); + writeValueToServer(key, result); + </script> +</body> diff --git a/testing/web-platform/tests/fenced-frame/resources/serviceWorker-dedicated-worker-inner.html.headers b/testing/web-platform/tests/fenced-frame/resources/serviceWorker-dedicated-worker-inner.html.headers new file mode 100644 index 0000000000..6247f6d632 --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/resources/serviceWorker-dedicated-worker-inner.html.headers @@ -0,0 +1 @@ +Supports-Loading-Mode: fenced-frame
\ No newline at end of file diff --git a/testing/web-platform/tests/fenced-frame/resources/serviceWorker-dedicated-worker-sw.js b/testing/web-platform/tests/fenced-frame/resources/serviceWorker-dedicated-worker-sw.js new file mode 100644 index 0000000000..027995a218 --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/resources/serviceWorker-dedicated-worker-sw.js @@ -0,0 +1,18 @@ +self.addEventListener('fetch', async (e) => { + if (e.request.url.includes('fenced_frame_dedicated_worker_test')) { + e.respondWith(new Response('OK')); + return; + } + + e.respondWith(fetch(e.request).catch(() => { + return new Response('not found'); + })); +}) + +self.addEventListener('install', () => { + return self.skipWaiting(); +}); + +self.addEventListener('activate', () => { + return self.clients.claim(); +}); diff --git a/testing/web-platform/tests/fenced-frame/resources/serviceWorker-dedicated-worker-sw.js.headers b/testing/web-platform/tests/fenced-frame/resources/serviceWorker-dedicated-worker-sw.js.headers new file mode 100644 index 0000000000..d0b9633bb0 --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/resources/serviceWorker-dedicated-worker-sw.js.headers @@ -0,0 +1 @@ +Service-Worker-Allowed: / diff --git a/testing/web-platform/tests/fenced-frame/resources/serviceWorker-dedicated-worker.js b/testing/web-platform/tests/fenced-frame/resources/serviceWorker-dedicated-worker.js new file mode 100644 index 0000000000..8a9fa5ef36 --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/resources/serviceWorker-dedicated-worker.js @@ -0,0 +1,8 @@ +self.addEventListener('message', async (e) => { + if (e.data === 'fetch') { + // Send a request to non-existing URL but handled by SW. + const res = await fetch('./fenced_frame_dedicated_worker_test'); + const data = res.ok ? await res.text() : res.statusText; + self.postMessage(data); + } +}); diff --git a/testing/web-platform/tests/fenced-frame/resources/serviceWorker-frameType-inner.html b/testing/web-platform/tests/fenced-frame/resources/serviceWorker-frameType-inner.html new file mode 100644 index 0000000000..103236e52a --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/resources/serviceWorker-frameType-inner.html @@ -0,0 +1,37 @@ +<!DOCTYPE html> +<script src="utils.js"></script> +<body> +<script> + +function getFrameType(service_worker, url) { + return new Promise((resolve, reject) => { + const channel = new MessageChannel(); + channel.port1.onmessage = e => { + resolve(e.data); + }; + service_worker.postMessage({port:channel.port2, url:url}, + [channel.port2]); + }); +} + +(async function() { + await navigator.serviceWorker.register('serviceWorker-frameType.js'); + const registration = await navigator.serviceWorker.ready; + const service_worker = registration.active; + + const [frame_type_key, frame_type_ack_key] = parseKeylist(); + + const frame_type = await getFrameType(service_worker, location.href); + writeValueToServer(frame_type_key, frame_type); + + // Wait for ACK, so we know that the outer page has read the last value from + // the `serviceWorker.frameType` stash and we can write to it again. + await nextValueFromServer(frame_type_ack_key); + + const iframe = document.createElement('iframe'); + iframe.src = generateURL("serviceWorker-frameType-nested.html", + [frame_type_key]); + document.body.append(iframe); +})(); +</script> +</body>
\ No newline at end of file diff --git a/testing/web-platform/tests/fenced-frame/resources/serviceWorker-frameType-inner.html.headers b/testing/web-platform/tests/fenced-frame/resources/serviceWorker-frameType-inner.html.headers new file mode 100644 index 0000000000..6247f6d632 --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/resources/serviceWorker-frameType-inner.html.headers @@ -0,0 +1 @@ +Supports-Loading-Mode: fenced-frame
\ No newline at end of file diff --git a/testing/web-platform/tests/fenced-frame/resources/serviceWorker-frameType-nested.html b/testing/web-platform/tests/fenced-frame/resources/serviceWorker-frameType-nested.html new file mode 100644 index 0000000000..10bb7ff8bd --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/resources/serviceWorker-frameType-nested.html @@ -0,0 +1,25 @@ +<!DOCTYPE html> +<script src="utils.js"></script> +<body> +<script> + +function getFrameType(service_worker, url) { + return new Promise((resolve, reject) => { + const channel = new MessageChannel(); + channel.port1.onmessage = e => { + resolve(e.data); + }; + service_worker.postMessage({port:channel.port2, url:url}, + [channel.port2]); + }); +} + +(async function() { + const service_worker = navigator.serviceWorker.controller; + const frame_type = await getFrameType(service_worker, location.href); + + const [frame_type_key] = parseKeylist(); + writeValueToServer(frame_type_key, frame_type); +})(); +</script> +</body>
\ No newline at end of file diff --git a/testing/web-platform/tests/fenced-frame/resources/serviceWorker-frameType-nested.html.headers b/testing/web-platform/tests/fenced-frame/resources/serviceWorker-frameType-nested.html.headers new file mode 100644 index 0000000000..6247f6d632 --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/resources/serviceWorker-frameType-nested.html.headers @@ -0,0 +1 @@ +Supports-Loading-Mode: fenced-frame
\ No newline at end of file diff --git a/testing/web-platform/tests/fenced-frame/resources/serviceWorker-frameType.js b/testing/web-platform/tests/fenced-frame/resources/serviceWorker-frameType.js new file mode 100644 index 0000000000..91003fc131 --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/resources/serviceWorker-frameType.js @@ -0,0 +1,19 @@ +self.onmessage = function(e) { + var port = e.data.port; + var url = e.data.url; + + e.waitUntil(self.clients.matchAll({includeUncontrolled: true}) + .then(function(clients) { + var frame_type = "none"; + for (client of clients) { + if (client.url === url) { + frame_type = client.frameType; + break; + } + } + port.postMessage(frame_type); + }) + .catch(e => { + port.postMessage('clients.matchAll() rejected: ' + e); + })); +};
\ No newline at end of file diff --git a/testing/web-platform/tests/fenced-frame/resources/serviceWorker-navigate-inner-success.html b/testing/web-platform/tests/fenced-frame/resources/serviceWorker-navigate-inner-success.html new file mode 100644 index 0000000000..4d77d9e9a6 --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/resources/serviceWorker-navigate-inner-success.html @@ -0,0 +1,11 @@ +<!DOCTYPE html> +<script src="utils.js"></script> +<body> +<script> + +(async function() { + const [navigate_key] = parseKeylist(); + writeValueToServer(navigate_key, 'success'); +})(); +</script> +</body> diff --git a/testing/web-platform/tests/fenced-frame/resources/serviceWorker-navigate-inner-success.html.headers b/testing/web-platform/tests/fenced-frame/resources/serviceWorker-navigate-inner-success.html.headers new file mode 100644 index 0000000000..6247f6d632 --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/resources/serviceWorker-navigate-inner-success.html.headers @@ -0,0 +1 @@ +Supports-Loading-Mode: fenced-frame
\ No newline at end of file diff --git a/testing/web-platform/tests/fenced-frame/resources/serviceWorker-navigate-inner.html b/testing/web-platform/tests/fenced-frame/resources/serviceWorker-navigate-inner.html new file mode 100644 index 0000000000..aaf330f4f6 --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/resources/serviceWorker-navigate-inner.html @@ -0,0 +1,16 @@ +<!DOCTYPE html> +<script src="utils.js"></script> +<body> +<script> + +(async function() { + await navigator.serviceWorker.register('serviceWorker-navigate.js'); + const registration = await navigator.serviceWorker.ready; + const service_worker = registration.active; + + const [navigate_key] = parseKeylist(); + + service_worker.postMessage({key:navigate_key, url:location.href}); +})(); +</script> +</body> diff --git a/testing/web-platform/tests/fenced-frame/resources/serviceWorker-navigate-inner.html.headers b/testing/web-platform/tests/fenced-frame/resources/serviceWorker-navigate-inner.html.headers new file mode 100644 index 0000000000..6247f6d632 --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/resources/serviceWorker-navigate-inner.html.headers @@ -0,0 +1 @@ +Supports-Loading-Mode: fenced-frame
\ No newline at end of file diff --git a/testing/web-platform/tests/fenced-frame/resources/serviceWorker-navigate.js b/testing/web-platform/tests/fenced-frame/resources/serviceWorker-navigate.js new file mode 100644 index 0000000000..a7a4db52ee --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/resources/serviceWorker-navigate.js @@ -0,0 +1,18 @@ +self.importScripts('utils.js'); + +self.onmessage = function(e) { + var key = e.data.key; + var url = e.data.url; + + e.waitUntil(self.clients.claim().then(() => { + return self.clients.matchAll({type: 'window'}); + }).then(clients => { + return clients.map(client => { + // Check to make sure WindowClient.navigate() is supported. + if (client.url === url) { + return client.navigate(generateURL('serviceWorker-navigate-inner-success.html', + [key])); + } + }); + })); +}; diff --git a/testing/web-platform/tests/fenced-frame/resources/serviceWorker-push-sw.js b/testing/web-platform/tests/fenced-frame/resources/serviceWorker-push-sw.js new file mode 100644 index 0000000000..e344b45fd8 --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/resources/serviceWorker-push-sw.js @@ -0,0 +1,19 @@ +self.addEventListener('install', e => e.waitUntil(skipWaiting())); +self.addEventListener('activate', e => e.waitUntil(clients.claim())); + +self.addEventListener('message', async e => { + const method = e.data; + + const promise = method === 'subscribe' ? + self.registration.pushManager.subscribe({userVisibleOnly: true}) : + Promise.resolve(); + const message = await promise + .then(() => { + return `${method}: Unexpectedly started`; + }) + .catch((e) => { + return e.message; + }); + + e.source.postMessage(message); +}); diff --git a/testing/web-platform/tests/fenced-frame/resources/unique-cookie-partition-inner.https.html b/testing/web-platform/tests/fenced-frame/resources/unique-cookie-partition-inner.https.html new file mode 100644 index 0000000000..1cf3fc8680 --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/resources/unique-cookie-partition-inner.https.html @@ -0,0 +1,42 @@ +<!DOCTYPE html> +<script src="/resources/testharness.js"></script> +<script src="utils.js"></script> +<title>Fenced frame accessing cookies</title> + +<body> +<script> +async function init() { + // This file is meant to run in a <fencedframe>. It sets up multiple frames + // in the following arrangements: + // 1.) A top-level fenced frame + // 2.) An iframe within a fenced frame + // 3.) A nested fenced frame + + // Set cookies in the root fenced frame via document and cookieStore APIs. + const [cookie_value_key, test_type] = parseKeylist(); + document.cookie = 'C=fenced; SameSite=Lax'; + document.cookie = 'D=fenced; SameSite=None; Secure'; + await cookieStore.set('E', 'fenced'); + + const cookie_access_url = generateURL("cookie-access.https.html", + [cookie_value_key]); + + switch (test_type) { + case "top-level fenced frame": + const cookie_value = document.cookie; + writeValueToServer(cookie_value_key, cookie_value); + break; + case "nested iframe": + const iframe = document.createElement('iframe'); + document.body.append(iframe); + iframe.src = cookie_access_url; + break; + case "nested fenced frame": + const ff = attachFencedFrame(cookie_access_url); + break; + } +} + +init(); +</script> +</body> diff --git a/testing/web-platform/tests/fenced-frame/resources/unique-cookie-partition-inner.https.html.headers b/testing/web-platform/tests/fenced-frame/resources/unique-cookie-partition-inner.https.html.headers new file mode 100644 index 0000000000..e2b453f463 --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/resources/unique-cookie-partition-inner.https.html.headers @@ -0,0 +1,2 @@ +Supports-Loading-Mode: fenced-frame +Set-Cookie: F=fenced; SameSite=Lax diff --git a/testing/web-platform/tests/fenced-frame/resources/unreached.https.html b/testing/web-platform/tests/fenced-frame/resources/unreached.https.html new file mode 100644 index 0000000000..bd389ec4fb --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/resources/unreached.https.html @@ -0,0 +1,15 @@ +<!DOCTYPE html> +<title>File used to assert that navigations do not succeed.</title> + +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="utils.js"></script> +<script src="/common/get-host-info.sub.js"></script> + +<body> +<script> + promise_test(async(t) => { + assert_unreached('This navigation should not have succeeded.'); + }); +</script> +</body> diff --git a/testing/web-platform/tests/fenced-frame/resources/utils.js b/testing/web-platform/tests/fenced-frame/resources/utils.js new file mode 100644 index 0000000000..cbea173f17 --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/resources/utils.js @@ -0,0 +1,648 @@ +const STORE_URL = '/fenced-frame/resources/key-value-store.py'; +const BEACON_URL = '/fenced-frame/resources/automatic-beacon-store.py'; +const REMOTE_EXECUTOR_URL = '/fenced-frame/resources/remote-context-executor.https.html'; + +// If your test needs to modify FLEDGE bidding or decision logic, you should +// update the generated JS in the corresponding handler below. +const FLEDGE_BIDDING_URL = '/fenced-frame/resources/fledge-bidding-logic.py'; +const FLEDGE_DECISION_URL = '/fenced-frame/resources/fledge-decision-logic.py'; + +// Creates a URL that includes a list of stash key UUIDs that are being used +// in the test. This allows us to generate UUIDs on the fly and let anything +// (iframes, fenced frames, pop-ups, etc...) that wouldn't have access to the +// original UUID variable know what the UUIDs are. +// @param {string} href - The base url of the page being navigated to +// @param {string list} keylist - The list of key UUIDs to be used. Note that +// order matters when extracting the keys +function generateURL(href, keylist) { + const ret_url = new URL(href, location.href); + ret_url.searchParams.append("keylist", keylist.join(',')); + return ret_url; +} + +function getRemoteContextURL(origin) { + return new URL(REMOTE_EXECUTOR_URL, origin); +} + +async function runSelectRawURL(href, resolve_to_config = false) { + try { + await sharedStorage.worklet.addModule( + "/shared-storage/resources/simple-module.js"); + } catch (e) { + // Shared Storage needs to have a module added before we can operate on it. + // It is generated on the fly with this call, and since there's no way to + // tell through the API if a module already exists, wrap the addModule call + // in a try/catch so that if it runs a second time in a test, it will + // gracefully fail rather than bring the whole test down. + } + return await sharedStorage.selectURL( + 'test-url-selection-operation', [{url: href, + reportingMetadata: { + 'reserved.top_navigation_start': BEACON_URL + + "?type=reserved.top_navigation_start", + 'reserved.top_navigation_commit': BEACON_URL + + "?type=reserved.top_navigation_commit", + }}], { + data: {'mockResult': 0}, + resolveToConfig: resolve_to_config, + keepAlive: true, + }); +} + +// Similar to generateURL, but creates +// 1. An urn:uuid if `resolve_to_config` is false. +// 2. A fenced frame config object if `resolve_to_config` is true. +// This relies on a mock Shared Storage auction, since it is the simplest +// WP-exposed way to turn a url into an urn:uuid or a fenced frame config. +// Note: this function, unlike generateURL, is asynchronous and needs to be +// called with an await operator. +// @param {string} href - The base url of the page being navigated to +// @param {string list} keylist - The list of key UUIDs to be used. Note that +// order matters when extracting the keys +// @param {boolean} [resolve_to_config = false] - Determines whether the result +// of `sharedStorage.selectURL()` +// is an urn:uuid or a fenced +// frame config. +// Note: +// 1. There is a limit of 3 calls per origin per pageload for +// `sharedStorage.selectURL()`, so `runSelectURL()` must also respect this +// limit. +// 2. If `resolve_to_config` is true, blink feature `FencedFramesAPIChanges` +// needs to be enabled for `selectURL()` to return a fenced frame config. +// Otherwise `selectURL()` will fall back to the old behavior that returns an +// urn:uuid. +async function runSelectURL(href, keylist = [], resolve_to_config = false) { + const full_url = generateURL(href, keylist); + return await runSelectRawURL(full_url, resolve_to_config); +} + +async function generateURNFromFledgeRawURL( + href, nested_urls, resolve_to_config = false, ad_with_size = false, + requested_size = null, automatic_beacon = false) { + const bidding_token = token(); + const seller_token = token(); + + const ad_components_list = nested_urls.map((url) => { + return ad_with_size ? + { renderURL: url, sizeGroup: "group1" } : + { renderURL: url } + }); + + let interestGroup = + { + name: 'testAd1', + owner: location.origin, + biddingLogicURL: new URL(FLEDGE_BIDDING_URL, location.origin), + ads: [{renderURL: href, bid: 1}], + userBiddingSignals: {biddingToken: bidding_token}, + trustedBiddingSignalsKeys: ['key1'], + adComponents: ad_components_list, + }; + + let biddingURLParams = + new URLSearchParams(interestGroup.biddingLogicURL.search); + if (requested_size) + biddingURLParams.set( + 'requested-size', requested_size[0] + '-' + requested_size[1]); + if (ad_with_size) + biddingURLParams.set('ad-with-size', 1); + if (automatic_beacon) + biddingURLParams.set('automatic-beacon', 1); + interestGroup.biddingLogicURL.search = biddingURLParams; + + if (ad_with_size) { + interestGroup.ads[0].sizeGroup = 'group1'; + interestGroup.adSizes = {'size1': {width: '100px', height: '50px'}}; + interestGroup.sizeGroups = {'group1': ['size1']}; + } + + // Pick an arbitrarily high duration to guarantee that we never leave the + // ad interest group while the test runs. + navigator.joinAdInterestGroup(interestGroup, /*durationSeconds=*/3000000); + + let auctionConfig = { + seller: location.origin, + interestGroupBuyers: [location.origin], + decisionLogicURL: new URL(FLEDGE_DECISION_URL, location.origin), + auctionSignals: {biddingToken: bidding_token, sellerToken: seller_token}, + resolveToConfig: resolve_to_config + }; + + if (requested_size) { + let decisionURLParams = + new URLSearchParams(auctionConfig.decisionLogicURL.search); + decisionURLParams.set( + 'requested-size', requested_size[0] + '-' + requested_size[1]); + auctionConfig.decisionLogicURL.search = decisionURLParams; + + auctionConfig['requestedSize'] = {width: requested_size[0], height: requested_size[1]}; + } + + return navigator.runAdAuction(auctionConfig); +} + +// Similar to runSelectURL, but uses FLEDGE instead of Shared Storage as the +// auctioning tool. +// Note: this function, unlike generateURL, is asynchronous and needs to be +// called with an await operator. @param {string} href - The base url of the +// page being navigated to @param {string list} keylist - The list of key UUIDs +// to be used. Note that order matters when extracting the keys +// @param {string} href - The base url of the page being navigated to +// @param {string list} keylist - The list of key UUIDs to be used. Note that +// order matters when extracting the keys +// @param {string list} nested_urls - A list of urls that will eventually become +// the nested configs/ad components +// @param {boolean} [resolve_to_config = false] - Determines whether the result +// of `navigator.runAdAuction()` +// is an urn:uuid or a fenced +// frame config. +// @param {boolean} [ad_with_size = false] - Determines whether the auction is +// run with ad sizes specified. +// @param {boolean} [automatic_beacon = false] - If true, FLEDGE logic will +// register an automatic beacon +// after completion. +async function generateURNFromFledge( + href, keylist, nested_urls = [], resolve_to_config = false, + ad_with_size = false, requested_size = null, automatic_beacon = false) { + const full_url = generateURL(href, keylist); + return generateURNFromFledgeRawURL( + full_url, nested_urls, resolve_to_config, ad_with_size, requested_size, + automatic_beacon); +} + +// Extracts a list of UUIDs from the from the current page's URL. +// @returns {string list} - The list of UUIDs extracted from the page. This can +// be read into multiple variables using the +// [key1, key2, etc...] = parseKeyList(); pattern. +function parseKeylist() { + const url = new URL(location.href); + const keylist = url.searchParams.get("keylist"); + return keylist.split(','); +} + +// Converts a same-origin URL to a cross-origin URL +// @param {URL} url - The URL object whose origin is being converted +// @param {boolean} [https=true] - Whether or not to use the HTTPS origin +// +// @returns {URL} The new cross-origin URL +function getRemoteOriginURL(url, https=true) { + const same_origin = location.origin; + const cross_origin = https ? get_host_info().HTTPS_REMOTE_ORIGIN + : get_host_info().HTTP_REMOTE_ORIGIN; + return new URL(url.toString().replace(same_origin, cross_origin)); +} + +// Builds a URL to be used as a remote context executor. +function generateRemoteContextURL(headers, origin) { + // Generate the unique id for the parent/child channel. + const uuid = token(); + + // Use the absolute path of the remote context executor source file, so that + // nested contexts will work. + const url = getRemoteContextURL(origin ? origin : location.origin); + url.searchParams.append('uuid', uuid); + + // Add the header to allow loading in a fenced frame. + headers.push(["Supports-Loading-Mode", "fenced-frame"]); + + // Transform the headers into the expected format. + // https://web-platform-tests.org/writing-tests/server-pipes.html#headers + function escape(s) { + return s.replace('(', '\\(').replace(')', '\\)'); + } + const formatted_headers = headers.map((header) => { + return `header(${escape(header[0])}, ${escape(header[1])})`; + }); + url.searchParams.append('pipe', formatted_headers.join('|')); + + return [uuid, url]; +} + +function buildRemoteContextForObject(object, uuid, html) { + // https://github.com/web-platform-tests/wpt/blob/master/common/dispatcher/README.md + const context = new RemoteContext(uuid); + if (html) { + context.execute_script( + (html_source) => { + document.body.insertAdjacentHTML('beforebegin', html_source); + }, + [html]); + } + + // We need a little bit of boilerplate in the handlers because Proxy doesn't + // work so nicely with HTML elements. + const handler = { + get: (target, key) => { + if (key == "execute") { + return context.execute_script; + } + if (key == "element") { + return object; + } + if (key in target) { + return target[key]; + } + return context[key]; + }, + set: (target, key, value) => { + target[key] = value; + return value; + } + }; + + const proxy = new Proxy(object, handler); + return proxy; +} + +// Attaches an object that waits for scripts to execute from RemoteContext. +// (In practice, this is either a frame or a window.) +// Returns a proxy for the object that first resolves to the object itself, +// then resolves to the RemoteContext if the property isn't found. +// The proxy also has an extra attribute `execute`, which is an alias for the +// remote context's `execute_script(fn, args=[])`. +function attachContext(object_constructor, html, headers, origin) { + const [uuid, url] = generateRemoteContextURL(headers, origin); + const object = object_constructor(url); + return buildRemoteContextForObject(object, uuid, html); +} + +// TODO(crbug.com/1347953): Update this function to also test +// `sharedStorage.selectURL()` that returns a fenced frame config object. +// This should be done after fixing the following flaky tests that use this +// function. +// 1. crbug.com/1372536: resize-lock-input.https.html +// 2. crbug.com/1394559: unfenced-top.https.html +async function attachOpaqueContext( + generator_api, resolve_to_config, ad_with_size, requested_size, + automatic_beacon, object_constructor, html, headers, origin, + num_components) { + const [uuid, url] = generateRemoteContextURL(headers, origin); + + let components_list = []; + for (let i = 0; i < num_components; i++) { + let [component_uuid, component_url] = + generateRemoteContextURL(headers, origin); + // This field will be read by attachComponentFrameContext() in order to + // know what uuid to point to when building the remote context. + html += '<input type=\'hidden\' id=\'component_uuid_' + i + '\' value=\'' + + component_uuid + '\'>'; + components_list.push(component_url); + } + + const id = await ( + generator_api == 'fledge' ? + generateURNFromFledge( + url, [], components_list, resolve_to_config, ad_with_size, + requested_size, automatic_beacon) : + runSelectURL(url, [], resolve_to_config)); + const object = object_constructor(id); + return buildRemoteContextForObject(object, uuid, html); +} + +function attachPotentiallyOpaqueContext( + generator_api, resolve_to_config, ad_with_size, requested_size, + automatic_beacon, frame_constructor, html, headers, origin, + num_components) { + generator_api = generator_api.toLowerCase(); + if (generator_api == 'fledge' || generator_api == 'sharedstorage') { + return attachOpaqueContext( + generator_api, resolve_to_config, ad_with_size, requested_size, + automatic_beacon, frame_constructor, html, headers, origin, + num_components); + } else { + return attachContext(frame_constructor, html, headers, origin); + } +} + +function attachFrameContext( + element_name, generator_api, resolve_to_config, ad_with_size, + requested_size, automatic_beacon, html, headers, attributes, origin, + num_components) { + frame_constructor = (id) => { + frame = document.createElement(element_name); + attributes.forEach(attribute => { + frame.setAttribute(attribute[0], attribute[1]); + }); + if (element_name == "iframe") { + frame.src = id; + } else if (id instanceof FencedFrameConfig) { + frame.config = id; + } else { + const config = new FencedFrameConfig(id); + frame.config = config; + } + document.body.append(frame); + return frame; + }; + return attachPotentiallyOpaqueContext( + generator_api, resolve_to_config, ad_with_size, requested_size, + automatic_beacon, frame_constructor, html, headers, origin, + num_components); +} + +function replaceFrameContext(frame_proxy, { + generator_api = '', + resolve_to_config = false, + ad_with_size = false, + requested_size = null, + automatic_beacon = false, + html = '', + headers = [], + origin = '' +} = {}) { + frame_constructor = (id) => { + if (frame_proxy.element.nodeName == "IFRAME") { + frame_proxy.element.src = id; + } else if (id instanceof FencedFrameConfig) { + frame_proxy.element.config = id; + } else { + const config = new FencedFrameConfig(id); + frame_proxy.element.config = config; + } + return frame_proxy.element; + }; + return attachPotentiallyOpaqueContext( + generator_api, resolve_to_config, ad_with_size, requested_size, + automatic_beacon, frame_constructor, html, headers, origin); +} + +// Attach a fenced frame that waits for scripts to execute. +// Takes as input a(n optional) dictionary of configs: +// - generator_api: the name of the API that should generate the urn/config. +// Supports (case-insensitive) "fledge" and "sharedstorage", or any other +// value as a default. +// If you generate a urn, then you need to await the result of this function. +// - resolve_to_config: whether a config should be used. (currently only works +// for FLEDGE and sharedStorage generator_api) +// - ad_with_size: whether an ad auction is run with size specified for the ads +// and ad components. (currently only works for FLEDGE) +// - requested_size: A 2-element list with the width and height for +// requestedSize in the FLEDGE auction config. This is different from +// ad_with_size, which refers to size information provided alongside the ads +// themselves. +// - automatic_beacon: If true and generator_api = "fledge", an automatic beacon +// will be registered for a top-level navigation after the FLEDGE auction +// completes. +// - html: extra HTML source code to inject into the loaded frame +// - headers: an array of header pairs [[key, value], ...] +// - attributes: an array of attribute pairs to set on the frame [[key, value], +// ...] +// - origin: origin of the url, default to location.origin if not set +// Returns a proxy that acts like the frame HTML element, but with an extra +// function `execute`. See `attachFrameContext` or the README for more details. +function attachFencedFrameContext({ + generator_api = '', + resolve_to_config = false, + ad_with_size = false, + requested_size = null, + automatic_beacon = false, + html = '', + headers = [], + attributes = [], + origin = '', + num_components = 0 +} = {}) { + return attachFrameContext( + 'fencedframe', generator_api, resolve_to_config, ad_with_size, + requested_size, automatic_beacon, html, headers, attributes, origin, + num_components); +} + +// Attach an iframe that waits for scripts to execute. +// See `attachFencedFrameContext` for more details. +function attachIFrameContext({ + generator_api = '', + automatic_beacon = false, + html = '', + headers = [], + attributes = [], + origin = '', + num_components = 0 +} = {}) { + return attachFrameContext( + 'iframe', generator_api, resolve_to_config = false, ad_with_size = false, + requested_size = null, automatic_beacon, html, headers, attributes, + origin, num_components); +} + +// Open a window that waits for scripts to execute. +// Returns a proxy that acts like the window object, but with an extra +// function `execute`. See `attachContext` for more details. +function attachWindowContext({target="_blank", html="", headers=[], origin=""}={}) { + window_constructor = (url) => { + return window.open(url, target); + } + + return attachContext(window_constructor, html, headers, origin); +} + +// Attaches an ad component in a fenced frame. For this to work, this must be +// called in a frame that was generated with attachFrameContext() using the +// Protected Audience API (generator_api: 'fledge'). +function attachComponentFencedFrameContext( + index = 0, {attributes = [], html = ''} = {}) { + const urn = window.fence.getNestedConfigs()[index]; + return attachComponentFrameContext( + index, 'fencedframe', urn, attributes, html); +} + +// Same as attachComponentFencedFrameContext, but in a urn iframe. +function attachComponentIFrameContext( + index = 0, {attributes = [], html = ''} = {}) { + const urn = navigator.adAuctionComponents(index + 1)[index]; + return attachComponentFrameContext(index, 'iframe', urn, attributes, html); +} + +function attachComponentFrameContext( + index, element_name, urn, attributes, html) { + assert_not_equals( + document.getElementById('component_uuid_' + index), null, + 'Component frames can only be attached to frames loaded with ' + + 'attach*FrameContext() with `num_components` set to at least ' + + (index + 1) + '.'); + + let frame = document.createElement(element_name); + attributes.forEach(attribute => { + frame.setAttribute(attribute[0], attribute[1]); + }); + if (element_name == 'iframe') { + frame.src = urn; + } else { + frame.config = urn; + } + document.body.append(frame); + const context_uuid = document.getElementById('component_uuid_' + index).value; + return buildRemoteContextForObject(frame, context_uuid, html); +} + +// Converts a key string into a key uuid using a cryptographic hash function. +// This function only works in secure contexts (HTTPS). +async function stringToStashKey(string) { + // Compute a SHA-256 hash of the input string, and convert it to hex. + const data = new TextEncoder().encode(string); + const digest = await crypto.subtle.digest('SHA-256', data); + const digest_array = Array.from(new Uint8Array(digest)); + const digest_as_hex = digest_array.map(b => b.toString(16).padStart(2, '0')).join(''); + + // UUIDs are structured as 8X-4X-4X-4X-12X. + // Use the first 32 hex digits and ignore the rest. + const digest_slices = [digest_as_hex.slice(0,8), + digest_as_hex.slice(8,12), + digest_as_hex.slice(12,16), + digest_as_hex.slice(16,20), + digest_as_hex.slice(20,32)]; + return digest_slices.join('-'); +} + +// Create a fenced frame. Then navigate it using the given `target`, which can +// be either an urn:uuid or a fenced frame config object. +function attachFencedFrame(target) { + assert_implements( + window.HTMLFencedFrameElement, + 'The HTMLFencedFrameElement should be exposed on the window object'); + + const fenced_frame = document.createElement('fencedframe'); + + if (target instanceof FencedFrameConfig) { + fenced_frame.config = target; + } else { + const config = new FencedFrameConfig(target); + fenced_frame.config = config; + } + + document.body.append(fenced_frame); + return fenced_frame; +} + +function attachIFrame(url) { + const iframe = document.createElement('iframe'); + iframe.src = url; + document.body.append(iframe); + return iframe; +} + +// Reads the value specified by `key` from the key-value store on the server. +async function readValueFromServer(key) { + // Resolve the key if it is a Promise. + key = await key; + + const serverURL = `${STORE_URL}?key=${key}`; + const response = await fetch(serverURL); + if (!response.ok) + throw new Error('An error happened in the server'); + const value = await response.text(); + + // The value is not stored in the server. + if (value === "<Not set>") + return { status: false }; + + return { status: true, value: value }; +} + +// Convenience wrapper around the above getter that will wait until a value is +// available on the server. +async function nextValueFromServer(key) { + // Resolve the key if it is a Promise. + key = await key; + + while (true) { + // Fetches the test result from the server. + const { status, value } = await readValueFromServer(key); + if (!status) { + // The test result has not been stored yet. Retry after a while. + await new Promise(resolve => setTimeout(resolve, 20)); + continue; + } + + return value; + } +} + +// Checks the automatic beacon data server to see if it has received an +// automatic beacon with a given event type and body. +async function readAutomaticBeaconDataFromServer(event_type, expected_body) { + let serverURL = `${BEACON_URL}`; + const response = await fetch(serverURL + "?" + new URLSearchParams({ + type: event_type, + expected_body: expected_body, + })); + if (!response.ok) + throw new Error('An error happened in the server ' + response.status); + const value = await response.text(); + + // The value is not stored in the server. + if (value === "<Not set>") + return { status: false }; + + return { status: true, value: value }; +} + +// Convenience wrapper around the above getter that will wait until a value is +// available on the server. The server uses a hash of the concatenated event +// type and beacon data as the key when storing the beacon in the database. To +// retrieve it, we need to supply the endpoint with both pieces of information. +async function nextAutomaticBeacon(event_type, expected_body) { + while (true) { + // Fetches the test result from the server. + const { status, value } = + await readAutomaticBeaconDataFromServer(event_type, expected_body); + if (!status) { + // The test result has not been stored yet. Retry after a while. + await new Promise(resolve => setTimeout(resolve, 20)); + continue; + } + + return value; + } +} + +// Writes `value` for `key` in the key-value store on the server. +async function writeValueToServer(key, value, origin = '') { + // Resolve the key if it is a Promise. + key = await key; + + const serverURL = `${origin}${STORE_URL}?key=${key}&value=${value}`; + await fetch(serverURL, {"mode": "no-cors"}); +} + +// Simulates a user gesture. +async function simulateGesture() { + // Wait until the window size is initialized. + while (window.innerWidth == 0) { + await new Promise(resolve => requestAnimationFrame(resolve)); + } + await test_driver.bless('simulate gesture'); +} + +// Fenced frames are always put in the public IP address space which is the +// least privileged. In case a navigation to a local data: URL or blob: URL +// resource is allowed, they would only be able to fetch things that are *also* +// in the public IP address space. So for the document described by these local +// URLs, we'll set them up to only communicate back to the outer page via +// resources obtained in the public address space. +function createLocalSource(key, url) { + return ` + <head> + <script src="${url}"><\/script> + </head> + <body> + <script> + writeValueToServer("${key}", "LOADED", /*origin=*/"${url.origin}"); + <\/script> + </body> + `; +} + +function setupCSP(csp, second_csp=null) { + let meta = document.createElement('meta'); + meta.httpEquiv = "Content-Security-Policy"; + meta.content = "fenced-frame-src " + csp; + document.head.appendChild(meta); + + if (second_csp != null) { + let second_meta = document.createElement('meta'); + second_meta.httpEquiv = "Content-Security-Policy"; + second_meta.content = "frame-src " + second_csp; + document.head.appendChild(second_meta); + } +} diff --git a/testing/web-platform/tests/fenced-frame/resources/web-bluetooth-inner.html b/testing/web-platform/tests/fenced-frame/resources/web-bluetooth-inner.html new file mode 100644 index 0000000000..3236886b97 --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/resources/web-bluetooth-inner.html @@ -0,0 +1,33 @@ +<!DOCTYPE html> +<script src="/resources/testdriver.js"></script> +<script src="/resources/testdriver-actions.js"></script> +<script src="/resources/testdriver-vendor.js"></script> +<script src="utils.js"></script> +<title>Fenced frame content to test Web Bluetooth</title> + +<body> +<button id="button">Button</button> +<script> +(async () => { + await simulateGesture(); + const [bluetooth_request_device_key] = parseKeylist(); + try { + await navigator.bluetooth.requestDevice({filters: [{name: 'device'}]}); + writeValueToServer(bluetooth_request_device_key, + 'Web Bluetooth requestDevice() succeeded'); + } catch(e) { + if (e.name == 'NotAllowedError' && + e.message.includes( + 'Web Bluetooth is not allowed in a fenced frame tree.')) { + writeValueToServer(bluetooth_request_device_key, + 'Web Bluetooth requestDevice() failed'); + } else { + writeValueToServer( + bluetooth_request_device_key, + 'Web Bluetooth requestDevice() failed with unknown error - ' + + `${e.name}: ${e.message}`); + } + } +})(); +</script> +</body> diff --git a/testing/web-platform/tests/fenced-frame/resources/web-bluetooth-inner.html.headers b/testing/web-platform/tests/fenced-frame/resources/web-bluetooth-inner.html.headers new file mode 100644 index 0000000000..1b63235b7c --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/resources/web-bluetooth-inner.html.headers @@ -0,0 +1 @@ +Supports-Loading-Mode: fenced-frame diff --git a/testing/web-platform/tests/fenced-frame/resources/web-nfc-inner.https.html b/testing/web-platform/tests/fenced-frame/resources/web-nfc-inner.https.html new file mode 100644 index 0000000000..682805d5d2 --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/resources/web-nfc-inner.https.html @@ -0,0 +1,23 @@ +<!DOCTYPE html> +<script src="utils.js"></script> +<title>Fenced frame content to report the value of Web NFC API</title> + +<body> +<script> +async function init() { + const [ndef_write_key, ndef_scan_key] = parseKeylist(); + + const ndef = new NDEFReader(); + ndef.write("Hello").then( + () => { writeValueToServer(ndef_write_key, "resolved"); }, + () => { writeValueToServer(ndef_write_key, "rejected"); } + ); + ndef.scan().then( + () => { writeValueToServer(ndef_scan_key, "resolved"); }, + () => { writeValueToServer(ndef_scan_key, "rejected"); } + ); +} + +init(); +</script> +</body> diff --git a/testing/web-platform/tests/fenced-frame/resources/web-nfc-inner.https.html.headers b/testing/web-platform/tests/fenced-frame/resources/web-nfc-inner.https.html.headers new file mode 100644 index 0000000000..1b63235b7c --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/resources/web-nfc-inner.https.html.headers @@ -0,0 +1 @@ +Supports-Loading-Mode: fenced-frame diff --git a/testing/web-platform/tests/fenced-frame/resources/web-share-inner.html b/testing/web-platform/tests/fenced-frame/resources/web-share-inner.html new file mode 100644 index 0000000000..aada6f04e1 --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/resources/web-share-inner.html @@ -0,0 +1,21 @@ +<!DOCTYPE html> +<script src="/resources/testdriver.js"></script> +<script src="/resources/testdriver-actions.js"></script> +<script src="/resources/testdriver-vendor.js"></script> +<script src="utils.js"></script> +<title>Fenced frame content to test Web Share</title> + +<body> +<script> +(async () => { + await simulateGesture(); + const [navigator_share_key] = parseKeylist(); + try { + await navigator.share({text: 'hello world'}); + writeValueToServer(navigator_share_key, 'Web Share succeeded'); + } catch(error) { + writeValueToServer(navigator_share_key, 'Web Share failed'); + } +})(); +</script> +</body> diff --git a/testing/web-platform/tests/fenced-frame/resources/web-share-inner.html.headers b/testing/web-platform/tests/fenced-frame/resources/web-share-inner.html.headers new file mode 100644 index 0000000000..1b63235b7c --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/resources/web-share-inner.html.headers @@ -0,0 +1 @@ +Supports-Loading-Mode: fenced-frame diff --git a/testing/web-platform/tests/fenced-frame/resources/window-frameElement-inner.html b/testing/web-platform/tests/fenced-frame/resources/window-frameElement-inner.html new file mode 100644 index 0000000000..897d9a0d59 --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/resources/window-frameElement-inner.html @@ -0,0 +1,14 @@ +<!DOCTYPE html> +<script src="utils.js"></script> +<title>Fenced frame content to report the value of window.frameElement</title> + +<body> +<script> +(async () => { + // Report whether or not `window.frameElement` is null + const [frame_element_key] = parseKeylist(); + let result = (window.frameElement == null) ? "PASS" : "FAIL"; + writeValueToServer(frame_element_key, result); +})(); +</script> +</body> diff --git a/testing/web-platform/tests/fenced-frame/resources/window-frameElement-inner.html.headers b/testing/web-platform/tests/fenced-frame/resources/window-frameElement-inner.html.headers new file mode 100644 index 0000000000..6247f6d632 --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/resources/window-frameElement-inner.html.headers @@ -0,0 +1 @@ +Supports-Loading-Mode: fenced-frame
\ No newline at end of file diff --git a/testing/web-platform/tests/fenced-frame/resources/window-navigation-204-inner.html b/testing/web-platform/tests/fenced-frame/resources/window-navigation-204-inner.html new file mode 100644 index 0000000000..e5e5adef1d --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/resources/window-navigation-204-inner.html @@ -0,0 +1,23 @@ +<!DOCTYPE html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="utils.js"></script> +<title>Fenced frame content to report the value of window.navigation</title> + +<body> +<script> +function init() { + // This file is meant to be navigated to from a <fencedframe> element. It + // reports back to the page hosting the <fencedframe> after manual timeout + // indicating that the 204 navigation succeeds without navigating away. + location.href = "response-204.py"; + + step_timeout(function() { + const [window_data_key] = parseKeylist(); + writeValueToServer(window_data_key, "still in page"); + }, 1000); +} + +init(); +</script> +</body> diff --git a/testing/web-platform/tests/fenced-frame/resources/window-navigation-204-inner.html.headers b/testing/web-platform/tests/fenced-frame/resources/window-navigation-204-inner.html.headers new file mode 100644 index 0000000000..1b63235b7c --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/resources/window-navigation-204-inner.html.headers @@ -0,0 +1 @@ +Supports-Loading-Mode: fenced-frame diff --git a/testing/web-platform/tests/fenced-frame/resources/window-outer-dimensions-inner.html b/testing/web-platform/tests/fenced-frame/resources/window-outer-dimensions-inner.html new file mode 100644 index 0000000000..81dee800fc --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/resources/window-outer-dimensions-inner.html @@ -0,0 +1,36 @@ +<!DOCTYPE html> +<script src="utils.js"></script> +<title>Fenced frame content to report the result of prerendering</title> + +<body> + <script> + async function report() { // Needed in order to use top-level await. + // This file is meant to run in a <fencedframe>. It reports its dimensions + // back to the outermost page, which in turn checks for correctness. + const [window_outer_size_key, window_inner_size_key, dimension, + extra_children] = parseKeylist(); + + const url = new URL(location.href); + + if (extra_children == "0") { + let outer_result = (dimension == "width") ? + window.outerWidth : window.outerHeight; + + let inner_result = (dimension == "width") ? + window.innerWidth : window.innerHeight; + + writeValueToServer(window_outer_size_key, outer_result); + writeValueToServer(window_inner_size_key, inner_result); + } else { + const iframe = document.createElement('iframe'); + const frame_url = generateURL('window-outer-dimensions-inner.html', + [window_outer_size_key, window_inner_size_key, dimension, + (parseInt(extra_children) - 1)]); + iframe.src = frame_url; + document.body.append(iframe); + } + + } + report(); + </script> +</body> diff --git a/testing/web-platform/tests/fenced-frame/resources/window-outer-dimensions-inner.html.headers b/testing/web-platform/tests/fenced-frame/resources/window-outer-dimensions-inner.html.headers new file mode 100644 index 0000000000..6247f6d632 --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/resources/window-outer-dimensions-inner.html.headers @@ -0,0 +1 @@ +Supports-Loading-Mode: fenced-frame
\ No newline at end of file diff --git a/testing/web-platform/tests/fenced-frame/resources/window-parent-inner.html b/testing/web-platform/tests/fenced-frame/resources/window-parent-inner.html new file mode 100644 index 0000000000..9008d7d923 --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/resources/window-parent-inner.html @@ -0,0 +1,64 @@ +<!DOCTYPE html> +<script src="/resources/testharness.js"></script> +<script src="utils.js"></script> +<title>Fenced frame content to report the value of window.parent</title> + +<body> +<script> +async function init() { // Needed in order to use top-level await. + // This file is meant to run in a <fencedframe>. It reports back to the + // outermost page whether or not the value of `window.parent` was correct for: + // 1.) Top-level fenced frames + // 2.) Nested iframes inside a fenced frame + // 3.) Nested fenced frames + const url = new URL(location.href); + + const [window_parent_key, window_parent_ack_key, nested] = parseKeylist(); + const is_nested_fenced_frame = (nested == "nested"); + + // Report whether or not `window.parent` was correct. + let pass_string = ""; + if (is_nested_fenced_frame) + pass_string = "pass: fenced frame > fenced frame"; + else + pass_string = "pass: fenced frame"; + + let result = (window.parent == window) ? pass_string : "fail"; + writeValueToServer(window_parent_key, result); + + // If this page is a nested fenced frame, all we need to do is report the + // top-level value. + if (is_nested_fenced_frame) + return; + + // Wait for ACK, so we know that the outer page has read the last value from + // the `window_parent_key` stash and we can write to it again. + await nextValueFromServer(window_parent_ack_key); + + // Now test `window.parent` inside an iframe. + const iframe = document.createElement('iframe'); + iframe.src = "dummy.html"; + const load_promise = new Promise((resolve, reject) => { + iframe.onload = resolve; + iframe.onerror = reject; + }); + document.body.append(iframe); + + await load_promise; + + // Report whether or not the subframe's `window.parent` was correct. + result = (iframe.contentWindow.parent == window) ? + "pass: fenced frame > iframe" : "fail"; + writeValueToServer(window_parent_key, result); + + // Wait for ACK, so we know that the outer page has read the last value from + // the `window_parent_key` stash and we can write to it again. + await nextValueFromServer(window_parent_ack_key); + + attachFencedFrame(generateURL("window-parent-inner.html", + [window_parent_key, window_parent_ack_key, "nested"])); +} + +init(); +</script> +</body> diff --git a/testing/web-platform/tests/fenced-frame/resources/window-parent-inner.html.headers b/testing/web-platform/tests/fenced-frame/resources/window-parent-inner.html.headers new file mode 100644 index 0000000000..6247f6d632 --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/resources/window-parent-inner.html.headers @@ -0,0 +1 @@ +Supports-Loading-Mode: fenced-frame
\ No newline at end of file diff --git a/testing/web-platform/tests/fenced-frame/resources/window-top-inner.html b/testing/web-platform/tests/fenced-frame/resources/window-top-inner.html new file mode 100644 index 0000000000..ddc30bf71b --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/resources/window-top-inner.html @@ -0,0 +1,63 @@ +<!DOCTYPE html> +<script src="/resources/testharness.js"></script> +<script src="utils.js"></script> +<title>Fenced frame content to report the value of window.top</title> + +<body> +<script> +async function init() { // Needed in order to use top-level await. + // This file is meant to run in a <fencedframe>. It reports back to the + // outermost page whether or not the value of `window.top` was correct for: + // 1.) Top-level fenced frames + // 2.) Nested iframes inside a fenced frame + // 3.) Nested fenced frames + const url = new URL(location.href); + + const [window_top_key, window_top_ack_key, nested] = parseKeylist(); + + // Report whether or not `window.top` was correct. + let pass_string = ""; + if (nested == "nested") + pass_string = "pass: fenced frame > fenced frame"; + else + pass_string = "pass: fenced frame"; + + let result = (window.top == window) ? pass_string : "fail"; + writeValueToServer(window_top_key, result); + + // If this page is a nested fenced frame, all we need to do is report the + // top-level value. + if (nested == "nested") + return; + + // Wait for ACK, so we know that the outer page has read the last value from + // the `window_top_key` stash and we can write to it again. + await nextValueFromServer(window_top_ack_key); + + // Now test `window.top` inside an iframe. + const iframe = document.createElement('iframe'); + iframe.src = "dummy.html"; + const load_promise = new Promise((resolve, reject) => { + iframe.onload = resolve; + iframe.onerror = reject; + }); + document.body.append(iframe); + + await load_promise; + + // Report whether or not the subframe's `window.top` was correct. + result = (iframe.contentWindow.top == window) ? + "pass: fenced frame > iframe" : "fail"; + writeValueToServer(window_top_key, result); + + // Wait for ACK, so we know that the outer page has read the last value from + // the `window_top_key` stash and we can write to it again. + await nextValueFromServer(window_top_ack_key); + + attachFencedFrame(generateURL("window-top-inner.html", + [window_top_key, window_top_ack_key, "nested"])); +} + +init(); +</script> +</body> diff --git a/testing/web-platform/tests/fenced-frame/resources/window-top-inner.html.headers b/testing/web-platform/tests/fenced-frame/resources/window-top-inner.html.headers new file mode 100644 index 0000000000..6247f6d632 --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/resources/window-top-inner.html.headers @@ -0,0 +1 @@ +Supports-Loading-Mode: fenced-frame
\ No newline at end of file |