diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 01:47:29 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 01:47:29 +0000 |
commit | 0ebf5bdf043a27fd3dfb7f92e0cb63d88954c44d (patch) | |
tree | a31f07c9bcca9d56ce61e9a1ffd30ef350d513aa /testing/web-platform/tests/fenced-frame | |
parent | Initial commit. (diff) | |
download | firefox-esr-0ebf5bdf043a27fd3dfb7f92e0cb63d88954c44d.tar.xz firefox-esr-0ebf5bdf043a27fd3dfb7f92e0cb63d88954c44d.zip |
Adding upstream version 115.8.0esr.upstream/115.8.0esr
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'testing/web-platform/tests/fenced-frame')
358 files changed, 11249 insertions, 0 deletions
diff --git a/testing/web-platform/tests/fenced-frame/README.md b/testing/web-platform/tests/fenced-frame/README.md new file mode 100644 index 0000000000..6055d17e97 --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/README.md @@ -0,0 +1,217 @@ +# Fenced Frames + +This directory contains [Web Platform +Tests](third_party/blink/web_tests/external/wpt) for the [Fenced +Frames](https://github.com/shivanigithub/fenced-frame) feature. + +These tests are generally intended to be upstreamed to the Web Platform Tests +repository (i.e., moved from `wpt_internal/fenced_frame/` to `external/wpt/`). +There are a few reasons why we're holding off doing that right now, see [Fenced +Frames Testing Plan > Web Platform Tests](https://docs.google.com/document/d/1A4Dkw8PesXSqmRLy2Xa-KxpXgIZUT4rPocbxMBuP_3E/edit#heading=h.3plnzof3mgvv). + +In general, these tests should follow Chromium's [web tests +guidelines](docs/testing/web_tests_tips.md) and [web-platform-tests +guidelines](/docs/testing/web_platform_tests.md). This document describes +how to use the specific fenced frame testing infrastructure. + +## How to run tests +Fenced frames feature needs to be enabled to run tests. A convenient way to +do this is to define the following variables for fenced frames [virtual test +suites](https://chromium.googlesource.com/chromium/src/+/HEAD/docs/testing/web_tests.md#virtual-test-suites) +directories. +```bash +# Fenced frame MPArch implementation +export MPTEST=virtual/fenced-frame-mparch/wpt_internal/fenced_frame +# Fenced frame ShadowDOM implementation +export SDTEST=virtual/fenced-frame-shadow-dom/wpt_internal/fenced_frame +``` + +Then run tests under the virtual test suite. This will include necessary +fenced frame flags. +```bash +third_party/blink/tools/run_web_tests.py -t Default $MPTEST/test-name.https.html +``` + +## How to write tests + +The `<fencedframe>` element has a strict requirement that it cannot directly +communicate with or reach its embedder document. The fenced frame does have +network access however, so we can use a server as a middleman to communicate +with the outer page. There are two main test patterns we use: remote execution +(recommended) and message passing (deprecated). + +### Remote execution + +Remote execution uses the helper `attachFencedFrameContext()` defined in +[resources/utils.js](resources/utils.js), which requires +[/common/dispatcher/dispatcher.js](/common/dispatcher/dispatcher.js) and +[/common/utils.js](/common/utils.js). This returns a fenced frame that is +wrapped with additional functionality from RemoteContext, which allows you to +perform a remote procedure call into the frame using the function +`execute(function, [arguments]=[])`. + +This interface allows us to write an entire test in only one file, with minimal +boilerplate and an obvious control flow between all the frames on the page +(including nested fenced frames, which can be achieved with nested `execute` +calls). + +Let's see an example of communication between the top-level frame and the fenced +frame. + +```js +promise_test(async () => { + const important_value = "Hello"; + + // First, create an empty fenced frame. + const frame = attachFencedFrameContext(); + + // Next, make a function call into the frame, passing a particular string + // "Hello" as an argument. Make sure to `await` the call. + const response = await frame.execute((message_from_embedder) => { + + // This code runs inside the fenced frame. + if (message_from_embedder == "Hello") { + // Message that we received was expected. + return "Hello to you too"); + } else { + // Message that we received was *not* expected, let's report an error to + // the outer page so it fails the test. + return "Unexpected message"; + } + + }, [important_value]); + + // Assert that the returned value was what we expected. + // Keep in mind that in a less contrived example, you can perform this assert + // inside the fenced frame. + assert_equals(response, "Hello to you too", + "The fenced frame received the message, and said hello back to us".) +}, "Fenced frame and receive and send a greeting"); +``` + +For test examples, see +[document-referrer.https.html](document-referrer.https.html), +[hid.https.html](hid.https.html), +or [web-usb.https.html](web-usb.https.html). + +Some tips to keep in mind while writing tests using remote execution: +* The functions `attachFencedFrameContext()` and `attachIFrameContext()` + optionally take a dictionary of configs as an argument. You can use it to + pass: + * The API you want to use to generate the fenced frame urn. Either `'fledge'`, + `'sharedstorage'`, or default (case-insensitive). When you use this option, + the return value becomes a promise so you **must** await it.For example: + ``` + await attachFencedFrameContext({generator_api: 'fledge'}); + ``` + * HTML source code to inject into the frame's DOM tree. For example: + ``` + attachFencedFrameContext({html: '<button id="Button">Click me!</button>'}); + ``` + * Response headers. For example: + ``` + attachFencedFrameContext({headers: [["Content-Security-Policy", "frame-src 'self'"]]}); + ``` + * Attributes to set on the frame. For example: + ``` + attachIFrameContext({attributes: [["csp", "frame-src 'self'"]]}) + ``` + * Origin of the url to allow cross-origin test. For example: + ``` + attachIFrameContext({origin:get_host_info().HTTPS_REMOTE_ORIGIN}) + ``` +* There is also a helper `attachIFrameContext()`, which does the same thing + but for iframes instead of fencedframes. +* There is also a helper `replaceFrameContext(frame, {options})` which will + replace an existing frame context using the same underlying element (i.e., you + can use it to test when happens when you navigate an existing frame). +* Make sure to `await` the result of an `execute` call, even if it doesn't + return anything. +* In order to save a global variable, you need to explicitly assign to + `window.variable_name`. Assigning to `variable_name` without declaring it + will not persist across `execute` calls. This is especially important for + tests with nested frames, if you want to keep a handle to the nested frame + across multiple calls. +* Remember to declare the function passed to `execute` as async if it itself + needs to invoke any async functions, including to create nested frames. + +### Message passing (deprecated) + +Message passing is done by using the helpers +defined in +[resources/utils.js](third_party/blink/web_tests/wpt_internal/fenced_frame/resources/utils.js) +to send a message to the server, and poll the server for a response. All +messages have a unique key associated with them so that documents that want to +receive messages can poll the server for a given message that can be identified +by a unique key. + +Let's see an example of sending a message to the server that a fenced frame will +receive and respond to. + +**outer-page.js:** +```js +promise_test(async () => { + const important_message_key = token(); + const fenced_frame_ack_key = token(); + const important_value = "Hello"; + + // First, let's embed a new fenced frame in our test, and pass the key we + // just created into it as a parameter. + const frame_url = generateURL("resources/page-inner.html", + [important_message_key, fenced_frame_ack_key]); + attachFencedFrame(frame_url); + + // Then, let's send the message over to the fenced frame. + writeValueToServer(important_message_key, important_value); + + // Now that the message has been sent to the fenced frame, let's wait for its + // ACK, so that we don't exit the test before the fenced frame gets the + // message. + const response_from_fenced_frame = await + nextValueFromServer(fenced_frame_ack_key); + assert_equals(response_from_fenced_frame, "Hello to you too", + "The fenced frame received the message, and said hello back to us"); +}, "Fenced frame and receive and send a greeting"); +``` + +**inner-fenced-frame.js:** + +```js +async function init() { // Needed in order to use top-level await. + const [important_message_key, fenced_frame_ack_key] = parseKeylist(); + const greeting_from_embedder = await nextValueFromServer(important_message_key); + + if (greeting_from_embedder == "Hello") { + // Message that we received was expected. + writeValueToServer(fenced_frame_ack_key, "Hello to you too"); + } else { + // Message that we received was *not* expected, let's report an error to the + // outer page so it fails the test. + writeValueToServer(fenced_frame_ack_key, "Unexpected message"); + } +} + +init(); +``` + +When you write a new web platform test, it will likely involve passing a _new_ +message like the messages above, to and from the fenced frame. Keep in mind +that you may have to use a _pair_ of keys, so that when one document writes a +message associated with one unique key, it can listen for an ACK from the +receiving document, so that it doesn't write over the message again before the +receiving document actually reads it. **No two tests should ever use the same +key to communicate information to and from a fenced frame**, as this will cause +server-side race conditions. + +For a good test example, see +[window-parent.html](window-parent.html). + +## Underlying implementations + +This directory contains <fencedframe> tests that exercise the +`blink::features::kFencedFrames` feature. + +## Wrap lines at 80 columns + +This is the convention for most Chromium/WPT style tests. Note that +`git cl format [--js]` does not reformat js code in .html files. diff --git a/testing/web-platform/tests/fenced-frame/add-fencedframe-to-detached-iframe.https.html b/testing/web-platform/tests/fenced-frame/add-fencedframe-to-detached-iframe.https.html new file mode 100644 index 0000000000..37c0cd6cba --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/add-fencedframe-to-detached-iframe.https.html @@ -0,0 +1,36 @@ +<!DOCTYPE html> +<title>Test Add Fenced Frame to Detached Iframe</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/utils.js"></script> + +<body> +<script> +promise_test(async (t) => { + const iframe = attachIFrame("resources/dummy.html"); + + const doc = document.querySelector("iframe").contentDocument; + iframe.remove(); + + const ff = doc.createElement("fencedframe"); + doc.body.append(ff); +}, 'Add fenced frame to detached iframe test'); + +promise_test(async (t) => { + const iframe = attachIFrame("resources/dummy.html"); + const doc = document.querySelector("iframe").contentDocument; + + const nested_iframe = doc.createElement('iframe'); + nested_iframe.src = "resources/dummy.html"; + doc.body.append(nested_iframe); + const nested_doc = doc.querySelector("iframe").contentDocument; + + iframe.remove(); + + const ff = nested_doc.createElement("fencedframe"); + nested_doc.body.append(ff); +}, 'Add fenced frame to nested iframe in detached frame test'); + +</script> +</body> +</html> diff --git a/testing/web-platform/tests/fenced-frame/ancestor-throttle.https.html b/testing/web-platform/tests/fenced-frame/ancestor-throttle.https.html new file mode 100644 index 0000000000..9b6dfb0d30 --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/ancestor-throttle.https.html @@ -0,0 +1,87 @@ +<!DOCTYPE html> +<html> +<title>Test frame-ancestor</title> +<meta name="timeout" content="long"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/utils.js"></script> +<script src="/common/utils.js"></script> +<script src="/common/get-host-info.sub.js"></script> +<body> + +<script> +async function runTest(embed_url, + cross_origin_to_top_level_fenced_frame, cross_origin_to_top_level_iframe, + expected_result) { + const ancestor_key = token(); + + // Generate the url for the top level fenced frame, including the information + // needed to pass on to its nested iframe + const cross_origin = get_host_info().HTTPS_REMOTE_ORIGIN; + let fenced_frame_url = generateURL( + "resources/ancestor-throttle-inner.https.html", + [ancestor_key, embed_url, cross_origin_to_top_level_iframe]); + if (cross_origin_to_top_level_fenced_frame) + fenced_frame_url = getRemoteOriginURL(fenced_frame_url, true); + + attachFencedFrame(fenced_frame_url); + + // There is no API to observe whether the document in the FencedFrame loaded + // or not. Instead, set up a timeout. If the document loads, "loaded" will be + // sent to the server. Otherwise "blocked" will be sent after 3 seconds. + step_timeout(() => { + writeValueToServer(ancestor_key, "blocked"); + }, 3000); + + // Get the result for the fenced frame's nested iframe. + const fenced_frame_result = await nextValueFromServer(ancestor_key); + assert_equals(fenced_frame_result, expected_result, + "The inner iframe was " + expected_result + "."); +} + +promise_test(async () => { + return runTest("fenced-frame/resources/" + + "ancestor-throttle-nested.https.html?" + + "nested_url=ancestor-throttle-iframe-csp.https.html", + true, false, "blocked"); +}, "root(origin1)->fenced(origin2)->iframe(origin1) should honor " + + "CSP frame-ancestors headers up until the fenced frame root"); + +promise_test(async () => { + return runTest("fenced-frame/resources/" + + "ancestor-throttle-nested.https.html?" + + "nested_url=ancestor-throttle-iframe-csp.https.html", + true, false, "blocked"); +}, "root(origin1)->fenced(origin2)->iframe(origin1) should honor " + + "XFO SAMEORIGIN headers up until the fenced frame root"); + +promise_test(async () => { + return runTest("fenced-frame/resources/" + + "ancestor-throttle-iframe-csp.https.html", true, true, "loaded"); +}, "root(origin1)->fenced(origin2)->iframe(origin2) should honor " + + "CSP frame-ancestors headers up until the fenced frame root"); + +promise_test(async () => { + return runTest("fenced-frame/resources/" + + "ancestor-throttle-iframe-xfo.https.html", true, true, "loaded"); +}, "root(origin1)->fenced(origin2)->iframe(origin2) should honor " + + "XFO SAMEORIGIN headers up until the fenced frame root"); + +promise_test(async () => { + return runTest("fenced-frame/resources/" + + "ancestor-throttle-nested.https.html?" + + "nested_url=ancestor-throttle-iframe-csp.https.html", + false, true, "blocked"); +}, "root(origin1)->fenced(origin1)->iframe(origin2)->iframe(origin2) should " + + "honor CSP frame-ancestors headers up until the fenced frame root"); + +promise_test(async () => { + return runTest("fenced-frame/resources/" + + "ancestor-throttle-nested.https.html?" + + "nested_url=ancestor-throttle-iframe-csp.https.html", + false, true, "blocked"); +}, "root(origin1)->fenced(origin1)->iframe(origin2)->iframe(origin2) should " + + "honor XFO SAMEORIGIN headers up until the fenced frame root"); +</script> +</body> +</html> diff --git a/testing/web-platform/tests/fenced-frame/anchor-focus.https.html b/testing/web-platform/tests/fenced-frame/anchor-focus.https.html new file mode 100644 index 0000000000..262781f571 --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/anchor-focus.https.html @@ -0,0 +1,47 @@ +<!DOCTYPE html> +<title>Anchor based focusing across a fenced frame boundary</title> +<script src="/resources/testdriver.js"></script> +<script src="/resources/testdriver-actions.js"></script> +<script src="/resources/testdriver-vendor.js"></script> +<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> + +<body> +<script> +function attemptAutofocus(frame) { + return frame.execute(async () => { + let autofocusInput = document.createElement('input'); + autofocusInput.id = "myinput"; + document.body.appendChild(autofocusInput); + document.location.href = document.location.href + "#myinput"; + await new Promise(resolve => requestAnimationFrame(resolve)); + return document.activeElement == autofocusInput; + }); +} + +promise_test(async () => { + const frame = attachFencedFrameContext(); + let autofocusIsFocused = await attemptAutofocus(frame); + assert_false(autofocusIsFocused, + "element should not get focus through anchor focusing"); +}, "Anchor focusing is blocked on an element in a fenced frame " + + "without user activation."); + +promise_test(async () => { + const frame = attachFencedFrameContext(); + const actions = new test_driver.Actions(); + await actions.pointerMove(0, 0, {origin: frame.element}) + .pointerDown() + .pointerUp() + .send(); + let autofocusIsFocused = await attemptAutofocus(frame); + assert_true(autofocusIsFocused, + "element should get focus through anchor focusing"); +}, "Anchor focusing is allowed on an element in a fenced frame " + + "with user activation."); +</script> +</body> +</html> diff --git a/testing/web-platform/tests/fenced-frame/autofocus-denied.https.html b/testing/web-platform/tests/fenced-frame/autofocus-denied.https.html new file mode 100644 index 0000000000..ff6955a3b2 --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/autofocus-denied.https.html @@ -0,0 +1,25 @@ +<!DOCTYPE html> +<title>Autofocusing is blocked in a fenced frame</title> +<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> + +<body> +<script> +promise_test(async () => { + const frame = attachFencedFrameContext(); + let autofocusIsFocused = await frame.execute(async () => { + let autofocusInput = document.createElement('input'); + autofocusInput.autofocus = true; + document.body.appendChild(autofocusInput); + await new Promise(resolve => requestAnimationFrame(resolve)); + return document.activeElement == autofocusInput; + }); + assert_false(autofocusIsFocused, "<input autofocus> received focus"); +}, "Autofocusing is blocked on an element in a fenced frame as "+ + "it's treated like a cross-origin subframe."); +</script> +</body> +</html> diff --git a/testing/web-platform/tests/fenced-frame/automatic-beacon-click-handler.https.html b/testing/web-platform/tests/fenced-frame/automatic-beacon-click-handler.https.html new file mode 100644 index 0000000000..505eb6174f --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/automatic-beacon-click-handler.https.html @@ -0,0 +1,80 @@ +<!DOCTYPE html> +<title>Test window.fence.setReportEventDataForAutomaticBeacons</title> +<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> + +<body> +<script> +promise_test(async(t) => { + const actions = new test_driver.Actions(); + const fencedframe = await attachFencedFrameContext( + {generator_api: 'fledge'}); + const new_url = new URL("resources/dummy.html", location.href); + const beacon_data = "This is the beacon data!"; + + await fencedframe.execute((new_url, beacon_data) => { + addEventListener("click", (event) => { + let beacon_event = { + eventType: "reserved.top_navigation", + eventData: beacon_data, + destination: ["buyer"], + } + window.fence.setReportEventDataForAutomaticBeacons(beacon_event); + window.open(new_url, "_blank"); + }); + }, [new_url, beacon_data]); + + await actions.pointerMove(0, 0, {origin: fencedframe.element}) + .pointerDown() + .pointerUp() + .send(); + + const received_beacon_data = await nextAutomaticBeacon(); + assert_equals(received_beacon_data, beacon_data); +}, 'Set and trigger an automatic beacon in a click handler'); + +promise_test(async(t) => { + const fencedframe = await attachFencedFrameContext( + {generator_api: 'fledge'}); + const new_url = new URL("resources/dummy.html", location.href); + const beacon_data = "This is the beacon data!"; + + await fencedframe.execute((new_url, beacon_data) => { + const actions = new test_driver.Actions(); + let a = document.createElement('a'); + a.textContent = "Click me!"; + a.href = new_url; + a.target = "_blank"; + + // When the anchor link is clicked, the click handler will set the data + // before the navigation happens. This test checks to make sure that the + // data makes it to the correct place by the time the navigation commits. + a.onclick = () => { + let beacon_event = { + eventType: "reserved.top_navigation", + eventData: beacon_data, + destination: ["buyer"], + } + window.fence.setReportEventDataForAutomaticBeacons(beacon_event); + }; + document.body.appendChild(a); + + // This will trigger the beacon data storing + navigation. + return actions.pointerMove(0, 0, {origin: a}) + .pointerDown() + .pointerUp() + .send(); + }, [new_url, beacon_data]); + + const received_beacon_data = await nextAutomaticBeacon(); + assert_equals(received_beacon_data, beacon_data); +}, 'Set and trigger an automatic beacon in an <a> click handler'); + +</script> +</body> diff --git a/testing/web-platform/tests/fenced-frame/background-fetch.https.html b/testing/web-platform/tests/fenced-frame/background-fetch.https.html new file mode 100644 index 0000000000..7036f2bb5f --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/background-fetch.https.html @@ -0,0 +1,128 @@ +<!DOCTYPE html> +<title>Test fenced frame does not allow call background fetch</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/utils.js"></script> +<script src="/common/get-host-info.sub.js"></script> +<script src="/common/utils.js"></script> + +<body> + + <script> + const background_fetch_register_key = token(); + + async function test(url, expected, message) { + attachFencedFrame(url) + + // Get the result for the top-level fenced frame. + const fenced_frame_result = await nextValueFromServer( + background_fetch_register_key); + assert_equals(fenced_frame_result, expected, message); + } + + promise_test(async () => { + const method = "fetch"; + const fenced_frame_url = 'resources/background-fetch-inner.https.html'; + const url = generateURL(fenced_frame_url, + [background_fetch_register_key, method]); + + await test( + url, + '[backgroundFetch.fetch] Failed inside fencedframe as expected', + 'backgroundFetch.fetch is disallowed inside a same-origin fenced frame'); + + const CROSS_ORIGIN_DESTINATION = getRemoteOriginURL(url); + await test( + CROSS_ORIGIN_DESTINATION, + '[backgroundFetch.fetch] Failed inside fencedframe as expected', + 'backgroundFetch.fetch is disallowed inside a cross-origin fenced frame'); + }, 'backgroundFetch.fetch'); + + promise_test(async () => { + const method = "get"; + const fenced_frame_url = 'resources/background-fetch-inner.https.html'; + const url = generateURL(fenced_frame_url, + [background_fetch_register_key, method]); + await test( + url, + '[backgroundFetch.get] Failed inside fencedframe as expected', + 'backgroundFetch.get is disallowed inside a same-origin fenced frame'); + + const CROSS_ORIGIN_DESTINATION = getRemoteOriginURL(url); + await test( + CROSS_ORIGIN_DESTINATION, + '[backgroundFetch.get] Failed inside fencedframe as expected', + 'backgroundFetch.get is disallowed inside a cross-origin fenced frame'); + }, 'backgroundFetch.get'); + + promise_test(async () => { + const method = "getIds"; + const fenced_frame_url = 'resources/background-fetch-inner.https.html'; + const url = generateURL(fenced_frame_url, + [background_fetch_register_key, method]); + await test( + url, + '[backgroundFetch.getIds] Failed inside fencedframe as expected', + 'backgroundFetch.getIds is disallowed inside a same-origin fenced frame'); + + const CROSS_ORIGIN_DESTINATION = getRemoteOriginURL(url); + await test( + CROSS_ORIGIN_DESTINATION, + '[backgroundFetch.getIds] Failed inside fencedframe as expected', + 'backgroundFetch.getIds is disallowed inside a cross-origin fenced frame'); + }, 'backgroundFetch.getIds'); + + promise_test(async () => { + const method = "fetch"; + const fenced_frame_url = 'resources/background-fetch-sw-inner.https.html'; + const url = generateURL(fenced_frame_url, + [background_fetch_register_key, method]); + + await test( + url, + '[backgroundFetch.fetch] Failed inside fencedframe as expected', + 'backgroundFetch.fetch is disallowed inside a same-origin fenced frame'); + + const CROSS_ORIGIN_DESTINATION = getRemoteOriginURL(url); + await test( + CROSS_ORIGIN_DESTINATION, + '[backgroundFetch.fetch] Failed inside fencedframe as expected', + 'backgroundFetch.fetch is disallowed inside a cross-origin fenced frame'); + }, 'backgroundFetch.fetch in service worker'); + + promise_test(async () => { + const method = "get"; + const fenced_frame_url = 'resources/background-fetch-sw-inner.https.html'; + const url = generateURL(fenced_frame_url, + [background_fetch_register_key, method]); + await test( + url, + '[backgroundFetch.get] Failed inside fencedframe as expected', + 'backgroundFetch.get is disallowed inside a same-origin fenced frame'); + + const CROSS_ORIGIN_DESTINATION = getRemoteOriginURL(url); + await test( + CROSS_ORIGIN_DESTINATION, + '[backgroundFetch.get] Failed inside fencedframe as expected', + 'backgroundFetch.get is disallowed inside a cross-origin fenced frame'); + }, 'backgroundFetch.get in service worker'); + + promise_test(async () => { + const method = "getIds"; + const fenced_frame_url = 'resources/background-fetch-sw-inner.https.html'; + const url = generateURL(fenced_frame_url, + [background_fetch_register_key, method]); + await test( + url, + '[backgroundFetch.getIds] Failed inside fencedframe as expected', + 'backgroundFetch.getIds is disallowed inside a same-origin fenced frame'); + + const CROSS_ORIGIN_DESTINATION = getRemoteOriginURL(url); + await test( + CROSS_ORIGIN_DESTINATION, + '[backgroundFetch.getIds] Failed inside fencedframe as expected', + 'backgroundFetch.getIds is disallowed inside a cross-origin fenced frame'); + }, 'backgroundFetch.getIds in service worker'); + </script> + +</body> diff --git a/testing/web-platform/tests/fenced-frame/background-sync.https.html b/testing/web-platform/tests/fenced-frame/background-sync.https.html new file mode 100644 index 0000000000..72eb44750b --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/background-sync.https.html @@ -0,0 +1,218 @@ +<!DOCTYPE html> +<title>Test fenced frame does not allow to register background sync</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/utils.js"></script> +<script src="/common/get-host-info.sub.js"></script> +<script src="/common/utils.js"></script> +<body> +<script> +const background_sync_register_key = token(); +const fenced_frame_url = 'resources/background-sync-inner.https.html'; +const fenced_frame_url_for_service_worker_test = + 'resources/background-sync-sw-inner.https.html'; + +const generateFencedFrameUrl = (params, is_worker_test = false) => { + const searchParams = new URLSearchParams(params); + const frame_url = is_worker_test ? + fenced_frame_url_for_service_worker_test : fenced_frame_url; + + return generateURL(`${frame_url}?${searchParams.toString()}`, + [background_sync_register_key]); +}; + +async function background_sync_test(url, expected_text, message) { + attachFencedFrame(url) + + // Get the result for the top-level fenced frame. + const fenced_frame_result = + await nextValueFromServer(background_sync_register_key); + assert_equals(fenced_frame_result, expected_text, message); +}; + +const background_sync_message = + "Background Sync is not allowed in fenced frames."; +const periodic_background_sync_message = + "Periodic Background Sync is not allowed in fenced frames."; + +promise_test(async (t) => { + const method = 'register'; + const frame_url = generateFencedFrameUrl({method}); + const expected_message = "Failed to execute 'register' on 'SyncManager': " + + background_sync_message; + await background_sync_test( + frame_url, + expected_message, + 'register() is disallowed inside a same-origin fenced frame'); + + const cross_origin_frame_url = getRemoteOriginURL(frame_url); + await background_sync_test( + cross_origin_frame_url, + expected_message, + 'register() is disallowed inside a cross-origin fenced frame'); +}, 'background sync register'); + +promise_test(async (t) => { + const method = 'getTags'; + const frame_url = generateFencedFrameUrl({method}); + const expected_message = background_sync_message; + await background_sync_test( + frame_url, + expected_message, + 'getTags() is disallowed inside a same-origin fenced frame'); + + const cross_origin_frame_url = getRemoteOriginURL(frame_url); + await background_sync_test( + cross_origin_frame_url, + expected_message, + 'getTags() is disallowed inside a cross-origin fenced frame'); +}, 'background sync getTags'); + +promise_test(async (t) => { + const method = 'register'; + const frame_url = generateFencedFrameUrl({method}, true); + const expected_message = "Failed to execute 'register' on 'SyncManager': " + + background_sync_message; + await background_sync_test( + frame_url, + expected_message, + 'register() is disallowed inside a same-origin fenced frame'); + const cross_origin_frame_url = getRemoteOriginURL(frame_url); + await background_sync_test( + cross_origin_frame_url, + expected_message, + 'register() is disallowed inside a cross-origin fenced frame'); +}, 'background sync register in service worker'); + +promise_test(async (t) => { + const method = 'getTags'; + const frame_url = generateFencedFrameUrl({method}, true); + const expected_message = background_sync_message; + await background_sync_test( + frame_url, + expected_message, + 'getTags() is disallowed inside a same-origin fenced frame'); + const cross_origin_frame_url = getRemoteOriginURL(frame_url); + await background_sync_test( + cross_origin_frame_url, + expected_message, + 'getTags() is disallowed inside a cross-origin fenced frame'); +}, 'background sync getTags in service worker'); + +promise_test(async (t) => { + const params = { + method: 'register', + periodic: true + }; + const frame_url = generateFencedFrameUrl(params) + const expected_message = "Failed to execute 'register' on " + + "'PeriodicSyncManager': " + periodic_background_sync_message; + await background_sync_test( + frame_url, + expected_message, + 'register() is disallowed inside a same-origin fenced frame'); + + const cross_origin_frame_url = getRemoteOriginURL(frame_url); + await background_sync_test( + cross_origin_frame_url, + expected_message, + 'register() is disallowed inside a cross-origin fenced frame'); +}, 'periodic sync register'); + +promise_test(async (t) => { + const params = { + method: 'getTags', + periodic: true + }; + const frame_url = generateFencedFrameUrl(params) + const expected_message = periodic_background_sync_message; + await background_sync_test( + frame_url, + expected_message, + 'getTags() is disallowed inside a same-origin fenced frame'); + + const cross_origin_frame_url = getRemoteOriginURL(frame_url); + await background_sync_test( + cross_origin_frame_url, + expected_message, + 'getTags() is disallowed inside a cross-origin fenced frame'); +}, 'periodic sync getTags'); + +promise_test(async (t) => { + const params = { + method: 'unregister', + periodic: true + }; + const frame_url = generateFencedFrameUrl(params) + const expected_message = periodic_background_sync_message; + await background_sync_test( + frame_url, + expected_message, + 'unregister() is disallowed inside a same-origin fenced frame'); + + const cross_origin_frame_url = getRemoteOriginURL(frame_url); + await background_sync_test( + cross_origin_frame_url, + expected_message, + 'unregister() is disallowed inside a cross-origin fenced frame'); +}, 'periodic sync unregister'); + +promise_test(async (t) => { + const params = { + method: 'register', + periodic: true + }; + const frame_url = generateFencedFrameUrl(params, true) + const expected_message = "Failed to execute 'register' on " + + "'PeriodicSyncManager': " + periodic_background_sync_message; + await background_sync_test( + frame_url, + expected_message, + 'register() is disallowed inside a same-origin fenced frame'); + + const cross_origin_frame_url = getRemoteOriginURL(frame_url); + await background_sync_test( + cross_origin_frame_url, + expected_message, + 'register() is disallowed inside a cross-origin fenced frame'); +}, 'periodic sync register in service worker'); + +promise_test(async (t) => { + const params = { + method: 'getTags', + periodic: true + }; + const frame_url = generateFencedFrameUrl(params, true) + const expected_message = periodic_background_sync_message; + await background_sync_test( + frame_url, + expected_message, + 'getTags() is disallowed inside a same-origin fenced frame'); + + const cross_origin_frame_url = getRemoteOriginURL(frame_url); + await background_sync_test( + cross_origin_frame_url, + expected_message, + 'getTags() is disallowed inside a cross-origin fenced frame'); +}, 'periodic sync getTags in service worker'); + +promise_test(async (t) => { + const params = { + method: 'unregister', + periodic: true + }; + const frame_url = generateFencedFrameUrl(params, true) + const expected_message = periodic_background_sync_message; + await background_sync_test( + frame_url, + expected_message, + 'unregister() is disallowed inside a same-origin fenced frame'); + + const cross_origin_frame_url = getRemoteOriginURL(frame_url); + await background_sync_test( + cross_origin_frame_url, + expected_message, + 'unregister() is disallowed inside a cross-origin fenced frame'); +}, 'periodic sync unregister in service worker'); +</script> +</body> diff --git a/testing/web-platform/tests/fenced-frame/badging.https.html b/testing/web-platform/tests/fenced-frame/badging.https.html new file mode 100644 index 0000000000..93683e096c --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/badging.https.html @@ -0,0 +1,79 @@ +<!DOCTYPE html> +<title>Test Badging API</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="/common/utils.js"></script> +<script src="/common/dispatcher/dispatcher.js"></script> +<script src="resources/utils.js"></script> + +<body> +<script> +const createRemoteFunctionForServiceWorkerTest = () => { + return async (method, service_worker_url) => { + const getController = () => { + if (navigator.serviceWorker.controller) { + return navigator.serviceWorker.controller; + } + return new Promise(resolve => { + navigator.serviceWorker.addEventListener('controllerchange', () => { + resolve(navigator.serviceWorker.controller); + }); + }); + }; + + await navigator.serviceWorker.register( + service_worker_url, { scope: location.href }); + const ctrl = await getController(); + + return new Promise(resolve => { + ctrl.postMessage(method); + navigator.serviceWorker.onmessage = e => { + resolve(e.data.name); + } + }); + } +} + +promise_test(async () => { + const frame = attachFencedFrameContext(); + const error_name = await frame.execute(() => { + return navigator.setAppBadge(1).catch(e => e.name); + }); + assert_equals(error_name, + "NotAllowedError", + "The Badge API should cause exception in a fencedfarme"); +}, 'setAppBadge should fail inside a fenced frame'); + +promise_test(async () => { + const frame = attachFencedFrameContext(); + const error_name = await frame.execute(() => { + return navigator.clearAppBadge().catch(e => e.name); + }); + assert_equals(error_name, + "NotAllowedError", + "The Badge API should cause exception in a fencedfarme"); +}, 'clearAppBadge should fail inside a fenced frame'); + +promise_test(async () => { + const frame = attachFencedFrameContext(); + const error_name = await frame.execute( + createRemoteFunctionForServiceWorkerTest(), + ['setAppBadge', 'badging-sw.js']); + assert_equals(error_name, "NotAllowedError", + "The Badge API should cause exception from a service worker " + + "in a fencedfarme"); +}, 'setAppBadge should fail from a service worker inside a fenced frame'); + +promise_test(async () => { + const frame = attachFencedFrameContext(); + const error_name = await frame.execute( + createRemoteFunctionForServiceWorkerTest(), + ['clearAppBadge', 'badging-sw.js']); + assert_equals(error_name, "NotAllowedError", + "The Badge API should cause exception from a service worker " + + "in a fencedfarme"); +}, 'clearAppBadge should fail from a service worker inside a fenced frame'); +</script> +</body> diff --git a/testing/web-platform/tests/fenced-frame/battery_status.https.html b/testing/web-platform/tests/fenced-frame/battery_status.https.html new file mode 100644 index 0000000000..d7e1dc0284 --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/battery_status.https.html @@ -0,0 +1,19 @@ +<!DOCTYPE html> +<title>Battery status API test</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/common/utils.js"></script> +<script src="resources/utils.js"></script> + +<body> +<script> +promise_test(async () => { + const uuid = token(); + const frame = attachFencedFrame( + generateURL(`resources/get_battery.html`, [uuid])); + const result = await nextValueFromServer(uuid); + assert_equals(result, "NotAllowedError", + "battery status API should cause exception"); +}, 'battery status should not be read in the fenced frame.'); +</script> +</body> diff --git a/testing/web-platform/tests/fenced-frame/before-unload.https.html b/testing/web-platform/tests/fenced-frame/before-unload.https.html new file mode 100644 index 0000000000..d924b1d1a4 --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/before-unload.https.html @@ -0,0 +1,25 @@ +<!DOCTYPE html> +<title>Test the before unload event</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/common/utils.js"></script> +<script src="resources/utils.js"></script> + +<body> + +<script> +promise_test(async () => { + const before_unload_key = token(); + + const frame_url = generateURL("resources/before-unload-inner.html", + [before_unload_key]); + + attachFencedFrame(frame_url); + + const result = await nextValueFromServer(before_unload_key); + assert_equals(result, "Loaded the next url in a fenced frame", + "A fenced frame should not fire the before unload event."); +}, "before unload event in fenced frames"); +</script> + +</body> diff --git a/testing/web-platform/tests/fenced-frame/can-load-api.https.html b/testing/web-platform/tests/fenced-frame/can-load-api.https.html new file mode 100644 index 0000000000..f9996dd5e9 --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/can-load-api.https.html @@ -0,0 +1,68 @@ +<!DOCTYPE html> +<title>Test canLoadOpaqueURL API</title> +<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> + +<body> +<script> +async function runTest(expected_result, generator_api, attribute_list, header_list, use_fencedframe=false) { + const frame = use_fencedframe ? + await attachFencedFrameContext({generator_api: generator_api, attributes:attribute_list, headers:header_list}) : + await attachIFrameContext({generator_api, generator_api, attributes:attribute_list, headers:header_list}); + await frame.execute(async (expected_result, attribute_list) => { + assert_equals(navigator.canLoadAdAuctionFencedFrame(), expected_result, + "A frame with attributes " + attribute_list + " should return " + + expected_result + " for canLoadOpaqueURL."); + }, [expected_result, attribute_list]); +} + +promise_test(async(t) => { + assert_true(navigator.canLoadAdAuctionFencedFrame()); +}, 'canLoadOpaqueURL called on a page that can load a FF should return true'); + +promise_test(async(t) => { + await runTest(true, "sharedstorage", + [["width", "300"], + ["height", "200"]], + [], use_fencedframe=true); +}, 'canLoadOpaqueURL returns true inside an opaque-ads fenced frame'); + +promise_test(async(t) => { + await runTest(false, "default", [], [], use_fencedframe=true); +}, 'canLoadOpaqueURL returns false inside an default fenced frame'); + +promise_test(async(t) => { + await runTest(true, "default", [], [["Content-Security-Policy", "fenced-frame-src *"]]); + await runTest(true, "default", [], [["Content-Security-Policy", "fenced-frame-src https:"]]); + await runTest(true, "default", [], [["Content-Security-Policy", "fenced-frame-src https://*:*"]]); +}, 'canLoadOpaqueURL returns true for all 3 fenced-frame-src allowed values'); + +promise_test(async(t) => { + await runTest(true, "default", [], [["Content-Security-Policy", "fenced-frame-src *; frame-src 'self'"]]); + await runTest(false, "default", [], [["Content-Security-Policy", "fenced-frame-src 'self'; frame-src *"]]); + await runTest(true, "default", [], [["Content-Security-Policy", "child-src 'self'; fenced-frame-src https:"]]); + await runTest(false, "default", [], [["Content-Security-Policy", "child-src *; fenced-frame-src 'self'"]]); +}, 'canLoadOpaqueURL ignores fallback CSPs'); + +promise_test(async(t) => { + await runTest(true, "default", [], [["Content-Security-Policy", "img-src 'none';"]]); + await runTest(true, "default", [], [["Content-Security-Policy", "font-src 'none';"]]); +}, 'canLoadOpaqueURL ignores unrelated CSPs'); + +promise_test(async(t) => { + const iframe = attachIFrame("resources/dummy.html"); + const iframe_ff_class = iframe.contentWindow.HTMLFencedFrameElement; + + // Sanity check to make sure the function returns true as we expect it to + // before we remove the frame. + assert_true(iframe_ff_class.canLoadOpaqueURL()); + + // The one variable we're changing is whether the frame is attached or not. + iframe.remove(); + assert_false(iframe_ff_class.canLoadOpaqueURL()); +}, 'canLoadOpaqueURL returns false in a detached frame'); +</script> +</body> diff --git a/testing/web-platform/tests/fenced-frame/change-src-attribute-after-config-installation-does-not-trigger-navigation.https.html b/testing/web-platform/tests/fenced-frame/change-src-attribute-after-config-installation-does-not-trigger-navigation.https.html new file mode 100644 index 0000000000..7ad73d3a4b --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/change-src-attribute-after-config-installation-does-not-trigger-navigation.https.html @@ -0,0 +1,52 @@ +<!DOCTYPE html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/common/utils.js"></script> +<script src="/common/get-host-info.sub.js"></script> +<script src="resources/utils.js"></script> +<title>Test changing a fenced frame's src attribute when there has been a config +with url installed already does not trigger navigation.</title> + +<body> + +<script> +function getTimeoutPromise(t) { + return new Promise(resolve => + t.step_timeout(() => resolve("NOT LOADED"), 2000)); +} + +promise_test(async (t) => { + const fenced_frame_loaded_key = token(); + + const url = generateURL( + 'resources/fenced-frame-loaded.html', [fenced_frame_loaded_key]); + const url_string = url.toString(); + + // Create a fenced frame and install an inner config constructed with an url. + const fenced_frame = document.createElement('fencedframe'); + const config = new FencedFrameConfig(url_string); + fenced_frame.config = config; + document.body.append(fenced_frame); + + // Installing a config to the fenced frame triggers navigation. + const load_expected = "fenced frame loaded"; + const load_actual = await nextValueFromServer(fenced_frame_loaded_key); + assert_equals(load_actual, load_expected, + "Fenced frame successfully loaded."); + + const src_key = token(); + const src_url = generateURL( + 'resources/fenced-frame-loaded.html', [src_key]); + + // Changing the src attribute, should not trigger navigation. + fenced_frame.src = src_url; + const src_loaded_promise = nextValueFromServer(src_key); + const src_loaded_result = await Promise.any([src_loaded_promise, + getTimeoutPromise(t)]); + assert_equals(src_loaded_result, "NOT LOADED"); + +}, 'Changing the src attribute of a fenced frame when a config with url', + 'has already been installed does not trigger navigation.'); +</script> + +</body> diff --git a/testing/web-platform/tests/fenced-frame/client-hints-meta.https.html b/testing/web-platform/tests/fenced-frame/client-hints-meta.https.html new file mode 100644 index 0000000000..3a4acf1b26 --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/client-hints-meta.https.html @@ -0,0 +1,40 @@ +<!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 hints in fenced frames test</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="resources/utils.js"></script> +<script src="resources/client-hints-common.sub.js"></script> +<script src="/common/get-host-info.sub.js"></script> +<script src="/common/utils.js"></script> +<script src="/common/dispatcher/dispatcher.js"></script> +<script src="/resources/testdriver-actions.js"></script> +<body> +<script> +promise_test(async () => { + const key = token(); + const url = generateURL('resources/client-hints-meta-inner.sub.https.html', [key]); + const remote_url = getRemoteOriginURL(url); + attachFencedFrame(remote_url); + const result = JSON.parse(await nextValueFromServer(key)); + + // We should not see client hints for the fenced frame root or subframes // + // within the fenced frame tree due to the reject-all permission policy used by + // fenced frames. + const headers = ['root-fenced-frame-headers', 'iframe-headers']; + const hints = [ + 'sec-ch-viewport-width', 'sec-ch-ua-reduced', 'sec-ch-ua-mobile', + ]; + headers.forEach(header => { + hints.forEach(hint => { + assert_equals(result[header][hint], ''); + }); + }); +}, 'fenced frames not send client hints'); +</script> +</body> diff --git a/testing/web-platform/tests/fenced-frame/client-hints.https.html b/testing/web-platform/tests/fenced-frame/client-hints.https.html new file mode 100644 index 0000000000..fc7b8db08d --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/client-hints.https.html @@ -0,0 +1,36 @@ +<!DOCTYPE html> +<title>Client hints in fenced frames test</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="resources/utils.js"></script> +<script src="resources/client-hints-common.sub.js"></script> +<script src="/common/get-host-info.sub.js"></script> +<script src="/common/utils.js"></script> +<script src="/common/dispatcher/dispatcher.js"></script> +<script src="/resources/testdriver-actions.js"></script> +<body> +<script> +promise_test(async () => { + const key = token(); + const url = generateURL('resources/client-hints-inner.sub.https.html', [key]); + const remote_url = getRemoteOriginURL(url); + attachFencedFrame(remote_url); + const result = JSON.parse(await nextValueFromServer(key)); + + // We should not see client hints for the fenced frame root or subframes // + // within the fenced frame tree due to the reject-all permission policy used by + // fenced frames. + const headers = ['root-fenced-frame-headers', 'iframe-headers']; + const hints = [ + 'sec-ch-viewport-width', 'sec-ch-ua-reduced', 'sec-ch-ua-mobile', + ]; + headers.forEach(header => { + hints.forEach(hint => { + assert_equals(result[header][hint], ''); + }); + }); +}, 'fenced frames not send client hints'); +</script> +</body> diff --git a/testing/web-platform/tests/fenced-frame/client-hints.https.html.headers b/testing/web-platform/tests/fenced-frame/client-hints.https.html.headers new file mode 100644 index 0000000000..56ce611418 --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/client-hints.https.html.headers @@ -0,0 +1,3 @@ +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/compute-pressure.https.html b/testing/web-platform/tests/fenced-frame/compute-pressure.https.html new file mode 100644 index 0000000000..81091afc8e --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/compute-pressure.https.html @@ -0,0 +1,34 @@ +<!DOCTYPE html> +<title>Verify that Compute Pressure API from a fenced frame is blocked</title> +<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> + +<body> +<script> +promise_test(async () => { + const frame = attachFencedFrameContext({ + headers: [["Permissions-Policy", "compute-pressure=*"]] + }); + const result = await frame.execute(async () => { + try { + const observer = new PressureObserver(() => {}); + await observer.observe('cpu'); + return 'observation succeeded'; + } catch (e) { + if (e.name == 'NotAllowedError' && + e.message.includes(`Access to the feature "compute pressure" is ` + + "disallowed by permissions policy.")) { + return 'observation failed'; + } else { + return `observation failed with unknown error - ${e.name}: ${e.message}`; + } + } + }); + assert_equals(result, 'observation failed', + 'PressureObserver.observe() fails in a fenced frame.'); +}, 'PressureObserver.observe() fails in a fenced frame.'); +</script> +</body> diff --git a/testing/web-platform/tests/fenced-frame/config-installation-triggers-navigation-of-navigated-fenced-frame.https.html b/testing/web-platform/tests/fenced-frame/config-installation-triggers-navigation-of-navigated-fenced-frame.https.html new file mode 100644 index 0000000000..74f810f6d2 --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/config-installation-triggers-navigation-of-navigated-fenced-frame.https.html @@ -0,0 +1,38 @@ +<!DOCTYPE html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/common/utils.js"></script> +<script src="/common/get-host-info.sub.js"></script> +<script src="resources/utils.js"></script> +<title>Test installing an config to a navigated fenced frame triggers + navigation. +</title> + +<body> + +<script> +promise_test(async () => { + const fenced_frame_loaded_key = token(); + + // Create a fenced frame. + const fenced_frame = document.createElement('fencedframe'); + document.body.append(fenced_frame); + + // Create an inner config. + const config_navigation_key = token(); + const config_url = generateURL('resources/fenced-frame-loaded.html', + [config_navigation_key]); + fenced_frame.config = new FencedFrameConfig(config_url.toString()); + + // Installing an inner config to the fenced frame triggers navigation. + const config_navigation_expected = "fenced frame loaded"; + const config_navigation_actual = + await nextValueFromServer(config_navigation_key); + assert_equals(config_navigation_actual, config_navigation_expected, + "Fenced frame successfully navigated by installing an inner config."); + +}, 'Installing an inner config to a fenced frame that has navigated triggers', + 'navigation.'); +</script> + +</body> diff --git a/testing/web-platform/tests/fenced-frame/config-installation-triggers-navigation.https.html b/testing/web-platform/tests/fenced-frame/config-installation-triggers-navigation.https.html new file mode 100644 index 0000000000..6a7238da05 --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/config-installation-triggers-navigation.https.html @@ -0,0 +1,34 @@ +<!DOCTYPE html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/common/utils.js"></script> +<script src="/common/get-host-info.sub.js"></script> +<script src="resources/utils.js"></script> +<title>Test fenced frame config installation triggers navigation.</title> + +<body> + +<script> +promise_test(async () => { + const fenced_frame_loaded_key = token(); + + const url = generateURL( + 'resources/fenced-frame-loaded.html', [fenced_frame_loaded_key]); + const url_string = url.toString(); + + // Create a fenced frame and install an inner config constructed with an url. + const fenced_frame = document.createElement('fencedframe'); + const config = new FencedFrameConfig(url_string); + fenced_frame.config = config; + document.body.append(fenced_frame); + + // Installing an inner config to the fenced frame triggers navigation. + const load_expected = "fenced frame loaded"; + const load_actual = await nextValueFromServer(fenced_frame_loaded_key); + assert_equals(load_actual, load_expected, + "Fenced frame successfully loaded."); + +}, 'Installing an inner config to a fenced frame triggers navigation.'); +</script> + +</body> diff --git a/testing/web-platform/tests/fenced-frame/config-with-empty-url-installation-unloads-navigated-fenced-frame.https.html b/testing/web-platform/tests/fenced-frame/config-with-empty-url-installation-unloads-navigated-fenced-frame.https.html new file mode 100644 index 0000000000..13e6904cf9 --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/config-with-empty-url-installation-unloads-navigated-fenced-frame.https.html @@ -0,0 +1,44 @@ +<!DOCTYPE html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/common/utils.js"></script> +<script src="/common/get-host-info.sub.js"></script> +<script src="resources/utils.js"></script> +<title>Test installing a config with empty url to a navigated fenced frame + unloads the original document</title> + +<body> + +<script> +promise_test(async () => { + const fenced_frame_loaded_key = token(); + + // Create a fenced frame and install a config constructed with an empty url. + const fenced_frame = document.createElement('fencedframe'); + document.body.append(fenced_frame); + + // Specify the fenced frame's src attribute to an url. + const url = generateURL('resources/fenced-frame-loaded.html', + [fenced_frame_loaded_key]); + fenced_frame.config = new FencedFrameConfig(url); + + // The fenced frame should navigate to the src url. + const load_expected = "fenced frame loaded"; + const load_actual = await nextValueFromServer(fenced_frame_loaded_key); + assert_equals(load_actual, load_expected, + "Fenced frame successfully loaded by specifying its src attribute."); + + // Create a config with an empty url. + const empty_url_config = new FencedFrameConfig(''); + fenced_frame.config = empty_url_config; + + // Installing a config with an empty url to the fenced frame should unload the + // original document. + const server_value = await readValueFromServer(fenced_frame_loaded_key); + assert_false(server_value.status); + +}, 'Installing a config with empty url to a navigated fenced unloads ', + 'the original document.'); +</script> + +</body> diff --git a/testing/web-platform/tests/fenced-frame/consume-user-activation.https.html b/testing/web-platform/tests/fenced-frame/consume-user-activation.https.html new file mode 100644 index 0000000000..e4ad20d17b --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/consume-user-activation.https.html @@ -0,0 +1,95 @@ +<!DOCTYPE html> +<title>Test that user activation propagation is fenced.</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.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/utils.js"></script> +<script src="/common/dispatcher/dispatcher.js"></script> +<script src="resources/utils.js"></script> + +<body> +<script> +// Simulate a click in frame context `frame`. +async function click(frame) { + var actions = new test_driver.Actions(); + await actions.pointerMove(0, 0, {origin: frame}) + .pointerDown() + .pointerUp() + .send(); +} + +assert_activations = (sticky_activation, transient_activation, frame_name) => { + if (sticky_activation) { + assert_true(navigator.userActivation.hasBeenActive, + frame_name + " has been activated."); + } else { + assert_false(navigator.userActivation.hasBeenActive, + frame_name + " has not been activated yet."); + } + if (transient_activation) { + assert_true(navigator.userActivation.isActive, + frame_name + " is currently active."); + } else { + assert_false(navigator.userActivation.isActive, + frame_name + " is not currently active."); + } +}; + +promise_test(async () => { + // This test checks that consumption of transient user activations is + // fenced, i.e. that when the top-level embedder and a fenced frame are + // both active, and one of them performs an operation that consumes + // transient user activation (sets `navigator.userActivation.isActive` + // to `false`), it doesn't affect the other frame. + + // Given a top-level frame A and fenced frame B, the structure of the + // test is as follows: + // - Activate both A and B with a click + // - Consume A's transient user activation with `window.open` + // - Check that A is inactive and B is active + // - Reactivate A with a click + // - Consume B's transient user activation with `window.open` + // - Check that A is active and B is inactive + + const B = attachFencedFrameContext(); + + // Check that both frames are inactive after loading. + assert_activations(false, false, "A"); + await B.execute(assert_activations, [false, false, "B"]); + + // Send a click to activate the top-level frame, and check user activation. + await click(document.documentElement); + assert_activations(true, true, "A"); + await B.execute(assert_activations, [false, false, "B"]); + + // Send a click to activate the fenced frame, and check user activation. + await click(B.element); + assert_activations(true, true, "A"); + await B.execute(assert_activations, [true, true, "B"]); + + // Open a window to consume the top-level transient user activation. + window.open('about:blank'); + + // Check that it consumed the navigation in only the top-level frame. + assert_activations(true, false, "A"); + await B.execute(assert_activations, [true, true, "B"]); + + // Reactivate this frame and check the user activation status. + await click(document.documentElement); + assert_activations(true, true, "A"); + await B.execute(assert_activations, [true, true, "B"]); + + // Open a window in the fenced frame to consume its transient activation. + await B.execute(() => { + window.open('about:blank'); + }); + + // Check that B's transient user activation was consumed. + assert_activations(true, true, "A"); + await B.execute(assert_activations, [true, false, "B"]); + +}, 'user-activation'); +</script> +</body> diff --git a/testing/web-platform/tests/fenced-frame/content-index.https.html b/testing/web-platform/tests/fenced-frame/content-index.https.html new file mode 100644 index 0000000000..99af848492 --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/content-index.https.html @@ -0,0 +1,150 @@ +<!DOCTYPE html> +<title>Test Content Index API</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="/common/utils.js"></script> +<script src="/common/dispatcher/dispatcher.js"></script> +<script src="resources/utils.js"></script> + +<body> +<script> +const id = 'fenced-frame-id'; + +promise_test(async () => { + const frame = attachFencedFrameContext(); + try { + await frame.execute(async (id) => { + navigator.serviceWorker.register( + "empty-worker.js", { scope: location.href }); + const registration = await navigator.serviceWorker.ready; + return registration.index.add({ + id, + title: 'same title', + description: 'same description', + url: 'resources/' + }); + }, [id]); + assert_unreached('index.add executed without error; want error'); + } catch(e) { + assert_equals(e.message, "Failed to execute 'add' on 'ContentIndex': " + + "ContentIndex is not allowed in fenced frames.") + } +}, 'index.add should fail inside a fenced frame'); + +promise_test(async () => { + const frame = attachFencedFrameContext(); + try { + await frame.execute(async (id) => { + navigator.serviceWorker.register( + "empty-worker.js", { scope: location.href }); + const registration = await navigator.serviceWorker.ready; + return registration.index.delete(id); + }, [id]); + assert_unreached('index.delete executed without error; want error'); + } catch(e) { + assert_equals(e.message, "Failed to execute 'delete' on 'ContentIndex': " + "ContentIndex is not allowed in fenced frames."); + } +}, 'index.delete should fail inside a fenced frame'); + +promise_test(async () => { + const frame = attachFencedFrameContext(); + try { + await frame.execute(async () => { + navigator.serviceWorker.register( + "empty-worker.js", { scope: location.href }); + const registration = await navigator.serviceWorker.ready; + return registration.index.getAll(); + }); + assert_unreached('index.getAll executed without error; want error'); + } catch(e) { + assert_equals(e.message, "Failed to execute 'getAll' on 'ContentIndex': " + "ContentIndex is not allowed in fenced frames."); + } +}, 'index.getAll should fail inside a fenced frame'); + +promise_test(async () => { + const frame = attachFencedFrameContext(); + const message = await frame.execute(async () => { + const getController = () => { + if (navigator.serviceWorker.controller) { + return navigator.serviceWorker.controller; + } + return new Promise(resolve => { + navigator.serviceWorker.addEventListener('controllerchange', () => { + resolve(navigator.serviceWorker.controller); + }); + }); + }; + + navigator.serviceWorker.register( + "content-index-sw.js", { scope: location.href }); + return new Promise(async resolve => { + const ctrl = await getController(); + ctrl.postMessage('add'); + navigator.serviceWorker.onmessage = e => { + resolve(e.data); + } + }); + }); + assert_equals(message, "Failed to execute 'add' on 'ContentIndex': " + + "ContentIndex is not allowed in fenced frames."); +}, 'index.add should fail from the service worker inside a fenced frame'); + +promise_test(async () => { + const frame = attachFencedFrameContext(); + const message = await frame.execute(async () => { + const getController = () => { + if (navigator.serviceWorker.controller) { + return navigator.serviceWorker.controller; + } + return new Promise(resolve => { + navigator.serviceWorker.addEventListener('controllerchange', () => { + resolve(navigator.serviceWorker.controller); + }); + }); + }; + + navigator.serviceWorker.register( + "content-index-sw.js", { scope: location.href }); + return new Promise(async resolve => { + const ctrl = await getController(); + ctrl.postMessage('delete'); + navigator.serviceWorker.onmessage = e => { + resolve(e.data); + } + }); + }); + assert_equals(message, "Failed to execute 'delete' on 'ContentIndex': " + + "ContentIndex is not allowed in fenced frames."); +}, 'index.delete should fail from the service worker inside a fenced frame'); + +promise_test(async () => { + const frame = attachFencedFrameContext(); + const message = await frame.execute(async () => { + const getController = () => { + if (navigator.serviceWorker.controller) { + return navigator.serviceWorker.controller; + } + return new Promise(resolve => { + navigator.serviceWorker.addEventListener('controllerchange', () => { + resolve(navigator.serviceWorker.controller); + }); + }); + }; + + navigator.serviceWorker.register( + "content-index-sw.js", { scope: location.href }); + return new Promise(async resolve => { + const ctrl = await getController(); + ctrl.postMessage('getAll'); + navigator.serviceWorker.onmessage = e => { + resolve(e.data); + } + }); + }); + assert_equals(message, "Failed to execute 'getAll' on 'ContentIndex': " + + "ContentIndex is not allowed in fenced frames."); +}, 'index.getAll should fail from the service worker inside a fenced frame'); +</script> +</body> diff --git a/testing/web-platform/tests/fenced-frame/coop-bcg-swap.https.html b/testing/web-platform/tests/fenced-frame/coop-bcg-swap.https.html new file mode 100644 index 0000000000..5a414fdfa1 --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/coop-bcg-swap.https.html @@ -0,0 +1,40 @@ +<!DOCTYPE html> +<title>Test window.name after bcg swap</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/common/dispatcher/dispatcher.js"></script> +<script src="/common/utils.js"></script> +<script src="resources/utils.js"></script> + +<body> +<script> +promise_test(async () => { + const fenced_frame = attachFencedFrameContext(); + + // This test verifies that COOP is not enabled in fenced frames. To do so, we + // set the fenced frame's `window.name` and navigate it to a cross-origin page + // with the `Cross-Origin-Opener-Policy: same-origin` response header. + await fenced_frame.execute(() => { + const remote_origin = get_host_info().REMOTE_ORIGIN; + window.name = "test"; + const remote_url = new URL(location.pathname + location.search, remote_origin); + + let existing_pipe_query = remote_url.searchParams.get('pipe'); + const all_pipes = existing_pipe_query.split('|'); + all_pipes.push('header(Cross-Origin-Opener-Policy, same-origin)'); + + remote_url.searchParams.set('pipe', all_pipes.join('|')); + + window.executor.suspend(() => { + location.href = remote_url; + }); + }); + + // Verify that the fenced frame's `window.name` is still there + await fenced_frame.execute(() => { + assert_equals(window.name, 'test'); + }); + +}, "window.name after bcg swap"); +</script> +</body> diff --git a/testing/web-platform/tests/fenced-frame/create-credential.https.html b/testing/web-platform/tests/fenced-frame/create-credential.https.html new file mode 100644 index 0000000000..15f0558e52 --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/create-credential.https.html @@ -0,0 +1,25 @@ +<!DOCTYPE html> +<title>Test WebAuthn navigator.credentials.create()</title> +<meta name="timeout" content="long"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/common/utils.js"></script> +<script src="resources/utils.js"></script> + +<body> + +<script> +promise_test(async () => { + const key = token(); + attachFencedFrame( + generateURL('resources/create-credential-inner.https.html', [key])); + // Get the result for the fenced frame. + const fenced_frame_result = await nextValueFromServer(key); + assert_equals( + fenced_frame_result, + 'createCredential failed', + 'credentials.create should fail on fenced frame'); +}, 'navigator.credentials.create'); +</script> + +</body> diff --git a/testing/web-platform/tests/fenced-frame/create-in-sandbox-and-adopt-outside-sandbox.https.html b/testing/web-platform/tests/fenced-frame/create-in-sandbox-and-adopt-outside-sandbox.https.html new file mode 100644 index 0000000000..4b4817b863 --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/create-in-sandbox-and-adopt-outside-sandbox.https.html @@ -0,0 +1,35 @@ +<!DOCTYPE html> +<title>Test fenced frame sandbox adoption</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/utils.js"></script> +<script src="/common/utils.js"></script> + +<body> +<script> +promise_test(async() => { + const fenced_frame_loaded_key = token(); + + const iframe = document.createElement('iframe'); + iframe.setAttribute('sandbox', 'allow-same-origin'); + document.body.append(iframe); + + const inner_document = iframe.contentDocument; + const fenced_frame = inner_document.createElement('fencedframe'); + // The `inner_document` is not suitable to host a fenced frame because its + // sandbox flags are too strict. + inner_document.body.append(fenced_frame); + + // Per https://dom.spec.whatwg.org/#concept-node-append, this will adopt the + // inner fenced frame into the outer (main frame) document. + document.body.append(fenced_frame); + fenced_frame.config = + new FencedFrameConfig(generateURL( + 'resources/fenced-frame-loaded.html', [fenced_frame_loaded_key])); + const response = await nextValueFromServer(fenced_frame_loaded_key); + assert_equals(response, "fenced frame loaded", + "The inner frame should be loaded."); +}, "Adopting a fenced frame from a too-strict document to a suitable " + + "document leaves the frame in a functional state"); +</script> +</body> diff --git a/testing/web-platform/tests/fenced-frame/csp-allowed.https.html b/testing/web-platform/tests/fenced-frame/csp-allowed.https.html new file mode 100644 index 0000000000..8c002bc8a9 --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/csp-allowed.https.html @@ -0,0 +1,40 @@ +<!DOCTYPE html> +<title>Test opaque fenced frame navigations with allowed CSP</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/common/utils.js"></script> +<script src="resources/utils.js"></script> + +<body> +<script> +for (const resolve_to_config of [true, false]) { + const allowedCSPs = ["*", "https:", "https://*:*"]; + allowedCSPs.forEach((csp) => { + promise_test(async() => { + setupCSP(csp); + + const key = token(); + window.addEventListener('securitypolicyviolation', function(e) { + // Write to the server even though the listener is in the same file in + // the test below. + writeValueToServer(key, e.violatedDirective + ";" + e.blockedURI); + }, {once: true}); + + attachFencedFrame(await runSelectURL("resources/embeddee.html", + [key], resolve_to_config)); + + const result = await nextValueFromServer(key); + assert_equals(result, "PASS", + "The fenced frame should load for CSP fenced-frame-src " + csp); + }, "Fenced frame loaded for CSP fenced-frame-src " + csp + " using " + + (resolve_to_config ? "config" : "urn:uuid")); + + promise_test(async() => { + setupCSP(csp); + assert_true(navigator.canLoadAdAuctionFencedFrame()); + }, "Opaque-ads can load API returns true for " + csp + " using " + + (resolve_to_config ? "config" : "urn:uuid")); + }); +} +</script> +</body> diff --git a/testing/web-platform/tests/fenced-frame/csp-blocked.https.html b/testing/web-platform/tests/fenced-frame/csp-blocked.https.html new file mode 100644 index 0000000000..3826fdd7f4 --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/csp-blocked.https.html @@ -0,0 +1,65 @@ +<!DOCTYPE html> +<title>Test opaque fenced frame navigations with disallowed CSP blocked</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/common/utils.js"></script> +<script src="resources/utils.js"></script> + +<body> +<script> +for (const resolve_to_config of [true, false]) { + const blockedCSPs = ["'none'", "'self'", "data:", "https://*", "https://*:80", + "https://b.test:*"]; + blockedCSPs.forEach((csp) => { + promise_test(async() => { + setupCSP(csp); + + const key = token(); + window.addEventListener('securitypolicyviolation', function(e) { + // Write to the server even though the listener is in the same file in + // the test below. + writeValueToServer(key, e.violatedDirective + ";" + e.blockedURI); + }, {once: true}); + + attachFencedFrame(await runSelectURL("resources/embeddee.html", + [key], resolve_to_config)); + + const result = await nextValueFromServer(key); + assert_equals(result, "fenced-frame-src;", + "The fenced frame should not load for CSP fenced-frame-src " + csp); + }, "Fenced frame blocked for CSP fenced-frame-src " + csp + " using " + + (resolve_to_config ? "config" : "urn:uuid")); + + promise_test(async() => { + setupCSP(csp); + assert_false(navigator.canLoadAdAuctionFencedFrame()); + }, "Opaque-ads can load API returns false for " + csp + " using " + + (resolve_to_config ? "config" : "urn:uuid")); + }); + + promise_test(async() => { + setupCSP("*", "'self'"); + + const key = token(); + window.addEventListener('securitypolicyviolation', function(e) { + // Write to the server even though the listener is in the same file in + // the test below. + writeValueToServer(key, e.violatedDirective + ";" + e.blockedURI); + }, {once: true}); + + attachFencedFrame(await runSelectURL("resources/embeddee.html", + [key], resolve_to_config)); + + const result = await nextValueFromServer(key); + assert_equals(result, "fenced-frame-src;", + "The fenced frame should not load for CSP frame-src 'self' even if " + + "another CSP allows loading a fenced frame."); + + // Test the canLoadOpaqueURL API to ensure it arrives at the same result. + assert_false(navigator.canLoadAdAuctionFencedFrame()); + }, "Fenced frame not loaded using " + + (resolve_to_config ? "config" : "urn:uuid") + + " if any of CSPs in place disallow loading"); +} +</script> +</body> diff --git a/testing/web-platform/tests/fenced-frame/csp-fenced-frame-src-allowed.https.html b/testing/web-platform/tests/fenced-frame/csp-fenced-frame-src-allowed.https.html new file mode 100644 index 0000000000..cf603c29a6 --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/csp-fenced-frame-src-allowed.https.html @@ -0,0 +1,22 @@ +<!DOCTYPE html> +<meta http-equiv="Content-Security-Policy" content="fenced-frame-src 'self'"> +<title>Test Content-Security-Policy fenced-frame-src</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/utils.js"></script> +<script src="/common/utils.js"></script> + +<body> +<script> +const key = token(); + +promise_test(async () => { + attachFencedFrame(generateURL( + "resources/csp-fenced-frame-src-allowed-inner.html", + [key])); + const result = await nextValueFromServer(key); + assert_equals(result, "loaded", + "The fenced frame is loaded as expected"); +}, "csp-fenced-frame-src-allowed"); +</script> +</body> diff --git a/testing/web-platform/tests/fenced-frame/csp-fenced-frame-src-blocked.https.html b/testing/web-platform/tests/fenced-frame/csp-fenced-frame-src-blocked.https.html new file mode 100644 index 0000000000..46f8778d47 --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/csp-fenced-frame-src-blocked.https.html @@ -0,0 +1,37 @@ +<!DOCTYPE html> +<meta http-equiv="Content-Security-Policy" content="fenced-frame-src 'none'"> +<title>Test Content-Security-Policy fenced-frame-src</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/utils.js"></script> +<script src="/common/utils.js"></script> + +<body> +<script> +const key = token(); + +window.addEventListener('securitypolicyviolation', function(e) { + // Write to the server even though the listener is in the same file in the + // test below. + writeValueToServer(key, e.violatedDirective + ";" + e.blockedURI); +}); + +promise_test(async () => { + attachFencedFrame(generateURL( + "resources/csp-fenced-frame-src-blocked-inner.html", + [key])); + const result = await nextValueFromServer(key); + + const expected_blocked_uri = generateURL( + "resources/csp-fenced-frame-src-blocked-inner.html", + [key]).toString(); + assert_equals(result, "fenced-frame-src;" + expected_blocked_uri, + "The fenced frame is blocked because of CSP violation"); +}, "csp-fenced-frame-src-blocked"); + +promise_test(async () => { + assert_false(navigator.canLoadAdAuctionFencedFrame()); +}, "fenced-frame-src none is taken into account with " + + "navigator.canLoadAdAuctionFencedFrame"); +</script> +</body> diff --git a/testing/web-platform/tests/fenced-frame/csp-frame-src-allowed.https.html b/testing/web-platform/tests/fenced-frame/csp-frame-src-allowed.https.html new file mode 100644 index 0000000000..08c51fc3ac --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/csp-frame-src-allowed.https.html @@ -0,0 +1,22 @@ +<!DOCTYPE html> +<meta http-equiv="Content-Security-Policy" content="frame-src 'self'"> +<title>Test Content-Security-Policy fenced-frame-src falling back to frame-src</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/utils.js"></script> +<script src="/common/utils.js"></script> + +<body> +<script> +const key = token(); + +promise_test(async () => { + attachFencedFrame(generateURL( + "resources/csp-frame-src-allowed-inner.html", + [key])); + const result = await nextValueFromServer(key); + assert_equals(result, "loaded", + "The fenced frame is loaded as expected"); +}, "csp-frame-src-allowed"); +</script> +</body> diff --git a/testing/web-platform/tests/fenced-frame/csp-frame-src-blocked.https.html b/testing/web-platform/tests/fenced-frame/csp-frame-src-blocked.https.html new file mode 100644 index 0000000000..88e4a8b89f --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/csp-frame-src-blocked.https.html @@ -0,0 +1,36 @@ +<!DOCTYPE html> +<meta http-equiv="Content-Security-Policy" content="img-src 'self' https: https://*:*"> +<meta http-equiv="Content-Security-Policy" content="frame-src 'none'"> +<title>Test Content-Security-Policy fenced-frame-src falling back to frame-src</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/utils.js"></script> +<script src="/common/utils.js"></script> + +<body> +<script> +const key = token(); + +window.addEventListener('securitypolicyviolation', function(e) { + // Write to the server even though the listener is in the same file in the + // test below. + writeValueToServer(key, e.violatedDirective + ";" + e.blockedURI); +}); + +promise_test(async () => { + attachFencedFrame(generateURL( + "resources/csp-frame-src-blocked-inner.html", + [key])); + const result = await nextValueFromServer(key); + + const expected_blocked_uri = generateURL( + "resources/csp-frame-src-blocked-inner.html", [key]).toString(); + assert_equals(result, "fenced-frame-src;" + expected_blocked_uri, + "The fenced frame is blocked because of CSP violation"); +}, "csp-frame-src-blocked"); + +promise_test(async () => { + assert_false(navigator.canLoadAdAuctionFencedFrame()); +}, "frame-src none is taken into account with navigator.canLoadAdAuctionFencedFrame"); +</script> +</body> diff --git a/testing/web-platform/tests/fenced-frame/csp-transparent-url.https.html b/testing/web-platform/tests/fenced-frame/csp-transparent-url.https.html new file mode 100644 index 0000000000..c1c815d49e --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/csp-transparent-url.https.html @@ -0,0 +1,53 @@ +<!DOCTYPE html> +<title>Test transparent url navigated in fenced frame interacting with CSP</title> +<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> + +<body> +<script> +function setupCSP(csp) { + let meta = document.createElement('meta'); + meta.httpEquiv = "Content-Security-Policy"; + meta.content = "fenced-frame-src " + csp; + document.head.appendChild(meta); +} + +const allowedCSPs = ["*", "https:", "'self'"]; +allowedCSPs.forEach((csp) => { + promise_test(async(t) => { + setupCSP(csp); + + t.step_timeout(t.unreached_func( + "The fenced frame should load for CSP fenced-frame-src " + csp), 3000); + + const fencedframe = attachFencedFrameContext(); + await fencedframe.execute(() => {}); + }, "Fenced frame loaded for CSP fenced-frame-src " + csp); +}); + +const blockedCSPs = ["'none'"]; +blockedCSPs.forEach((csp) => { + promise_test(async(t) => { + setupCSP(csp); + + const csp_violation = new Promise(resolve => { + window.addEventListener("securitypolicyviolation", resolve); + }); + + const fencedframe = attachFencedFrameContext(); + + const fencedframe_loaded = fencedframe.execute(() => {}); + fencedframe_loaded.then(t.unreached_func( + "The fenced frame should not load for CSP fenced-frame-src " + csp)); + + const csp_violation_event = await csp_violation; + const remote_url = getRemoteContextURL(location.origin).toString(); + assert_true(csp_violation_event.blockedURI.includes(remote_url), + "blockedURI should include the url"); + }, "Fenced frame blocked for CSP fenced-frame-src " + csp); +}); +</script> +</body> diff --git a/testing/web-platform/tests/fenced-frame/csp.https.html b/testing/web-platform/tests/fenced-frame/csp.https.html new file mode 100644 index 0000000000..6daa6a96e8 --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/csp.https.html @@ -0,0 +1,39 @@ +<!DOCTYPE html> + <title>Test Content Security Policy</title> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + <script src="resources/utils.js"></script> + <script src="/common/utils.js"></script> + + <body> + + <script> + promise_test(async () => { + const csp_key = token(); + + // The 'csp' property does not appear in the IDL definition for + // fenced frames, so ensure that the 'csp' property didn't + // leak over from the IFrame prototype. + assert_equals(HTMLFencedFrameElement.prototype.hasOwnProperty('csp'), + false); + + const new_frame = document.createElement('fencedframe'); + const new_config = new FencedFrameConfig(generateURL( + "resources/csp-inner.html", + [csp_key])); + new_frame.config = new_config; + + // This attribute will be ignored since the IDL for + // fenced frames do not support the 'csp' attribute. + new_frame.setAttribute("csp", "style-src 'none';"); + document.body.append(new_frame); + + // Get the result for the top-level fenced frame. + const fenced_frame_result = await nextValueFromServer(csp_key); + assert_equals(fenced_frame_result, "pass"); + + }, "Fenced Frames should not honor the csp attribute from parent page"); + </script> + + </body> +</html> diff --git a/testing/web-platform/tests/fenced-frame/cspee.https.html b/testing/web-platform/tests/fenced-frame/cspee.https.html new file mode 100644 index 0000000000..7df3e882fa --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/cspee.https.html @@ -0,0 +1,58 @@ +<!DOCTYPE html> +<title>Test fenced frame in CSPEE</title> +<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> + +<body> +<script> +promise_test(async(t) => { + const iframe = attachIFrameContext({attributes:[["csp", "frame-src *"]]}); + t.step_timeout(() => t.done(), 1000); + await iframe.execute(async (t) => { + const fencedframe = attachFencedFrameContext(); + await fencedframe.execute(() => {}); + }); + assert_unreached("fenced frame should not be loaded."); +}, 'fenced frame should not be loaded in CSPEE'); + +promise_test(async(t) => { + const iframe_a = attachIFrameContext({attributes:[["csp", "frame-src *"]]}); + t.step_timeout(() => t.done(), 1000); + await iframe_a.execute(async (t) => { + const iframe_b = attachIFrameContext(); + await iframe_b.execute(async (t) => { + const fencedframe = attachFencedFrameContext(); + await fencedframe.execute(() => {}); + }); + }); + assert_unreached("fenced frame should not be loaded."); +}, 'fenced frame should not be loaded if any ancestor has CSPEE'); + +promise_test(async(t) => { + const iframe = attachIFrameContext({attributes:[["csp", "frame-src *"]]}); + await iframe.execute(async (t) => { + assert_false(navigator.canLoadAdAuctionFencedFrame()); + }); +}, 'canLoadOpaqueURL considers CSPEE headers'); + +promise_test(async(t) => { + const iframe_a = attachIFrameContext({attributes:[["csp", "frame-src *"]]}); + await iframe_a.execute(async (t) => { + const iframe_b = attachIFrameContext(); + await iframe_b.execute(async (t) => { + assert_false(navigator.canLoadAdAuctionFencedFrame()); + }); + }); +}, 'canLoadOpaqueURL considers CSPEE headers up the ancestor chain'); + +promise_test(async(t) => { + const iframe = attachIFrameContext(); + await iframe.execute(async (t) => { + assert_true(navigator.canLoadAdAuctionFencedFrame()); + }); +}, 'canLoadOpaqueURL returns true if no CSPEE headers are present in iframe'); +</script> +</body> diff --git a/testing/web-platform/tests/fenced-frame/default-enabled-features-allow-all.https.html b/testing/web-platform/tests/fenced-frame/default-enabled-features-allow-all.https.html new file mode 100644 index 0000000000..f5084cf386 --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/default-enabled-features-allow-all.https.html @@ -0,0 +1,27 @@ +<!DOCTYPE html> +<title>Test default permission policy features gating (*)</title> +<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> +<script src="resources/default-enabled-features-helper.js"></script> + +<body> +<script> +promise_test(async(t) => { + await runDefaultEnabledFeaturesTest(t, true, get_host_info().ORIGIN); + await runDefaultEnabledFeaturesTest(t, true, get_host_info().ORIGIN, + generator_api="sharedstorage"); +}, 'Same-origin fenced frame loads when feature policies are *'); + +promise_test(async(t) => { + await runDefaultEnabledFeaturesTest(t, true, get_host_info().REMOTE_ORIGIN); + await runDefaultEnabledFeaturesTest(t, true, get_host_info().REMOTE_ORIGIN, + generator_api="sharedstorage"); +}, 'Cross-origin fenced frame loads when feature policies are *'); + +</script> +</body> +</html> diff --git a/testing/web-platform/tests/fenced-frame/default-enabled-features-allow-all.https.html.headers b/testing/web-platform/tests/fenced-frame/default-enabled-features-allow-all.https.html.headers new file mode 100644 index 0000000000..d39cf98dd5 --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/default-enabled-features-allow-all.https.html.headers @@ -0,0 +1 @@ +Permissions-Policy: attribution-reporting=(*), shared-storage=(*)
\ No newline at end of file diff --git a/testing/web-platform/tests/fenced-frame/default-enabled-features-allow-none.https.html b/testing/web-platform/tests/fenced-frame/default-enabled-features-allow-none.https.html new file mode 100644 index 0000000000..d44cb5bca7 --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/default-enabled-features-allow-none.https.html @@ -0,0 +1,25 @@ +<!DOCTYPE html> +<title>Test default permission policy features gating ()</title> +<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> +<script src="resources/default-enabled-features-helper.js"></script> + +<body> +<script> +// Note: Shared storage will refuse to run selectURL() on this page because its +// permissions policy is disabled. Therefore, we can only test the FLEDGE case. +promise_test(async(t) => { + await runDefaultEnabledFeaturesTest(t, false, get_host_info().ORIGIN); +}, 'Same-origin fenced frame does not load when feature policies are none'); + +promise_test(async(t) => { + await runDefaultEnabledFeaturesTest(t, false, get_host_info().REMOTE_ORIGIN); +}, 'Cross-origin fenced frame does not load when feature policies are none'); + +</script> +</body> +</html> diff --git a/testing/web-platform/tests/fenced-frame/default-enabled-features-allow-none.https.html.headers b/testing/web-platform/tests/fenced-frame/default-enabled-features-allow-none.https.html.headers new file mode 100644 index 0000000000..5d534499d7 --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/default-enabled-features-allow-none.https.html.headers @@ -0,0 +1 @@ +Permissions-Policy: attribution-reporting=(), shared-storage=()
\ No newline at end of file diff --git a/testing/web-platform/tests/fenced-frame/default-enabled-features-allow-self.https.html b/testing/web-platform/tests/fenced-frame/default-enabled-features-allow-self.https.html new file mode 100644 index 0000000000..0360963a0b --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/default-enabled-features-allow-self.https.html @@ -0,0 +1,73 @@ +<!DOCTYPE html> +<title>Test default permission policy features gating (self)</title> +<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> +<script src="resources/default-enabled-features-helper.js"></script> + +<body> +<script> +promise_test(async(t) => { + await runDefaultEnabledFeaturesTest(t, true, get_host_info().ORIGIN); + await runDefaultEnabledFeaturesTest(t, true, get_host_info().ORIGIN, + generator_api="sharedstorage"); +}, 'Same-origin fenced frame loads when feature policies are self'); + +promise_test(async(t) => { + await runDefaultEnabledFeaturesTest(t, false, get_host_info().REMOTE_ORIGIN); + await runDefaultEnabledFeaturesTest(t, false, get_host_info().REMOTE_ORIGIN, + generator_api="sharedstorage"); +}, 'Cross-origin fenced frame does not load when feature policies are self'); + +promise_test(async(t) => { + const fencedframe = await attachFencedFrameContext({ + origin: get_host_info().ORIGIN}); + + await fencedframe.execute(async () => { + assert_false( + document.featurePolicy.allowsFeature('shared-storage'), + "Shared storage should be disallowed in the fenced frame."); + assert_false( + document.featurePolicy.allowsFeature('attribution-reporting'), + "Attribution reporting should be disallowed in the fenced frame."); + assert_false( + document.featurePolicy.allowsFeature('sync-xhr'), + "USB access should be disallowed in the fenced frame."); + }, []); +}, 'Fenced frames default feature policies are set to not allow anything.'); + +promise_test(async(t) => { + // We do this test the "old fashioned way" because a redirect in a fenced + // frame remote context will cause it to lose its ability to communicate with + // the main page (which results in a timeout). + const page1_key = token(); + const redirect_key = token(); + + const fencedframe = attachFencedFrame( + await generateURNFromFledge( + "resources/default-enabled-features-redirect.https.html", + [page1_key, redirect_key])); + + // The fenced frame will send its attribution reporting result and then + // attempt to redirect to a remote origin page. + const page1_resp = await nextValueFromServer(page1_key); + assert_equals(page1_resp, "true", + "Attribution reporting should be enabled on the original page."); + + // There is no API to observe whether the document in the fenced frame loaded + // or not. Instead, set up a timeout. If the document loads, "FAIL" will be + // sent to the server. Otherwise "blocked" will be sent after 2 seconds. + const fencedframe_blocked = new Promise(r => t.step_timeout(r, 1000)); + assert_equals("blocked", await Promise.any([ + nextValueFromServer(redirect_key).then(() => "loaded"), + fencedframe_blocked.then(() => "blocked") + ]), "The fenced frame redirect should not be successful."); +}, 'A fenced frame redirected to a page that does not allow feature policies ' + + 'does not navigate'); + +</script> +</body> +</html> diff --git a/testing/web-platform/tests/fenced-frame/default-enabled-features-allow-self.https.html.headers b/testing/web-platform/tests/fenced-frame/default-enabled-features-allow-self.https.html.headers new file mode 100644 index 0000000000..8bfa33eb8a --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/default-enabled-features-allow-self.https.html.headers @@ -0,0 +1 @@ +Permissions-Policy: attribution-reporting=(self), shared-storage=(self)
\ No newline at end of file diff --git a/testing/web-platform/tests/fenced-frame/default-enabled-features-attribute-allow.https.html b/testing/web-platform/tests/fenced-frame/default-enabled-features-attribute-allow.https.html new file mode 100644 index 0000000000..3c3831f015 --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/default-enabled-features-attribute-allow.https.html @@ -0,0 +1,49 @@ +<!DOCTYPE html> +<title>Test default permission policy features with allow="" attribute</title> +<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> +<script src="resources/default-enabled-features-helper.js"></script> + +<body> +<script> +promise_test(async(t) => { + await runDefaultEnabledFeaturesTest(t, /*should_load=*/true, + get_host_info().ORIGIN, + generator_api="fledge", + allow="shared-storage *; attribution-reporting *"); + await runDefaultEnabledFeaturesTest(t, /*should_load=*/true, + get_host_info().ORIGIN, + generator_api="sharedstorage", + allow="shared-storage *; attribution-reporting *"); +}, 'Same-origin fenced frame with allow attribute enabling required features'); + +promise_test(async(t) => { + await runDefaultEnabledFeaturesTest(t, /*should_load=*/true, + get_host_info().REMOTE_ORIGIN, + generator_api="fledge", + allow="shared-storage *; attribution-reporting *"); + await runDefaultEnabledFeaturesTest(t, /*should_load=*/true, + get_host_info().REMOTE_ORIGIN, + generator_api="sharedstorage", + allow="shared-storage *; attribution-reporting *"); +}, 'Cross-origin fenced frame with allow attribute enabling required features'); + +promise_test(async(t) => { + const fencedframe = await attachFencedFrameContext({ + generator_api: "fledge", + headers: [["Permissions-Policy", "attribution-reporting=()"]], + origin: get_host_info().ORIGIN}); + + await fencedframe.execute(async () => { + assert_false(document.featurePolicy.allowsFeature('attribution-reporting'), + "Attribution reporting should NOT be allowed in the fenced frame."); + }, []); +}, 'Delivered policies can further restrict permissions of a fenced frame'); + +</script> +</body> +</html> diff --git a/testing/web-platform/tests/fenced-frame/default-enabled-features-attribute-change.https.html b/testing/web-platform/tests/fenced-frame/default-enabled-features-attribute-change.https.html new file mode 100644 index 0000000000..9878e615ad --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/default-enabled-features-attribute-change.https.html @@ -0,0 +1,48 @@ +<!DOCTYPE html> +<title>Test changing the allow="" attribute after a navigation</title> +<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> +<script src="resources/default-enabled-features-helper.js"></script> + +<body> +<script> +promise_test(async(t) => { + const fencedframe = await attachFencedFrameContext({ + generator_api: 'fledge', + attributes: [["allow", "attribution-reporting *"]], + origin: get_host_info().ORIGIN}); + + fencedframe.element.allow = "attribution-reporting 'none'"; + + await fencedframe.execute(async () => { + assert_true(document.featurePolicy.allowsFeature('attribution-reporting'), + "Changing the allow attribute should do nothing for this navigation."); + }, []); +}, 'Changing the allow attribute is a no-op for the current navigation'); + +promise_test(async(t) => { + const fencedframe = await attachFencedFrameContext({ + generator_api: 'fledge', + attributes: [["allow", "attribution-reporting *"]], + origin: get_host_info().ORIGIN}); + + fencedframe.element.allow = "attribution-reporting 'none'"; + + await fencedframe.execute(async () => { + location.reload(); + }, []); + + await fencedframe.execute(async () => { + assert_true(document.featurePolicy.allowsFeature('attribution-reporting'), + "Changing the allow attribute should do nothing on frame refresh."); + }, []); + +}, 'Changing the allow attribute is a no-op for frame-initiated navigations'); + +</script> +</body> +</html> diff --git a/testing/web-platform/tests/fenced-frame/default-enabled-features-attribute-disallow.https.html b/testing/web-platform/tests/fenced-frame/default-enabled-features-attribute-disallow.https.html new file mode 100644 index 0000000000..f9ef9b6db2 --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/default-enabled-features-attribute-disallow.https.html @@ -0,0 +1,33 @@ +<!DOCTYPE html> +<title>Test default permission policy features with allow="" attribute</title> +<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> +<script src="resources/default-enabled-features-helper.js"></script> + +<body> +<script> +promise_test(async(t) => { + await runDefaultEnabledFeaturesTest(t, /*should_load=*/false, + get_host_info().ORIGIN, generator_api="fledge", + allow="private-aggregation 'none'"); + await runDefaultEnabledFeaturesTest(t, /*should_load=*/false, + get_host_info().ORIGIN, generator_api="sharedstorage", + allow="shared-storage 'none'"); +}, 'Same-origin fenced frame with allow attribute disabling required feature'); + +promise_test(async(t) => { + await runDefaultEnabledFeaturesTest(t, /*should_load=*/false, + get_host_info().REMOTE_ORIGIN, generator_api="fledge", + allow="private-aggregation 'none'"); + await runDefaultEnabledFeaturesTest(t, /*should_load=*/false, + get_host_info().REMOTE_ORIGIN, generator_api="sharedstorage", + allow="shared-storage 'none'"); +}, 'Cross-origin fenced frame with allow attribute disabling required feature'); + +</script> +</body> +</html> diff --git a/testing/web-platform/tests/fenced-frame/default-enabled-features-attribution-disabled.https.html b/testing/web-platform/tests/fenced-frame/default-enabled-features-attribution-disabled.https.html new file mode 100644 index 0000000000..9e037e24a3 --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/default-enabled-features-attribution-disabled.https.html @@ -0,0 +1,28 @@ +<!DOCTYPE html> +<title>Test where attribution-reporting is disabled in the top-level page</title> +<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> +<script src="resources/default-enabled-features-helper.js"></script> + +<body> +<script> +promise_test(async(t) => { + await runDefaultEnabledFeaturesTest(t, /*should_load=*/false, + get_host_info().ORIGIN, generator_api="fledge", + allow="attribution-reporting *;"); +}, 'Same-origin fenced frame with allow attribute enabling required feature ' + + 'but page disabling feature.'); + +promise_test(async(t) => { + await runDefaultEnabledFeaturesTest(t, /*should_load=*/false, + get_host_info().REMOTE_ORIGIN, generator_api="fledge", + allow="attribution-reporting *;"); +}, 'Cross-origin fenced frame with allow attribute enabling required feature ' + + 'but page disabling feature.'); +</script> +</body> +</html> diff --git a/testing/web-platform/tests/fenced-frame/default-enabled-features-attribution-disabled.https.html.headers b/testing/web-platform/tests/fenced-frame/default-enabled-features-attribution-disabled.https.html.headers new file mode 100644 index 0000000000..af6d6ecb2e --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/default-enabled-features-attribution-disabled.https.html.headers @@ -0,0 +1 @@ +Permissions-Policy: attribution-reporting=()
\ No newline at end of file diff --git a/testing/web-platform/tests/fenced-frame/default-enabled-features-subframe.https.html b/testing/web-platform/tests/fenced-frame/default-enabled-features-subframe.https.html new file mode 100644 index 0000000000..a86a02b00a --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/default-enabled-features-subframe.https.html @@ -0,0 +1,43 @@ +<!DOCTYPE html> +<title>Test nested iframes inheriting permissions from fenced frames</title> +<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> + +<body> +<script> +promise_test(async(t) => { + const key = token(); + + const urn = await runSelectURL( + 'resources/default-enabled-features-subframe-fencedframe.https.html', + [key, /*should_restrict_select_url=*/false]); + const fencedframe = await attachFencedFrame(urn); + + const result = await nextValueFromServer(key); + const [allows_shared_storage, allows_select_url] = result.split(","); + + assert_equals(allows_shared_storage, "false"); + assert_equals(allows_select_url, "true"); +}, 'Iframes nested in fenced frames inherit the fenced frame policies'); + +promise_test(async(t) => { + const key = token(); + + const urn = await runSelectURL( + 'resources/default-enabled-features-subframe-fencedframe.https.html', + [key, /*should_restrict_select_url=*/true]); + const fencedframe = await attachFencedFrame(urn); + + const result = await nextValueFromServer(key); + const [allows_shared_storage, allows_select_url] = result.split(","); + + assert_equals(allows_shared_storage, "false"); + assert_equals(allows_select_url, "false"); +}, 'Iframes nested in fenced frames can further restrict policies'); +</script> +</body> +</html> diff --git a/testing/web-platform/tests/fenced-frame/default-enabled-features-unset.https.html b/testing/web-platform/tests/fenced-frame/default-enabled-features-unset.https.html new file mode 100644 index 0000000000..b438d1ff35 --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/default-enabled-features-unset.https.html @@ -0,0 +1,29 @@ +<!DOCTYPE html> +<title>Test default permission policy features gating unset</title> +<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> +<script src="resources/default-enabled-features-helper.js"></script> + +<body> +<script> +promise_test(async(t) => { + await runDefaultEnabledFeaturesTest(t, /*should_load=*/true, + get_host_info().ORIGIN); + await runDefaultEnabledFeaturesTest(t, /*should_load=*/true, + get_host_info().ORIGIN, generator_api="sharedstorage"); +}, 'Same-origin fenced frame loads when feature policies are unset'); + +promise_test(async(t) => { + await runDefaultEnabledFeaturesTest(t, /*should_load=*/true, + get_host_info().REMOTE_ORIGIN); + await runDefaultEnabledFeaturesTest(t, /*should_load=*/true, + get_host_info().REMOTE_ORIGIN, generator_api="sharedstorage"); +}, 'Cross-origin fenced frame loads when feature policies are unset'); + +</script> +</body> +</html> diff --git a/testing/web-platform/tests/fenced-frame/default-enabled-features-unset.https.html.headers b/testing/web-platform/tests/fenced-frame/default-enabled-features-unset.https.html.headers new file mode 100644 index 0000000000..e087474e2a --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/default-enabled-features-unset.https.html.headers @@ -0,0 +1 @@ +Permissions-Policy: geolocation=(*)
\ No newline at end of file diff --git a/testing/web-platform/tests/fenced-frame/deprecated-config-apis.https.html b/testing/web-platform/tests/fenced-frame/deprecated-config-apis.https.html new file mode 100644 index 0000000000..11cd247f23 --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/deprecated-config-apis.https.html @@ -0,0 +1,42 @@ +<!DOCTYPE html> +<title>Tests for configs as arguments to `deprecatedReplaceInURN` and `deprecatedURNToURL`.</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/utils.js"></script> +<script src="/common/utils.js"></script> +<script src="/common/get-host-info.sub.js"></script> + +<body> +<script> + +promise_test(async () => { + const expected_url = location.href + '?foo'; + const config = await generateURNFromFledgeRawURL(location.href + '?${X}', [], true); + await navigator.deprecatedReplaceInURN(config, {'${X}': 'foo'}); + const url = await navigator.deprecatedURNToURL(config, false); + assert_equals(url, expected_url, 'The retrieved mapped url should match.'); +}, 'deprecated urn APIs should work with configs generated by an API'); + +promise_test(async () => { + const config = new FencedFrameConfig(location.href); + try { + await navigator.deprecatedReplaceInURN(config, {}); + assert_unreached('deprecatedReplaceInURN must throwfor default configs'); + } catch (e) { + assert_equals(e.message, + "Failed to execute 'deprecatedReplaceInURN' on 'Navigator': " + + "Passed config must have a mapped URL."); + } + try { + await navigator.deprecatedURNToURL(config, false); + assert_unreached('deprecatedURNToURL must throw for default configs'); + } catch (e) { + assert_equals(e.message, + "Failed to execute 'deprecatedURNToURL' on 'Navigator': " + + "Passed config must have a mapped URL."); + } +}, 'deprecated urn APIs should not work with configs generated by a WebIDL constructor'); + +</script> +</body> +</html> diff --git a/testing/web-platform/tests/fenced-frame/disallowed-navigation-to-blob.https.html b/testing/web-platform/tests/fenced-frame/disallowed-navigation-to-blob.https.html new file mode 100644 index 0000000000..9b299cd5e1 --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/disallowed-navigation-to-blob.https.html @@ -0,0 +1,37 @@ +<!DOCTYPE html> +<title>Fenced frame disallowed navigations to blob: URL</title> +<meta name="timeout" content="long"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/common/utils.js"></script> +<script src="resources/utils.js"></script> +<script src="/fetch/local-network-access/resources/support.sub.js"></script> + +<body> +<script> +const kPublicUtils = resolveUrl("resources/utils.js", Server.HTTPS_PUBLIC); + +function getTimeoutPromise(t) { + return new Promise(resolve => + t.step_timeout(() => resolve("NOT LOADED"), 2000)); +} + +// The following tests ensure that an embedder cannot navigate a +// `mode=opaque-ads` fenced frame to an opaque URN or a fenced frame config +// object that represents a blob: URL +for (const resolve_to_config of [true, false]) { + promise_test(async t => { + const key = token(); + const blobURL = URL.createObjectURL( + new Blob([`${createLocalSource(key, kPublicUtils)}`], + {type: 'text/html'})); + const select_url_result = await runSelectURL(blobURL); + attachFencedFrame(select_url_result); + const loaded_promise = nextValueFromServer(key); + const result = await Promise.any([loaded_promise, getTimeoutPromise(t)]); + assert_equals(result, "NOT LOADED"); + }, "fenced frame " + (resolve_to_config ? "config" : "urn:uuid") + + " => blob: URL"); +} +</script> +</body> diff --git a/testing/web-platform/tests/fenced-frame/disallowed-navigation-to-data.https.html b/testing/web-platform/tests/fenced-frame/disallowed-navigation-to-data.https.html new file mode 100644 index 0000000000..7ab8edc7e1 --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/disallowed-navigation-to-data.https.html @@ -0,0 +1,35 @@ +<!DOCTYPE html> +<title>Fenced frame disallowed navigations to data: URL</title> +<meta name="timeout" content="long"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/common/utils.js"></script> +<script src="resources/utils.js"></script> +<script src="/fetch/local-network-access/resources/support.sub.js"></script> + +<body> +<script> +const kPublicUtils = resolveUrl("resources/utils.js", Server.HTTPS_PUBLIC); + +function getTimeoutPromise(t) { + return new Promise(resolve => + t.step_timeout(() => resolve("NOT LOADED"), 2000)); +} + +// The following tests ensure that an embedder cannot navigate a +// `mode=opaque-ads` fenced frame to an opaque URN or a fenced frame config +// object that represents a data: URL +for (const resolve_to_config of [true, false]) { + promise_test(async t => { + const key = token(); + const select_url_result = await + runSelectURL(`data:text/html, ${createLocalSource(key, kPublicUtils)}`); + attachFencedFrame(select_url_result); + const loaded_promise = nextValueFromServer(key); + const result = await Promise.any([loaded_promise, getTimeoutPromise(t)]); + assert_equals(result, "NOT LOADED"); + }, "fenced frame " + (resolve_to_config ? "config" : "urn:uuid") + + " => data: URL"); +} +</script> +</body> diff --git a/testing/web-platform/tests/fenced-frame/disallowed-navigation-to-http.https.html b/testing/web-platform/tests/fenced-frame/disallowed-navigation-to-http.https.html new file mode 100644 index 0000000000..18ed92851a --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/disallowed-navigation-to-http.https.html @@ -0,0 +1,40 @@ +<!DOCTYPE html> +<title>Fenced frame disallowed navigations</title> +<meta name="timeout" content="long"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.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/utils.js"></script> + +<body> +<script> +function getTimeoutPromise(t) { + return new Promise(resolve => + t.step_timeout(() => resolve("NOT LOADED"), 2000)); +} + +// The following test ensures that an embedder cannot navigate a +// `mode=opaque-ads` fenced frame to an opaque URN that represents a: +// - http: URL +// We split this into a separate test file because `sharedStorage.selectURL()`, +// which is used to generate the URN in the test, has a limit of 3 calls per +// origin per pageload. We are unabled to generate this URN from FLEDGE. +for (const resolve_to_config of [true, false]) { + promise_test(async t => { + const key = token(); + const http_url = new URL("resources/embeddee.html", + get_host_info().HTTP_ORIGIN + location.pathname); + const select_url_result = await runSelectURL(http_url, [key], + resolve_to_config); + const fencedframe = attachFencedFrame(select_url_result, + /*mode=*/'opaque-ads'); + const loaded_promise = nextValueFromServer(key); + const result = await Promise.any([loaded_promise, getTimeoutPromise(t)]); + assert_equals(result, "NOT LOADED"); + }, "fenced frame " + (resolve_to_config ? "config" : "urn:uuid") + + " => http: URL"); +} +</script> +</body>
\ No newline at end of file diff --git a/testing/web-platform/tests/fenced-frame/disallowed-navigations-dangling-markup-urn.https.html b/testing/web-platform/tests/fenced-frame/disallowed-navigations-dangling-markup-urn.https.html new file mode 100644 index 0000000000..1a5720b034 --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/disallowed-navigations-dangling-markup-urn.https.html @@ -0,0 +1,64 @@ +<!DOCTYPE html> +<title>Fenced frame disallowed navigations with potentially-dangling markup</title> +<meta name="timeout" content="long"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.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/utils.js"></script> +<script src="/fetch/local-network-access/resources/support.sub.js"></script> +<script src="resources/dangling-markup-helper.js"></script> + +<body> + +<script> +// These tests assert that fenced frames cannot be navigated to a urn:uuid URL +// that represents an HTTPS URLs with dangling markup. +for (const substring of kDanglingMarkupSubstrings) { + promise_test(async t => { + const key = token(); + + // Copied from from `generateURNFromFlege()`, since we have to modify the + // final URL that goes into `interestGroup.ads[0].renderUrl` for + // `navigator.joinAdInterestGroup()`. + const bidding_token = token(); + const seller_token = token(); + + let url_string = generateURL("resources/report-url.html?blocked", + [key]).toString(); + url_string = url_string.replace("blocked", substring); + + const interestGroup = { + name: 'testAd1', + owner: location.origin, + biddingLogicUrl: new URL(FLEDGE_BIDDING_URL, location.origin), + ads: [{renderUrl: url_string, bid: 1}], + userBiddingSignals: {biddingToken: bidding_token}, + trustedBiddingSignalsKeys: ['key1'], + adComponents: [], + }; + + // Pick an arbitrarily high duration to guarantee that we never leave the + // ad interest group while the test runs. + navigator.joinAdInterestGroup(interestGroup, /*durationSeconds=*/3000000); + + const auctionConfig = { + seller: location.origin, + interestGroupBuyers: [location.origin], + decisionLogicUrl: new URL(FLEDGE_DECISION_URL, location.origin), + auctionSignals: {biddingToken: bidding_token, sellerToken: seller_token}, + }; + + const urn = await navigator.runAdAuction(auctionConfig); + + const fencedframe = attachFencedFrame(urn); + const loaded_promise = nextValueFromServer(key); + const result = await Promise.any([loaded_promise, getTimeoutPromise(t)]); + assert_equals(result, "NOT LOADED"); + }, `fenced frame opaque URN => https: URL with dangling markup '${substring}'`); +} + +</script> + +</body> diff --git a/testing/web-platform/tests/fenced-frame/disallowed-navigations-dangling-markup.https.html b/testing/web-platform/tests/fenced-frame/disallowed-navigations-dangling-markup.https.html new file mode 100644 index 0000000000..72f0855d59 --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/disallowed-navigations-dangling-markup.https.html @@ -0,0 +1,32 @@ +<!DOCTYPE html> +<title>Fenced frame disallowed navigations with potentially-dangling markup</title> +<meta name="timeout" content="long"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.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/utils.js"></script> +<script src="/fetch/local-network-access/resources/support.sub.js"></script> +<script src="resources/dangling-markup-helper.js"></script> + +<body> + +<script> +// These tests assert that fenced frames cannot be navigated to HTTPs URLs +// with dangling markup. +for (const substring of kDanglingMarkupSubstrings) { + promise_test(async t => { + const key = token(); + let url_string = generateURL("resources/embeddee.html?blocked", [key]).toString(); + url_string = url_string.replace("blocked", substring); + const fencedframe = attachFencedFrame(url_string); + const loaded_promise = nextValueFromServer(key); + const result = await Promise.any([loaded_promise, getTimeoutPromise(t)]); + assert_equals(result, "NOT LOADED"); + }, `fenced frame dangling-markup URL with '${substring}'`); +} + +</script> + +</body> diff --git a/testing/web-platform/tests/fenced-frame/disallowed-navigations.https.html b/testing/web-platform/tests/fenced-frame/disallowed-navigations.https.html new file mode 100644 index 0000000000..5cde50d420 --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/disallowed-navigations.https.html @@ -0,0 +1,118 @@ +<!DOCTYPE html> +<title>Fenced frame disallowed navigations</title> +<meta name="timeout" content="long"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.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/utils.js"></script> +<script src="/fetch/local-network-access/resources/support.sub.js"></script> + +<body> + +<script> +// Baseline tests: +// - Embedder can navigate iframe to blob: URL +// - Embedder can navigate iframe to data: URL +// - Same-origin embedder can navigate iframe to javascript: URL +// - Embedder can navigate iframe to http: URL +// Fenced frame tests: +// - Embedder cannot navigate fenced frame to blob: URL +// - Embedder cannot navigate fenced frame to data: URL +// - Same-origin embedder cannot navigate fenced frame to +// javascript: URL +// - Embedder cannot navigate fenced frame to http: URL + +// 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. +const kPublicUtils = resolveUrl("resources/utils.js", Server.HTTPS_PUBLIC); + +// These are just baseline tests asserting that this test's machinery to load +// blob:, data:, and javascript: URLs work properly in contexts where they are +// expected to. +promise_test(async () => { + const key = token(); + attachIFrame(`data:text/html, ${createLocalSource(key, kPublicUtils)}`); + const result = await nextValueFromServer(key); + assert_equals(result, "LOADED"); +}, "iframe data: URL"); + +promise_test(async () => { + const key = token(); + const blobURL = URL.createObjectURL( + new Blob([`${createLocalSource(key, kPublicUtils)}`], + {type: 'text/html'})); + attachIFrame(blobURL); + const result = await nextValueFromServer(key); + assert_equals(result, "LOADED"); +}, "iframe blob: URL"); + +promise_test(async () => { + const iframe = attachIFrameContext(); + iframe.src = "javascript:window.jsURLExecuted = true;" + await iframe.execute(async () => { + assert_equals(window.jsURLExecuted, true); + }); +}, "iframe javascript: URL"); + +// The following tests ensure that an embedder cannot navigate a fenced frame +// to: +// - data: URLs +// - blob: URLs +// - javascript: URLs +// - http: URLs +function getTimeoutPromise(t) { + return new Promise(resolve => + t.step_timeout(() => resolve("NOT LOADED"), 2000)); +} + +promise_test(async t => { + const key = token(); + attachFencedFrame(`data:text/html, ${createLocalSource(key, kPublicUtils)}`); + const loaded_promise = nextValueFromServer(key); + const result = await Promise.any([loaded_promise, getTimeoutPromise(t)]); + assert_equals(result, "NOT LOADED"); +}, `fenced frame data: URL`); + +promise_test(async t => { + const key = token(); + const blobURL = URL.createObjectURL( + new Blob([`${createLocalSource(key, kPublicUtils)}`], + {type: 'text/html'})); + attachFencedFrame(blobURL); + const loaded_promise = nextValueFromServer(key); + const result = await Promise.any([loaded_promise, getTimeoutPromise(t)]); + assert_equals(result, "NOT LOADED"); +}, `fenced frame blob: URL`); + +promise_test(async t => { + const fencedframe = attachFencedFrameContext(); + fencedframe.src = "javascript:window.jsURLExecuted = true;" + // Just in case the javascript URL executes asynchronously, let's wait for + // it. + await getTimeoutPromise(t); + await fencedframe.execute(async () => { + assert_equals(window.jsURLExecuted, undefined); + }); +}, `fenced frame javascript: URL`); + +promise_test(async t => { + const key = token(); + let http_url = new URL("resources/embeddee.html", + get_host_info().HTTP_ORIGIN + location.pathname); + http_url = generateURL(http_url, [key]); + assert_equals(http_url.protocol, "http:"); + const fencedframe = attachFencedFrame(http_url); + const loaded_promise = nextValueFromServer(key); + const result = await Promise.any([loaded_promise, getTimeoutPromise(t)]); + assert_equals(result, "NOT LOADED"); +}, `fenced frame http: URL`); + +</script> + +</body> diff --git a/testing/web-platform/tests/fenced-frame/document-picture-in-picture-denied.https.html b/testing/web-platform/tests/fenced-frame/document-picture-in-picture-denied.https.html new file mode 100644 index 0000000000..3838fd683a --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/document-picture-in-picture-denied.https.html @@ -0,0 +1,28 @@ +<!DOCTYPE html> +<title>Test that fencedframes cannot open a DocumentPictureInPicture window.</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.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/utils.js"></script> +<script src="/common/dispatcher/dispatcher.js"></script> +<script src="resources/utils.js"></script> + +<body> +</body> +<script> +promise_test(async () => { + const frame = attachFencedFrameContext(); + const result = await frame.execute(async () => { + await simulateGesture(); + try { + await documentPictureInPicture.requestWindow(); + return null; + } catch(e) { + return e.name; + } + }); + assert_equals(result, 'NotAllowedError', 'requestWindow() from a fencedframe should trigger a NotAllowedError'); +}, 'fencedframes cannot open a DocumentPictureInPicture window.'); +</script> diff --git a/testing/web-platform/tests/fenced-frame/document-referrer.https.html b/testing/web-platform/tests/fenced-frame/document-referrer.https.html new file mode 100644 index 0000000000..2bbc319c7b --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/document-referrer.https.html @@ -0,0 +1,60 @@ +<!DOCTYPE html> +<title>Test document.referrer referrer</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/common/dispatcher/dispatcher.js"></script> +<script src="/common/utils.js"></script> +<script src="resources/utils.js"></script> + +<body> + +<script> +promise_test(async () => { + const fenced_frame = attachFencedFrameContext(); + + // Test the value of `document.referrer` inside a top-level fenced frame. + const fenced_frame_url = await fenced_frame.execute(() => { + assert_equals(document.referrer, "", + "The top-level fenced frame's document.referrer is censored"); + return location.href; + }); + + // Test an iframe nested inside a fenced frame. + await fenced_frame.execute(async () => { + const nested_iframe = attachIFrameContext(); + await nested_iframe.execute((expected_referrer) => { + assert_equals(document.referrer, expected_referrer, + `The document.referrer of the iframe inside the fenced + frame is not censored`); + }, [location.href]); + }); + + // Test a nested fenced frame. + await fenced_frame.execute(async () => { + const nested_fenced_frame = attachFencedFrameContext(); + await nested_fenced_frame.execute(() => { + assert_equals(document.referrer, "", + `The document.referrer of the nested fenced frame is + censored`); + }); + }); + + // Test a top-level fenced frame after it navigates itself. + // Navigate the fenced frame. (Refresh it, so we can still send it scripts.) + await fenced_frame.execute(() => { + window.executor.suspend(() => { + location.href = location.href; + }); + }); + + // Check that it now sees its original URL as the referrer. + await fenced_frame.execute((expected_referrer) => { + assert_equals(document.referrer, expected_referrer, + `The document.referrer of a fenced frame after it navigates + itself is not censored.`); + }, [fenced_frame_url]); + +}, "document.referrer"); +</script> + +</body> diff --git a/testing/web-platform/tests/fenced-frame/download.https.html b/testing/web-platform/tests/fenced-frame/download.https.html new file mode 100644 index 0000000000..ae9cebbe05 --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/download.https.html @@ -0,0 +1,39 @@ +<!DOCTYPE html> +<title>Test fenced frame does not allow triggering download</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/common/utils.js"></script> +<script src="resources/utils.js"></script> +<script src="resources/download-helper.js"></script> + +<body> + <script> + const testTypes = [ + { + type: "anchor", + description: "Anchor click triggering download in fenced frames is blocked." + }, + { + type: "navigation", + description: "Navigation resulted download in fenced frames is blocked." + } + ]; + + testTypes.forEach(({type, description}) => { + promise_test(async t => { + const download_key = token(); + const download_ack_key = token(); + + // The download link is clicked inside the fenced frame after the loading + const fenced_frame_url = generateURL("resources/download-inner.html", [download_key, download_ack_key]) + `&type=${type}`; + attachFencedFrame(fenced_frame_url); + + const response = await nextValueFromServer(download_ack_key); + assert_equals(response, 'Triggered the action for download'); + + const result = await VerifyDownload(t, download_key); + assert_false(result, 'Expect no download to happen'); + }, description); + }); + </script> +</body> diff --git a/testing/web-platform/tests/fenced-frame/embedder-coop-coep-blocked.https.html b/testing/web-platform/tests/fenced-frame/embedder-coop-coep-blocked.https.html new file mode 100644 index 0000000000..e0e418577d --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/embedder-coop-coep-blocked.https.html @@ -0,0 +1,21 @@ +<!DOCTYPE html> +<title>Test COOP/COEP properties set for a Fenced Frame Tree</title> +<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> + +<body> +<script> +promise_test(async(t) => { + const fencedframe = attachFencedFrameContext(); + const fencedframe_loaded = fencedframe.execute(() => {}); + const fencedframe_blocked = new Promise(r => t.step_timeout(r, 1000)); + assert_equals("blocked", await Promise.any([ + fencedframe_blocked.then(() => "blocked"), + fencedframe_loaded.then(() => "loaded"), + ]), "fenced frame should not be loaded."); +}, 'Create a fencedframe without COEP from an embedder setting COEP'); +</script> +</body> diff --git a/testing/web-platform/tests/fenced-frame/embedder-coop-coep-blocked.https.html.headers b/testing/web-platform/tests/fenced-frame/embedder-coop-coep-blocked.https.html.headers new file mode 100644 index 0000000000..807872014a --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/embedder-coop-coep-blocked.https.html.headers @@ -0,0 +1,2 @@ +cross-origin-opener-policy:same-origin +cross-origin-embedder-policy: require-corp
\ No newline at end of file diff --git a/testing/web-platform/tests/fenced-frame/embedder-csp-not-propagate.https.html b/testing/web-platform/tests/fenced-frame/embedder-csp-not-propagate.https.html new file mode 100644 index 0000000000..425cc9661e --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/embedder-csp-not-propagate.https.html @@ -0,0 +1,24 @@ +<!DOCTYPE html> +<title>Test embedder CSP not propagate to fenced frame</title> +<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> + +<body> +<script> +promise_test(async(t) => { + const iframe = attachIFrameContext( + {headers:[["Content-Security-Policy", "frame-src 'self'"]]}); + await iframe.execute(async() => { + const fencedframe = attachFencedFrameContext(); + await fencedframe.execute(async() => { + const nested_iframe = attachIFrameContext( + {origin:get_host_info().HTTPS_REMOTE_ORIGIN}); + await nested_iframe.execute(() => {}); + }); + }); +}, 'Embedder CSP should not propagate to fenced frame'); +</script> +</body> diff --git a/testing/web-platform/tests/fenced-frame/embedder-no-coep.https.html b/testing/web-platform/tests/fenced-frame/embedder-no-coep.https.html new file mode 100644 index 0000000000..75980a1301 --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/embedder-no-coep.https.html @@ -0,0 +1,27 @@ +<!DOCTYPE html> +<title>Test COEP properties set for a Fenced Frame Tree</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/common/utils.js"></script> +<script src="resources/utils.js"></script> +<script src="resources/embedder-policy.js"></script> + +<body> +<script> +promise_test(async () => { + const uuid = token(); + const frame = setupTest("coep:require-corp", uuid); + const result = await nextValueFromServer(uuid); + assert_equals(result, "PASS", "embedded page has been loaded."); +}, "Create fencedframe with COEP:require-corp"); + +promise_test(async () => { + const uuid = token(); + const frame = setupTest("no coep", uuid); + const result = await nextValueFromServer(uuid); + assert_equals(result, "PASS", + "page without COEP should be able to load page with COEP"); +}, "Create fencedframe without COEP header"); + +</script> +</body> diff --git a/testing/web-platform/tests/fenced-frame/embedder-require-corp.https.html b/testing/web-platform/tests/fenced-frame/embedder-require-corp.https.html new file mode 100644 index 0000000000..9432d0cbb5 --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/embedder-require-corp.https.html @@ -0,0 +1,39 @@ +<!DOCTYPE html> +<meta name="timeout" content="long"> +<title>Test COEP properties set for a Fenced Frame Tree</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/common/get-host-info.sub.js"></script> +<script src="/common/utils.js"></script> +<script src="resources/utils.js"></script> +<script src="resources/embedder-policy.js"></script> + +<body> +<script> +promise_test(async (t) => { + const uuid = token(); + const frame = setupTest("coep:require-corp", uuid); + const result = await nextValueFromServer(uuid); + assert_equals(result, "PASS", "embedded page has been loaded."); +}, "Create fencedframe with COEP:require-corp"); + +promise_test(async (t) => { + const uuid = token(); + t.step_timeout(() => t.done(), 1000); + const frame = setupTest("no coep", uuid); + const result = await nextValueFromServer(uuid); + assert_unreached("embedded page should not be loaded."); +}, "Create fencedframe without COEP header"); + +promise_test(async (t) => { + const uuid = token(); + // Make sure a different site is used. + hostname = get_host_info().REMOTE_HOST; + t.step_timeout(() => t.done(), 1000); + const frame = setupTest("coep:require-corp", uuid, hostname=hostname); + const result = await nextValueFromServer(uuid); + assert_unreached("embedded page should not be loaded."); +}, "Create fencedframe with a cross site COEP:require-corp and CORP:same-orign"); +</script> +</script> +</body> diff --git a/testing/web-platform/tests/fenced-frame/embedder-require-corp.https.html.headers b/testing/web-platform/tests/fenced-frame/embedder-require-corp.https.html.headers new file mode 100644 index 0000000000..8df98474b5 --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/embedder-require-corp.https.html.headers @@ -0,0 +1 @@ +cross-origin-embedder-policy: require-corp diff --git a/testing/web-platform/tests/fenced-frame/fedcm-get-credential.https.html b/testing/web-platform/tests/fenced-frame/fedcm-get-credential.https.html new file mode 100644 index 0000000000..396da5946b --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/fedcm-get-credential.https.html @@ -0,0 +1,25 @@ +<!DOCTYPE html> +<title>Test FedCM navigator.credentials.get()</title> +<meta name="timeout" content="long"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/common/utils.js"></script> +<script src="resources/utils.js"></script> + +<body> + +<script> +promise_test(async () => { + const key = token(); + attachFencedFrame( + generateURL('resources/fedcm-get-credential-inner.https.html', [key])); + // Get the result for the fenced frame. + const fenced_frame_result = await nextValueFromServer(key); + assert_equals( + fenced_frame_result, + 'navigator.credentials.get failed', + 'navigator.credentials.get should fail on fenced frame'); +}, 'navigator.credentials.get'); +</script> + +</body> diff --git a/testing/web-platform/tests/fenced-frame/fence-api.https.html b/testing/web-platform/tests/fenced-frame/fence-api.https.html new file mode 100644 index 0000000000..7d350ff75f --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/fence-api.https.html @@ -0,0 +1,29 @@ +<!DOCTYPE html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/common/utils.js"></script> +<script src="resources/utils.js"></script> +<title>Test window.fence object</title> + +<body> +<script> +promise_test(async () => { + + // Check that window.fence is undefined in the top-level frame. + assert_true(window.fence == null, + "window.fence should only be visible inside fenced frames."); + assert_true(fence == null, + "fence should only be visible inside fenced frames."); + + // Create a fenced frame. + const fence_api_token = token(); + const frame_url = generateURL("resources/fence-api-inner.https.html", + [fence_api_token]); + attachFencedFrame(frame_url); + + // Wait for the fenced frame to complete its fence API tests. + await nextValueFromServer(fence_api_token); + +}, "window.fence"); +</script> +</body> diff --git a/testing/web-platform/tests/fenced-frame/fence-report-event.https.html b/testing/web-platform/tests/fenced-frame/fence-report-event.https.html new file mode 100644 index 0000000000..ce217c7763 --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/fence-report-event.https.html @@ -0,0 +1,28 @@ +<!DOCTYPE html> +<title>Test window.fence.reportEvent</title> +<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> + +<body> +<script> +promise_test(async(t) => { + const fencedframe = await attachFencedFrameContext({generator_api: 'fledge'}); + await fencedframe.execute(() => { + let event = {}; + assert_throws_js(TypeError, () => {window.fence.reportEvent(event);}); + + event.eventType = "click"; + assert_throws_js(TypeError, () => {window.fence.reportEvent(event);}); + + event.eventData = "dummy"; + assert_throws_js(TypeError, () => {window.fence.reportEvent(event);}); + + event.destination = ["buyer"]; + window.fence.reportEvent(event); + }); +}, 'window.fence.reportEvent'); +</script> +</body> diff --git a/testing/web-platform/tests/fenced-frame/fence-urn-iframes.https.html b/testing/web-platform/tests/fenced-frame/fence-urn-iframes.https.html new file mode 100644 index 0000000000..cdde07329d --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/fence-urn-iframes.https.html @@ -0,0 +1,52 @@ +<!DOCTYPE html> +<title>Test window.fence availability in iframes.</title> +<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> + +<body> +<script> + +promise_test(async(t) => { + const frame = await attachIFrameContext(); + await frame.execute(() => { + assert_equals(window.fence, null); + }); +}, 'window.fence is unavailable in normal (non-urn) iframes'); + +promise_test(async(t) => { + // window.fence works in urn iframes. + var frame = await attachIFrameContext({generator_api: 'fledge'}); + await frame.execute(() => { + assert_not_equals(window.fence, null); + }); + // window.fence works after navigating to a new urn. + frame = await replaceFrameContext(frame, {generator_api: 'fledge'}); + await frame.execute(() => { + assert_not_equals(window.fence, null); + }); + // window.fence still works after embedder-initiated navigations to non-urns, + // because the concept of "embedder-initiated" navigations is nebulous in + // iframes. + frame = await replaceFrameContext(frame); + await frame.execute(() => { + assert_not_equals(window.fence, null); + }); +}, 'window.fence is available in urn iframes'); + +promise_test(async(t) => { + // window.fence works in urn iframes. + const frame = await attachIFrameContext({generator_api: 'fledge'}); + await frame.execute(async () => { + assert_not_equals(window.fence, null); + const nested_frame = await attachIFrameContext(); + await nested_frame.execute(() => { + assert_not_equals(window.fence, null); + }); + }); +}, 'window.fence is available in same-origin subframes of urn iframes'); + +</script> +</body> diff --git a/testing/web-platform/tests/fenced-frame/fledge-container-size-mutation-observer.https.html b/testing/web-platform/tests/fenced-frame/fledge-container-size-mutation-observer.https.html new file mode 100644 index 0000000000..b6085afce3 --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/fledge-container-size-mutation-observer.https.html @@ -0,0 +1,48 @@ +<!DOCTYPE html> +<title>Test that mutation observer doesn't break noassert container size setter.</title> +<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> + +<body> +<script> + +async function checkMutationObserver() { + // Create a FLEDGE FF with a constant content size and given container size. + const requested_width_1 = '299px'; + const requested_height_1 = '72px'; + const frame = await attachFencedFrameContext({ + generator_api: 'fledge', resolve_to_config: true, ad_with_size: true, + requested_size: [requested_width_1, requested_height_1]}); + + // Install a mutation observer. + const config = { attributes: true, childList: true, subtree: true }; + const callback = (mutationList, observer) => { + throw new Error("mutation observed"); + } + const observer = new MutationObserver(callback); + observer.observe(frame.element, config); + + // Modify the container size manually. + const modified_width = '121px'; + const modified_height = '444px'; + frame.element.width = modified_width; + frame.element.height = modified_height; + + // Navigate to a new FLEDGE FF config with a different container size. + const requested_width_2 = '321px'; + const requested_height_2 = '49px'; + const replaced_frame = await replaceFrameContext(frame, { + generator_api: 'fledge', resolve_to_config: true, ad_with_size: true, + requested_size: [requested_width_2, requested_height_2]}); + + observer.disconnect(); +} + +setup({allow_uncaught_exception: true}); +promise_test(async () => { return checkMutationObserver(); }, 'Container size assert no exception ignores mutation observer'); + +</script> +</body> diff --git a/testing/web-platform/tests/fenced-frame/fledge-container-size.https.html b/testing/web-platform/tests/fenced-frame/fledge-container-size.https.html new file mode 100644 index 0000000000..5347c841d5 --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/fledge-container-size.https.html @@ -0,0 +1,102 @@ +<!DOCTYPE html> +<title>Test container size in FLEDGE fenced frames.</title> +<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> + +<body> +<script> +async function checkSyntaxError(requested_width, requested_height) { + try { + const frame = await attachFencedFrameContext({ + generator_api: "fledge", resolve_to_config: true, ad_with_size: true, + requested_size: [requested_width, requested_height]}); + } catch(e) { + assert_equals(e.name, 'TypeError'); + return; + } + assert_unreached('Missing expected type error'); +} + +async function checkSuccess() { + const assert_outer_dimensions = + (frame, label, expected_width, expected_height) => { + assert_equals(frame.element.width, expected_width, + label + ' outer attribute width'); + assert_equals(frame.element.height, expected_height, + label + ' outer attribute height'); + assert_equals(getComputedStyle(frame.element).width, expected_width, + label + ' outer computed width'); + assert_equals(getComputedStyle(frame.element).height, expected_height, + label + ' outer computed height'); + } + + const assert_inner_dimensions = + (label, expected_width, expected_height) => { + assert_equals(getComputedStyle(document.documentElement).width, expected_width+'px', + label + ' inner computed width'); + assert_equals(window.innerWidth, expected_width, + label + ' inner width'); + assert_equals(window.innerHeight, expected_height, + label + ' inner height'); + } + + // `ad_with_size` is hardcoded to use '100px' by '50px'. + const content_width = 100; + const content_height = 50; + + // Create a FLEDGE FF with a constant content size and given container size. + const requested_width_1 = '299px'; + const requested_height_1 = '72px'; + const frame = await attachFencedFrameContext({ + generator_api: 'fledge', resolve_to_config: true, ad_with_size: true, + requested_size: [requested_width_1, requested_height_1]}); + + // The outer size should reflect the container size, and the inner size should reflect the content size. + await frame.execute(assert_inner_dimensions, ['First config', content_width, content_height]); + assert_outer_dimensions(frame, 'First config', requested_width_1, requested_height_1); + + // Modify the container size manually. + const modified_width = '121px'; + const modified_height = '444px'; + frame.element.width = modified_width; + frame.element.height = modified_height; + + // The outer size should reflect the new size, and the inner size should be unchanged. + await frame.execute(assert_inner_dimensions, ['Modified container size', content_width, content_height]); + assert_outer_dimensions(frame, 'Modified container size', modified_width, modified_height); + + // Navigate to a new FLEDGE FF config with a different container size. + const requested_width_2 = '321px'; + const requested_height_2 = '49px'; + const replaced_frame = await replaceFrameContext(frame, { + generator_api: 'fledge', resolve_to_config: true, ad_with_size: true, + requested_size: [requested_width_2, requested_height_2]}); + + // The outer size should reflect the new size, and the inner size should be unchanged. + await replaced_frame.execute(assert_inner_dimensions, ['Second config', content_width, content_height]); + assert_outer_dimensions(replaced_frame, 'Second config', requested_width_2, requested_height_2); + + // Navigate to a new FLEDGE FF config with no container size. + const replaced_frame_2 = await replaceFrameContext(frame, { + generator_api: 'fledge', resolve_to_config: true, ad_with_size: true}); + + // The dimensions should be unchanged. + await replaced_frame_2.execute(assert_inner_dimensions, ['Third config', content_width, content_height]); + assert_outer_dimensions(replaced_frame_2, 'Third config', requested_width_2, requested_height_2); +} + +// Type error cases. +promise_test(async () => { return checkSyntaxError('-299px', '72px'); }, '-299px x 72px'); +promise_test(async () => { return checkSyntaxError('299px', '-72px'); }, '299px x -72px'); +promise_test(async () => { return checkSyntaxError('0px', '0px'); }, '0px x 0px'); +promise_test(async () => { return checkSyntaxError('299px', '72ab'); }, '299px x 72ab'); +promise_test(async () => { return checkSyntaxError('299bc', '72px'); }, '299bc x 72px'); + +// Success cases. +promise_test(async () => { return checkSuccess(); }, 'FLEDGE successful container size'); + +</script> +</body> diff --git a/testing/web-platform/tests/fenced-frame/fragment-navigation.https.html b/testing/web-platform/tests/fenced-frame/fragment-navigation.https.html new file mode 100644 index 0000000000..1d548e35da --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/fragment-navigation.https.html @@ -0,0 +1,50 @@ +<!DOCTYPE html> +<title>Test that embedder-initiated fragment navigations are forced to be cross-document.</title> +<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> + +<body> +<script> +async function runTest(first_url_opaque, second_url_opaque) { + const frame = await attachFencedFrameContext({generator_api: 'fledge'}); + let base_url = frame.src; + let fragment_url = base_url + "#foo"; + + if (first_url_opaque) { + base_url = await generateURNFromFledge(base_url, []); + } + + if (second_url_opaque) { + fragment_url = await generateURNFromFledge(fragment_url, []); + } + + // Start the fenced frame at about:blank. + await frame.execute(() => { + window.executor.suspend(() => { location.href = "about:blank"; }); + }); + + // Navigate the fenced frame to the base url from the embedder, and then + // suspend the remote executor. + frame.src = base_url; + await frame.execute(() => { window.executor.suspend(() => {}); }); + + // Navigate the fenced frame to the fragment url from the embedder. Now + // the remote executor will only exist if the navigation wasn't considered + // same-document. + frame.src = fragment_url; + await frame.execute(() => {}); +} + +promise_test(async () => { await runTest(true, true); }, + "opaque to opaque fragment navigation"); +promise_test(async () => { await runTest(true, false); }, + "opaque to transparent fragment navigation"); +promise_test(async () => { await runTest(false, true); }, + "transparent to opaque fragment navigation"); +promise_test(async () => { await runTest(false, false); }, + "transparent to transparent fragment navigation"); +</script> +</body> diff --git a/testing/web-platform/tests/fenced-frame/frame-navigation.https.html b/testing/web-platform/tests/fenced-frame/frame-navigation.https.html new file mode 100644 index 0000000000..f3464c94e9 --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/frame-navigation.https.html @@ -0,0 +1,86 @@ +<!DOCTYPE html> +<title>Test fenced frame navigations (by a parent frame setting its src). </title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/common/utils.js"></script> +<script src="/common/get-host-info.sub.js"></script> +<script src="resources/utils.js"></script> + +<body> + +<script> +promise_test(async () => { + // This test checks that fenced frames can be navigated (given a new src). + // The access pattern is as follows, to exercise same- and cross-origin + // navigations in root and nested fenced frames. + + // [root] [nested] + // simple (origin 1) + // create-nested (origin 2) + // simple (origin 1) + // simple (origin 2) + // simple (origin 2) + // simple (origin 2) + + const navigation_key = token(); + const navigation_ack_key = token(); + + // Create URL prefixes to simulate different origins. + // (www1 and www2 are different origins) + const simple_url = generateURL( + "resources/frame-navigation-inner-simple.https.html", + [navigation_key, navigation_ack_key]); + const nested_url = generateURL( + "resources/frame-navigation-inner-create-nested.https.html", + [navigation_key, navigation_ack_key]); + const origin1_simple_url = getRemoteOriginURL(simple_url); + const origin2_nested_url = getRemoteOriginURL(nested_url) + .toString().replace("www1", "www2"); + const origin2_simple_url = getRemoteOriginURL(simple_url) + .toString().replace("www1", "www2"); + + // Create a root fenced frame. + root_frame = attachFencedFrame(origin1_simple_url); + const root_load1_actual = await nextValueFromServer(navigation_key); + const root_load1_expected = "pass"; + assert_equals(root_load1_actual, root_load1_expected, + "The 1st root fenced frame navigation succeeded"); + + // Navigate the root fenced frame to a second site (which will nest). + root_frame.config = new FencedFrameConfig(origin2_nested_url); + const root_load2_actual = await nextValueFromServer(navigation_key); + const root_load2_expected = "create-nested"; + assert_equals(root_load2_actual, root_load2_expected, + "The 2nd root fenced frame navigation (cross-origin) succeeded"); + writeValueToServer(navigation_ack_key, "ACK"); + + // Check that all of the nested navigations succeed. + const nested_load1_actual = await nextValueFromServer(navigation_key); + const nested_load1_expected = "pass"; + assert_equals(nested_load1_actual, nested_load1_expected, + "The 1st nested fenced frame navigation succeeded"); + writeValueToServer(navigation_ack_key, "ACK"); + + const nested_load2_actual = await nextValueFromServer(navigation_key); + const nested_load2_expected = "pass"; + assert_equals(nested_load2_actual, nested_load2_expected, + "The 2nd nested fenced frame navigation (cross-origin) succeeded"); + writeValueToServer(navigation_ack_key, "ACK"); + + const nested_load3_actual = await nextValueFromServer(navigation_key); + const nested_load3_expected = "pass"; + assert_equals(nested_load3_actual, nested_load3_expected, + "The 3rd nested fenced frame navigation (same-origin) succeeded"); + writeValueToServer(navigation_ack_key, "ACK"); + + // Navigate the root fenced frame. + root_frame.config = root_frame.config = new FencedFrameConfig(origin2_simple_url); + const root_load3_actual = await nextValueFromServer(navigation_key); + const root_load3_expected = "pass"; + assert_equals(root_load3_actual, root_load3_expected, + "The 3rd root fenced frame navigation (same-origin) succeeded"); + +}, "Fenced frame navigation should succeed"); +</script> + +</body> diff --git a/testing/web-platform/tests/fenced-frame/gamepad.https.html b/testing/web-platform/tests/fenced-frame/gamepad.https.html new file mode 100644 index 0000000000..ef6b91e921 --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/gamepad.https.html @@ -0,0 +1,20 @@ +<!DOCTYPE html> +<title>Gamepad API test</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/common/utils.js"></script> +<script src="resources/utils.js"></script> + +<body> +<script> +promise_test(async () => { + const key = token(); + attachFencedFrame(generateURL('resources/gamepad-inner.html', [key])); + const result = await nextValueFromServer(key); + assert_equals( + result, + 'SecurityError', + 'getGamepads should throw an error in a fenced frame'); +}, 'Gamepads information should not be read in the fenced frame.'); +</script> +</body> diff --git a/testing/web-platform/tests/fenced-frame/get-mode-in-nested-frame.https.html b/testing/web-platform/tests/fenced-frame/get-mode-in-nested-frame.https.html new file mode 100644 index 0000000000..d0888ce2e5 --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/get-mode-in-nested-frame.https.html @@ -0,0 +1,33 @@ +<!DOCTYPE html> +<title>Test GetFencedFrameMode() on a nested nested iframe.</title> +<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> + +<body> +<script> +promise_test(async () => { + // This test has the following setup: + // root -> (fencedframe) -> (first_iframe) -> (second_iframe) + // (second_iframe) will attempt an _unfencedTop navigation, which will result + // in GetFencedframeMode() being called. For this test to pass, it shouldn't + // timeout. + const fencedframe = await attachFencedFrameContext( + {generator_api: 'fledge'}); + await fencedframe.execute(async () => { + const first_iframe = attachIFrameContext(); + await first_iframe.execute(async () => { + const second_iframe = attachIFrameContext(); + await second_iframe.execute(async () => { + // This call will cause FrameTreeNode::GetFencedFrameMode() to be called + // for second_iframe. If the browser process doesn't hang (and timeout + // the test), then the test passes. + window.open("https://www.google.com", "_unfencedTop"); + }); + }); + }); +}, 'Trigger GetFencedFrameMode() in a nested nested iframe'); +</script> +</body> diff --git a/testing/web-platform/tests/fenced-frame/get-nested-configs.https.html b/testing/web-platform/tests/fenced-frame/get-nested-configs.https.html new file mode 100644 index 0000000000..ed9f953e74 --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/get-nested-configs.https.html @@ -0,0 +1,125 @@ +<!DOCTYPE html> +<title>window.fence.getNestedConfigs() test</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/utils.js"></script> +<script src="/common/utils.js"></script> +<script src="/common/get-host-info.sub.js"></script> + +<body> +<script> +promise_test(async (t) => { + const key = token(); + const urn = await generateURNFromFledge( + "resources/get-nested-configs-inner.html", [key]); + attachFencedFrame(urn); + + const response = await nextValueFromServer(key); + const [length, first_url] = response.split(","); + + assert_equals(length, '20', 'There should be 20 nested configurations.'); + assert_equals(first_url, 'opaque', 'The first config should be opaque.'); +}, 'getNestedConfigs() created by FLEDGE should return configurations'); + +for (const resolve_to_config of [true, false]) { + promise_test(async (t) => { + const key = token(); + const select_url_result = await runSelectURL( + generateURL("resources/get-nested-configs-inner.html", [key]), + [], resolve_to_config); + attachFencedFrame(select_url_result); + + const response = await nextValueFromServer(key); + const [length, first_url] = response.split(","); + + assert_equals(length, '0', 'There should be 0 nested configurations.'); + }, 'getNestedConfigs() from a fenced frame with the ' + + (resolve_to_config ? 'config' : 'urn:uuid') + + ' from sharedStroage.selectURL() should be empty'); +} + +promise_test(async (t) => { + const key = token(); + const url = generateURL("resources/get-nested-configs-inner.html", [key]); + attachFencedFrame(url, mode='default'); + + const response = await nextValueFromServer(key); + const [length, first_url] = response.split(","); + + assert_equals(length, '0', 'There should be 0 nested configurations.'); +}, 'getNestedConfigs() from a default mode frame should be empty'); + +promise_test(async (t) => { + const key = token(); + const urn = await generateURNFromFledge( + "resources/get-nested-configs-nested-iframe.html", [key]); + attachFencedFrame(urn); + + const response = await nextValueFromServer(key); + const [length, first_url] = response.split(","); + + assert_equals(length, '20', 'There should be 20 nested configurations.'); +}, 'getNestedConfigs() should work in a same-origin nested iframe'); + +promise_test(async (t) => { + const key = token(); + + const nested_url = generateURL("resources/embeddee.html", [key]); + + // Navigate a fenced frame to `navigate-nested-config.html`. That page will + // in turn create a nested fenced frame which will be navigated to the URN of + // the first item in the nested configs list, `nested_url`. + const urn = await generateURNFromFledge( + "resources/navigate-nested-config.html", [key], + [nested_url]); + attachFencedFrame(urn); + + const response = await nextValueFromServer(key); + assert_equals(response, 'PASS', 'The nested URL should load.'); +}, 'Nested configs created by FLEDGE should be navigable by fenced frame'); + +promise_test(async (t) => { + const key = token(); + + const nested_url = generateURL("resources/embeddee.html", [key]); + + // Navigate a fenced frame to `navigate-nested-config.html`. That page will + // in turn create a nested fenced frame which will be navigated to the URN of + // the first item in the nested configs list, `nested_url`. + const urn = await generateURNFromFledge( + "resources/navigate-nested-config.html", [key], + [nested_url]); + attachIFrame(urn); + + const response = await nextValueFromServer(key); + assert_equals(response, 'PASS', 'The nested URL should load.'); +}, 'Nested configs created by FLEDGE should be navigable by URN iframe'); + +promise_test(async (t) => { + const key = token(); + + const nested_url = generateURL("resources/embeddee.html", [key]); + + // Navigate a fenced frame to `navigate-nested-config.html`. That page will + // in turn create a nested fenced frame which will be navigated to the URN of + // the first item in the nested configs list, `nested_url`. Since this URN + // is invalid, the navigation should gracefully fail. + const urn = await generateURNFromFledge( + "resources/navigate-nested-config.html", [key], + []); + attachFencedFrame(urn); + + // There is no API to observe whether the document in the FencedFrame loaded + // or not. Instead, set up a timeout. If the document loads, "PASS" will be + // sent to the server. Otherwise "BLOCKED" will be sent after 1 second. + step_timeout(() => { + writeValueToServer(key, "BLOCKED"); + }, 1000); + + const response = await nextValueFromServer(key); + assert_equals(response, 'BLOCKED', 'The nested URL should not load.'); +}, 'Navigating an invalid config should be handled gracefully'); + +</script> +</body> +</html> diff --git a/testing/web-platform/tests/fenced-frame/header-referrer.https.html b/testing/web-platform/tests/fenced-frame/header-referrer.https.html new file mode 100644 index 0000000000..b6323962d8 --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/header-referrer.https.html @@ -0,0 +1,55 @@ +<!DOCTYPE html> +<title>Test `Referer` header</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/utils.js"></script> +<script src="/common/utils.js"></script> + +<body> + +<script> +promise_test(async () => { + const referrer_key = token(); + const referrer_ack_key = token(); + + const check_url = generateURL('resources/check-header-referrer.py', + [referrer_key]) + attachFencedFrame(check_url); + + const expected_referrer = ""; + const actual_referrer = await nextValueFromServer(referrer_key); + assert_equals(actual_referrer, expected_referrer, + "The top-level fenced frame has the right value for " + + "`Referer` header"); + + const inner_url = generateURL('resources/header-referrer-inner.html', + [referrer_key, referrer_ack_key]) + attachFencedFrame(inner_url); + + const iframe_expected_referrer = inner_url.toString().replace("%2C", ","); + + const iframe_actual_referrer = await nextValueFromServer(referrer_key); + assert_equals(iframe_actual_referrer, iframe_expected_referrer, + "The iframe inside the fenced frame has the right value for " + + "`Referer` header"); + + writeValueToServer(referrer_ack_key, "ACK"); + + const nested_fenced_frame_expected_referrer = ""; + const nested_fenced_frame_actual_referrer = await nextValueFromServer(referrer_key); + assert_equals(nested_fenced_frame_actual_referrer, nested_fenced_frame_expected_referrer, + "The nested fenced frame has the right value for " + + "`Referer` header"); + + writeValueToServer(referrer_ack_key, "ACK"); + + const navigate_self_expected_referrer = + inner_url.toString().replace("%2C", ","); + const navigate_self_actual_referrer = await nextValueFromServer(referrer_key); + assert_equals(navigate_self_actual_referrer, navigate_self_expected_referrer, + "The fenced frame after it navigates itself has the right value for " + + "`Referer` header"); +}, "header.referrer"); +</script> + +</body> diff --git a/testing/web-platform/tests/fenced-frame/header-secFetchDest.https.html b/testing/web-platform/tests/fenced-frame/header-secFetchDest.https.html new file mode 100644 index 0000000000..70b8c32749 --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/header-secFetchDest.https.html @@ -0,0 +1,38 @@ +<!DOCTYPE html> +<title>Test `Sec-Fetch-Dest` header</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/common/get-host-info.sub.js"></script> +<script src="/common/utils.js"></script> +<script src="resources/utils.js"></script> + +<body> + +<script> +promise_test(async () => { + const sec_fetch_dest_value_key = token(); + const https_origin_url = + getRemoteOriginURL( + generateURL( + 'resources/check-header-sec-fetch-dest.py', + [sec_fetch_dest_value_key])); + attachFencedFrame(https_origin_url); + + // Get the result for the top-level fenced frame. + const actual_result = await nextValueFromServer(sec_fetch_dest_value_key); + assert_equals(actual_result, "b'fencedframe'", "The fenced frame " + + "has the right value for " + + "`Sec-Fetch-Dest` header"); + + const inner_url = generateURL("resources/header-secFetchDest-inner.html", + [sec_fetch_dest_value_key]); + attachFencedFrame(inner_url); + + const iframe_actual_result = await nextValueFromServer(sec_fetch_dest_value_key); + assert_equals(iframe_actual_result, "b'fencedframe'", + "The iframe inside the fenced frame has the right value for " + + "`Sec-Fetch-Dest` header"); +}, "header.secFetchDest"); +</script> + +</body> diff --git a/testing/web-platform/tests/fenced-frame/hid.https.html b/testing/web-platform/tests/fenced-frame/hid.https.html new file mode 100644 index 0000000000..762ed6715a --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/hid.https.html @@ -0,0 +1,37 @@ +<!DOCTYPE html> +<title>Test of Web Bluetooth API</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="resources/utils.js"></script> +<script src="/common/utils.js"></script> +<script src="/common/dispatcher/dispatcher.js"></script> +<script src="/resources/testdriver-actions.js"></script> + +<body> + +<script> +promise_test(async () => { + const frame = attachFencedFrameContext(); + const result = await frame.execute(async () => { + await simulateGesture(); + try { + await navigator.hid.getDevices(); + return 'HID getDevice succeeded'; + } catch(e) { + if (e.name == 'SecurityError' && + e.message.includes( + 'Access to the feature "hid" is disallowed by permissions policy.')) { + return 'HID getDevice failed'; + } else { + return `HID getDevice failed with unknown error - ${e.name}: ${e.message}`; + } + } + }); + assert_equals(result, 'HID getDevice failed', + 'HID getDevice must fail in a fenced frame.'); +}, 'HID getDevice must fail in a fenced frame'); +</script> + +</body> diff --git a/testing/web-platform/tests/fenced-frame/history-back-and-forward-should-not-work-in-fenced-tree.https.html b/testing/web-platform/tests/fenced-frame/history-back-and-forward-should-not-work-in-fenced-tree.https.html new file mode 100644 index 0000000000..910b2ba792 --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/history-back-and-forward-should-not-work-in-fenced-tree.https.html @@ -0,0 +1,66 @@ +<!DOCTYPE html> +<html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/utils.js"></script> +<script src="/common/utils.js"></script> +<title>history-back-and-forward-should-not-work-in-fenced-tree</title> + +<body> + <script> + const test_desc = "history.back() and history.forward() should be " + + "restricted within a fenced tree in shadowDOM thus have" + + " no effect when called within a fenced tree scope."; + const history_navigation_performed_key = token(); + const outer_page_ready_key = token(); + + // attach a fenced frame to exeute a series of back and forward history + // navigations. + attachFencedFrame( + generateURL( + "resources/history-back-and-forward-should-not-work-in-" + + "fenced-tree-inner.html", + [history_navigation_performed_key, outer_page_ready_key])); + + promise_test(async function () { + await nextValueFromServer(history_navigation_performed_key); + + // Perform a series of history.pushState() to help observe any popstate due + // to back and forward history navigations. + window.history.pushState(1, document.title, '#tag1'); + window.history.pushState(2, document.title, '#tag2'); + window.history.pushState(3, document.title, '#tag3'); + + writeValueToServer(outer_page_ready_key, "yes"); + + // Assert restricted history.back() within fenced frame. + await nextValueFromServer(history_navigation_performed_key); + assert_equals(window.history.state, 3, "history.back() should be " + + "restricted and will not work when called from a fenced frame."); + + writeValueToServer(outer_page_ready_key, "yes"); + + // Assert restricted history.forward() within fenced frame. + await nextValueFromServer(history_navigation_performed_key); + assert_equals(window.history.state, 3, "history.forward() should be " + + "restricted and will not work when called from a fenced frame."); + + writeValueToServer(outer_page_ready_key, "yes"); + + // Assert restricted history.back() within iframe in fenced frame. + await nextValueFromServer(history_navigation_performed_key); + assert_equals(window.history.state, 3, "history.back() should be " + + "restricted and will not work within an iframe embeeded in a fenced " + + "frame."); + + writeValueToServer(outer_page_ready_key, "yes"); + + // Assert restricted history.forward() within iframe in fenced frame. + await nextValueFromServer(history_navigation_performed_key); + assert_equals(window.history.state, 3, "history.forward() should be " + + "restricted and will not work within an iframe embeeded in a fenced " + + "frame."); + }, test_desc); + </script> +</body> +</html> diff --git a/testing/web-platform/tests/fenced-frame/history-length-fenced-navigations-replace-do-not-contribute-to-joint.https.html b/testing/web-platform/tests/fenced-frame/history-length-fenced-navigations-replace-do-not-contribute-to-joint.https.html new file mode 100644 index 0000000000..bd1d5f7309 --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/history-length-fenced-navigations-replace-do-not-contribute-to-joint.https.html @@ -0,0 +1,55 @@ +<!DOCTYPE html> +<title>Test history.length in outermost document</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/utils.js"></script> +<script src="/common/utils.js"></script> + +<body> +<script> +promise_test(async () => { + const kInnerAssertion = "Navigations inside of a fenced frame are always " + + "replacement navigations and never increase " + + "`history.length` inside the fence: "; + const kOuterAssertion = "Navigations inside of a fenced frame never " + + "contribute to joint session history, or increase " + + "`history.length` outside the fence: "; + // This is used by + // `resources/history-length-fenced-navigations-replace-do-not-contribute-to-joint-inner.html` + // to let us know once a navigation is complete inside the fence. The inner + // page will test its `history.length` and report to us "PASS <frame type>" or + // "FAIL <frame type>" (the history length should be unaffected by the + // navigation). If the inner test passes, then we will check our + // `history.length` and ensure that the inner fenced navigation did not + // contribute to the joint session history. + const fenced_navigation_complete_key = token(); + // This is sent by us to the inner page to let it know that we're finished + // observing the effects of the last navigation, and that it should perform + // the next one for us to observe. + const outer_page_ready_for_next_fenced_navigation_key = token(); + + const level = "top-level-fenced-frame"; + + attachFencedFrame(generateURL( + "resources/history-length-fenced-navigations-replace-do-" + + "not-contribute-to-joint-inner.html", + [fenced_navigation_complete_key, + outer_page_ready_for_next_fenced_navigation_key, + level])); + + const tests = ["top-level-fenced-frame", "nested-fenced-frame", "nested-iframe"]; + for (test_type of tests) { + // Wait for the fenced navigations to complete, and then see if they + // observable via the outermost `history.length`. + let result = await nextValueFromServer(fenced_navigation_complete_key); + assert_equals(result, "PASS > " + test_type, kInnerAssertion + test_type); + assert_equals(history.length, 1, kOuterAssertion + test_type); + + // Acknowledge the results, and let the fenced frame know that we're ready + // to observe more fenced navigations. + writeValueToServer(outer_page_ready_for_next_fenced_navigation_key, "READY"); + } +}, "All fenced navigations should be replace-only and not contribute to joint " + + "session history"); +</script> +</body> diff --git a/testing/web-platform/tests/fenced-frame/history-length-outer-page-navigation-not-reflected-in-fenced.https.html b/testing/web-platform/tests/fenced-frame/history-length-outer-page-navigation-not-reflected-in-fenced.https.html new file mode 100644 index 0000000000..da64304031 --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/history-length-outer-page-navigation-not-reflected-in-fenced.https.html @@ -0,0 +1,78 @@ +<!DOCTYPE html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/utils.js"></script> +<script src="/common/utils.js"></script> +<title>history-length-outer-page-navigation-not-reflected-in-fenced</title> + +<body> +<script> +const fenced_history_length_key = token(); +const outer_page_ready_for_next_navigation_key = token(); + +async function runTest(embed_scope_reporting) { + const kAssertionDesc = "history.length Should never reflect the length " + + "of joint session history that occured outside " + + "of the fenced frame tree. history.length will " + + "always return 1." + + ////////////// BEGIN NAVIGATIONS + // This block performs a sequence of 'kNavigationLimit' navigations in: + // -- the outer page + 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 + + // Append an iframe to the outer page for subsequent navigations within + // the iframe + const iframe = document.createElement('iframe'); + const embed_scope_iframe = "outer_page::iframe"; + iframe.src = generateURL("resources/history-length-outer-page-navigation-" + + "not-reflected-in-fenced-inner.html", + [fenced_history_length_key, outer_page_ready_for_next_navigation_key, + embed_scope_iframe, null]); + document.body.append(iframe); + + await nextValueFromServer(outer_page_ready_for_next_navigation_key); + + // Append a fenced frame to observe 'history.length' and report it back + // to the outer page + const embed_scope_fenced_frame = "outer_page::fenced_frame"; + attachFencedFrame(generateURL( + "resources/history-length-outer-page-navigation-" + + "not-reflected-in-fenced-inner.html", + [fenced_history_length_key, outer_page_ready_for_next_navigation_key, + embed_scope_fenced_frame, embed_scope_reporting]) + ); + + // Wait for the 'embed_scope_reporting' to report 'history.length' + let result = await nextValueFromServer(fenced_history_length_key); + assert_equals(result, "PASS > " + " history.length: 1", kAssertionDesc); +} + +promise_test(async () => { + await runTest("outer_page::fenced_frame"); +}, "history.length should not reflect navigations within outer page in " + + "fenced frame"); + +promise_test(async () => { + await runTest("outer_page::fenced_frame::iframe"); +}, "history.length should not reflect navigations within outer page in " + + "fenced frame nexted iframe"); +</script> +</body> diff --git a/testing/web-platform/tests/fenced-frame/ignore-child-fenced-frame-onload-event.https.html b/testing/web-platform/tests/fenced-frame/ignore-child-fenced-frame-onload-event.https.html new file mode 100644 index 0000000000..a542c25909 --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/ignore-child-fenced-frame-onload-event.https.html @@ -0,0 +1,31 @@ +<!DOCTYPE html> +<title>Ignore child fenced frame onload event</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/common/utils.js"></script> +<script src="resources/utils.js"></script> + +<body> +</body> +<script> + promise_test(async () => { + const toplevel_loaded_key = token(); + const result_key = token(); + + // Appends a fencedframe to the top-level doc. + attachFencedFrame(generateURL("resources/ignore-child-fenced-frame-onload" + + "-event-inner.html", ["fencedframe", toplevel_loaded_key, result_key])); + + // Relays the messsage to the fenced frame when the onload event is fired. + window.onload = function () { + writeValueToServer(toplevel_loaded_key, "yes"); + } + + assert_equals(await nextValueFromServer(result_key), "passed", + "The parent frame onload event should not be blocked by any onload " + + "event from subframes belonging to fenced tree, but should be blocked " + + "by that of any non fenced subframe"); + + }, "ignore child fenced frame onload event test."); +</script> +</html> diff --git a/testing/web-platform/tests/fenced-frame/input-on-coop-fenced-frame.https.html b/testing/web-platform/tests/fenced-frame/input-on-coop-fenced-frame.https.html new file mode 100644 index 0000000000..e7b44a0784 --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/input-on-coop-fenced-frame.https.html @@ -0,0 +1,57 @@ +<!DOCTYPE html> +<title>Test input inside a fenced frame with non-matching COOP.</title> +<link rel="help" href="https://crbug.com/1316535"> +<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> +<body> +<script> + +// Navigating a fenced frame to a page with a COOP mismatch (in this case, +// unsafe-none -> same-origin-allow-popups) will force the page into a new +// browsing context group. +// +// In Chromium, this forces using a "speculative" frame host which initializes +// the new frame using a different path than normal. +// +// This test is a basic smoke test that sending input through this setup works +// and doesn't cause any crashes. See linked bug for more details. +// +// TODO(https://crbug.com/1411599): COOP is no longer taken into account inside +// fenced frames. To test the "speculative" frame host route, we might only need +// to do a cross-site navigation. Once figured out, use the new approach to test +// that route. +promise_test(async () => { + const frame = attachFencedFrameContext({ + headers: [["Cross-Origin-Opener-Policy", "same-origin-allow-popups"]] + }); + + await frame.execute(async () => { + if (document.readyState !== 'complete') { + await new Promise((resolve) => { addEventListener('load', resolve); }); + } + + assert_equals(document.readyState, 'complete', 'Fenced frame was loaded'); + + window.was_clicked = false; + addEventListener('click', () => { + window.was_clicked = true; + }); + }); + + await test_driver.click(frame.element); + + // Ensure the fenced frame actually saw the click but the real test is that + // there isn't a crash. + await frame.execute(async () => { + assert_equals(window.was_clicked, true, 'Fenced frame received click'); + }); + +}, 'Input in non-matching COOP fenced frame doesn\'t crash.'); +</script> +</body> diff --git a/testing/web-platform/tests/fenced-frame/input-on-coop-fenced-frame.https.html.headers b/testing/web-platform/tests/fenced-frame/input-on-coop-fenced-frame.https.html.headers new file mode 100644 index 0000000000..073ce7adfb --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/input-on-coop-fenced-frame.https.html.headers @@ -0,0 +1 @@ +Cross-Origin-Opener-Policy: unsafe-none diff --git a/testing/web-platform/tests/fenced-frame/insecure-context.html b/testing/web-platform/tests/fenced-frame/insecure-context.html new file mode 100644 index 0000000000..44db6432a4 --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/insecure-context.html @@ -0,0 +1,33 @@ +<!DOCTYPE html> +<title>Insecure Context Test</title> +<script src="/common/utils.js"></script> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/utils.js"></script> + +<body> + +<script> +promise_test(async t => { + const loaded_key = token(); + attachFencedFrame(generateURL( + "resources/csp-fenced-frame-src-allowed-inner.html", [loaded_key])); + + // There is no API to observe whether the document in the FencedFrame loaded + // or not. Instead, set up a timeout. If the document loads, "loaded" will be + // sent to the server. Otherwise "blocked" will be sent after 3 seconds. + step_timeout(() => { + writeValueToServer(loaded_key, "blocked"); + }, 3000); + + const message = await nextValueFromServer(loaded_key); + + assert_equals(message, "blocked"); +}, 'FencedFrame is not available in an insecure context'); + +promise_test(async t => { + assert_throws_js(TypeError, () => navigator.canLoadAdAuctionFencedFrame()); +}, 'navigator.canLoadAdAuctionFencedFrame is unavailable in insecure contexts'); +</script> + +</body> diff --git a/testing/web-platform/tests/fenced-frame/intersection-observer.https.html b/testing/web-platform/tests/fenced-frame/intersection-observer.https.html new file mode 100644 index 0000000000..592e7c127f --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/intersection-observer.https.html @@ -0,0 +1,53 @@ +<!DOCTYPE html> +<title>Test Intersection Observer in fenced frame</title> +<script src="/common/rendering-utils.js"></script> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/common/utils.js"></script> +<script src="resources/utils.js"></script> +<script src="/common/utils.js"></script> +<style> +fencedframe { + width: 100px; + height: 100px; + position: fixed; + top: 0px; + left: 0px; + border: unset; +} +</style> + +<body> +<script> +promise_test(async (t) => { + // first entry after observe. + const io_entry_on_registration = token(); + // entry after transform. + const io_entry_on_transform = token(); + // entry with clip. + const io_entry_on_clip = token(); + + const frame = attachFencedFrame(generateURL( + "resources/frame-with-intersection-observer.html", + [io_entry_on_registration, io_entry_on_transform, io_entry_on_clip])); + + let result = await nextValueFromServer(io_entry_on_registration); + assert_equals(result, "0,0,100,100,true", + "Subscribing to IO dispatches a notification"); + + // Apply a transform to the fencedframe and ensure it gets applied to the + // intersectionRect. + frame.style.transform = 'translate(-10px, -20px)'; + result = await nextValueFromServer(io_entry_on_transform); + assert_equals(result, "10,20,90,80,true", + "Transform applies to intersection rect"); + + // Now add a clip to the fencedframe which should clip the intersectionRect. + frame.style.clipPath = 'inset(10px)'; + result = await nextValueFromServer(io_entry_on_clip); + assert_equals(result, "10,20,80,70,false", "Clip applies to intersection rect"); +}, 'Intersection Observer Test'); + +</script> +</body> +</html> diff --git a/testing/web-platform/tests/fenced-frame/invalid-url.https.html b/testing/web-platform/tests/fenced-frame/invalid-url.https.html new file mode 100644 index 0000000000..4e17185d81 --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/invalid-url.https.html @@ -0,0 +1,17 @@ +<!DOCTYPE html> +<title>Test Navigate Fenced Frame to Invalid URL</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/common/utils.js"></script> +<script src="resources/utils.js"></script> +<script src="/common/utils.js"></script> + +<body> +<script> +promise_test(async (t) => { + attachFencedFrame('http://localhost:95101/fenced_frame.php'); +}, 'URL with invalid port specified'); + +</script> +</body> +</html> diff --git a/testing/web-platform/tests/fenced-frame/key-scrolling.https.html b/testing/web-platform/tests/fenced-frame/key-scrolling.https.html new file mode 100644 index 0000000000..7a23a72cb0 --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/key-scrolling.https.html @@ -0,0 +1,67 @@ +<!DOCTYPE html> +<title>Test keyboard scroll bubbling from a fenced frame.</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.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/utils.js"></script> +<script src="/common/dispatcher/dispatcher.js"></script> +<script src="resources/utils.js"></script> +<style> + body { + /* Make main frame scrollable */ + width: 200vw; + height: 200vh; + } +</style> + +<body> +<script> + +// Ensure keyboard scrolling from inside a fenced frame is bubbled out to the +// embedding frame. +promise_test(async t => { + const frame = attachFencedFrameContext({html: ` + <!DOCTYPE html> + <style> + body { + /* Make fenced frame scrollable */ + width: 200vw; + height: 200vh; + } + </style> + <script src="/resources/testdriver.js"><\/script> + <script src="/resources/testdriver-actions.js"><\/script> + <script src="/resources/testdriver-vendor.js"><\/script> + `}); + + // Scroll the fenced frame to its full extent so that left/down arrow key + // scrolling will bubble to the embedder. + await frame.execute(async () => { + window.scrollTo(10000, 10000); + }); + + assert_equals(window.scrollX, 0, '[PRECONDITION] main frame has no x scroll.'); + assert_equals(window.scrollY, 0, '[PRECONDITION] main frame has no y scroll.'); + + // Simulate a right arrow and down arrow key in the fenced frame. + await frame.execute(async () => { + const arrow_right = "\uE014"; + const arrow_down = "\uE015"; + + test_driver.send_keys(document.body, arrow_right); + test_driver.send_keys(document.body, arrow_down); + }); + + // Use step_wait to poll since the scroll may be executed asynchronously + // (e.g. IPC to embedder, scroll animation). + await t.step_wait(() => window.scrollX > 0, "Wait for horizontal scroll."); + assert_greater_than(window.scrollX, 0, 'Horizontal scroll should bubble to main frame.'); + + await t.step_wait(() => window.scrollY > 0, "Wait for vertical scroll."); + assert_greater_than(window.scrollY, 0, 'Vertical scroll should bubble to main frame.'); +}, 'Keyboard scrolling bubbles out of fenced frame'); + +</script> +</body> diff --git a/testing/web-platform/tests/fenced-frame/key-value-store.https.html b/testing/web-platform/tests/fenced-frame/key-value-store.https.html new file mode 100644 index 0000000000..ba6b1c0a4f --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/key-value-store.https.html @@ -0,0 +1,43 @@ +<!DOCTYPE html> +<title>Test the key value store</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/common/utils.js"></script> +<script src="resources/utils.js"></script> + +<body> + +<script> +promise_test(async () => { + const key = token(); + const test_value = "TESTVALUE"; + + let server_value = await readValueFromServer(key); + assert_false(server_value.status, + "The server returns a sentinel value when requesting a value " + + "that the stash does not have"); + + server_value = await readValueFromServer(key); + assert_false(server_value.status, + "Requesting a not-set value twice is idempotent"); + + writeValueToServer(key, ""); + server_value = await nextValueFromServer(key); + assert_equals(server_value, "", + "The server correctly identifies that an empty string was " + + "set, and returns it, and not the sentinel value"); + + writeValueToServer(key, test_value); + server_value = await nextValueFromServer(key); + assert_equals(server_value, test_value, + "The server correctly sets and returns non-empty strings"); + + writeValueToServer(key, ""); + server_value = await nextValueFromServer(key); + assert_equals(server_value, "", + "The server correctly identifies empty strings after dealing " + + "with non-empty-string values"); +}, "Test the key-value store, specifically with empty strings"); +</script> + +</body> diff --git a/testing/web-platform/tests/fenced-frame/load-ad-with-size.https.html b/testing/web-platform/tests/fenced-frame/load-ad-with-size.https.html new file mode 100644 index 0000000000..c06205f056 --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/load-ad-with-size.https.html @@ -0,0 +1,29 @@ +<!DOCTYPE html> +<title>Fenced frames loading a winning ad from FLEDGE auction with size</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/utils.js"></script> +<script src="/common/dispatcher/dispatcher.js"></script> +<script src="/common/utils.js"></script> + +<body> + <script> + promise_test(async (t) => { + var frame = await attachFencedFrameContext({ + generator_api: "fledge", + resolve_to_config: true, + ad_with_size: true + }); + + const assert_dimensions = + (expected_width, expected_height) => { + getComputedStyle(document.documentElement).width; // Force layout. + assert_equals(window.innerWidth, expected_width, "width"); + assert_equals(window.innerHeight, expected_height, "height"); + } + await frame.execute(assert_dimensions, [100, 50]); + }, "Fenced frame loading an ad with size."); + </script> +</body> + +</html>
\ No newline at end of file diff --git a/testing/web-platform/tests/fenced-frame/loading.https.html b/testing/web-platform/tests/fenced-frame/loading.https.html new file mode 100644 index 0000000000..2f39af0395 --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/loading.https.html @@ -0,0 +1,26 @@ +<!DOCTYPE html> +<title>Fenced frames loading tests</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/utils.js"></script> +<script src="/common/utils.js"></script> + +<body> +<script> + for (const resolve_to_config of [true, false]) { + promise_test(async (t) => { + const key = token(); + + attachFencedFrame(await runSelectURL("resources/embeddee.html", [key], + resolve_to_config)); + const result = await nextValueFromServer(key); + + assert_equals(result, "PASS", + "The fenced frame with src=urn:uuid should load"); + }, "fenced frame loading " + + (resolve_to_config ? "a config." : "an urn:uuid.")); + } +</script> +</body> + +</html> diff --git a/testing/web-platform/tests/fenced-frame/location-ancestorOrigins.https.html b/testing/web-platform/tests/fenced-frame/location-ancestorOrigins.https.html new file mode 100644 index 0000000000..f72a668bdc --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/location-ancestorOrigins.https.html @@ -0,0 +1,51 @@ +<!DOCTYPE html> +<title>Test location.ancestorOrigins</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/common/get-host-info.sub.js"></script> +<script src="/common/utils.js"></script> +<script src="resources/utils.js"></script> + +<body> + +<script> +promise_test(async () => { + const location_ao_key = token(); + const location_ao_ack_key = token(); + + // We load the top-level fenced frame in a cross-origin, so that we can + // more-completely verify the important cross-origin + // `location.ancestorOrigins` case. + const cross_origin = get_host_info().HTTPS_REMOTE_ORIGIN; + const cross_origin_fenced_frame = getRemoteOriginURL(generateURL( + 'resources/location-ancestorOrigins-inner.https.html', + [location_ao_key, location_ao_ack_key] + ), https=true); + attachFencedFrame(cross_origin_fenced_frame); + + // Get the result for the top-level fenced frame. + const fenced_frame_result = await nextValueFromServer(location_ao_key); + assert_equals(fenced_frame_result, "", "The top-level fenced frame has the " + + "right ancestor origins"); + + // Write an ACK, so that the fenced frame knows it can send message over the + // `window_parent_key` channel again. + writeValueToServer(location_ao_ack_key, "ACK"); + + // Get the result for the iframe inside the fenced frame. + const iframe_in_fenced_frame_result = await nextValueFromServer(location_ao_key); + assert_equals(iframe_in_fenced_frame_result, cross_origin, "The iframe " + + "inside the top-level fenced frame has the right " + + "ancestor origins"); + + writeValueToServer(location_ao_ack_key, "ACK"); + + // Get the result for the nested fenced frame. + const nested_fenced_frame_result = await nextValueFromServer(location_ao_key); + assert_equals(nested_fenced_frame_result, "", "The nested fenced frame " + + "inside the top-level fenced frame has the right ancestor " + + "origins"); +}, "location.ancestorOrigins"); +</script> + +</body> diff --git a/testing/web-platform/tests/fenced-frame/mediaDevices-setCaptureHandle.https.html b/testing/web-platform/tests/fenced-frame/mediaDevices-setCaptureHandle.https.html new file mode 100644 index 0000000000..fcc95a401f --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/mediaDevices-setCaptureHandle.https.html @@ -0,0 +1,32 @@ +<!DOCTYPE html> +<title>Test MediaDevice navigator.mediaDevices.setCaptureHandleConfig()</title> +<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> + +<body> +<script> +promise_test(async () => { + const frame = attachFencedFrameContext(); + await frame.execute(async () => { + // If capture handle is set inside the fenced frame, it should fail because + // it should not be the top-level browsing context. + // https://w3c.github.io/mediacapture-handle/identity/index.html#set-capture-handle-config + try { + navigator.mediaDevices.setCaptureHandleConfig({ + handle: 'dummyhandle', + permittedOrigins: ["*"], + }); + throw 'The setCaptureHandleConfig request should not succeed.'; + } catch (e) { + assert_equals(e.name, 'InvalidStateError'); + assert_equals(e.message, + "Failed to execute 'setCaptureHandleConfig' on 'MediaDevices': " + + 'Can only be called from the top-level document.'); + } + }); +}, 'navigator.mediaDevices.setCaptureHandleConfig'); +</script> +</body> diff --git a/testing/web-platform/tests/fenced-frame/navigate-ancestor-by-name.https.html b/testing/web-platform/tests/fenced-frame/navigate-ancestor-by-name.https.html new file mode 100644 index 0000000000..a5df1e9942 --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/navigate-ancestor-by-name.https.html @@ -0,0 +1,67 @@ +<!DOCTYPE html> +<title>Test named frame navigation of ancestors.</title> +<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> + +<body> +<script> +promise_test(async () => { + // This test uses the following layout: + // A: Top-level frame + // B: iframe + // C: fencedframe + // D: fencedframe + // E: iframe + // + // The purpose is to test that name resolution of navigation targets ignores + // ancestors beyond fence boundaries. + + // Create an iframe B. + const B = attachIFrameContext(); + await B.execute(async () => { + window.name = "B"; + + // Create a fenced frame C inside of it. + window.C = attachFencedFrameContext(); + await window.C.execute(async () => { + window.name = "C"; + + // Navigate the target "B" from inside the fenced frame. + // It should open in a new tab due to fenced name lookup. + window.open("resources/dummy.html", "B"); + }); + }); + + // Observe that it created a new window, and the frame B is still here. + await B.execute(async () => { + // Create a nested iframe and fenced frame. + await window.C.execute(async () => { + window.D = attachFencedFrameContext(); + window.E = attachIFrameContext(); + + // Navigate the target "C" from inside the nested fenced frame. + // It should open in a new tab due to fenced name lookup. + await window.D.execute(async () => { + window.open("resources/dummy.html", "C"); + }); + }); + // Observe that it created a new window, and the frame C is still here. + await window.C.execute(async () => { + // Now attempt to navigate the target "C" from inside the iframe. + // It should open in a new tab with a console error, because sandboxed + // iframes (inherited from the fenced frame) are not allowed to navigate + // their ancestors. + await window.E.execute(() => { + window.open("resources/dummy.html", "C"); + }); + }); + + // Observe that C is still here. + await window.C.execute(() => {}); + }); +}, 'navigate named ancestors'); +</script> +</body> diff --git a/testing/web-platform/tests/fenced-frame/navigate-ancestor-nested-fenced-frame.https.html b/testing/web-platform/tests/fenced-frame/navigate-ancestor-nested-fenced-frame.https.html new file mode 100644 index 0000000000..ec41fe8757 --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/navigate-ancestor-nested-fenced-frame.https.html @@ -0,0 +1,20 @@ +<!DOCTYPE html> +<title>Test navigating an ancestor frame from a nested fenced frame</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/utils.js"></script> +<script src="/common/utils.js"></script> +<script src="resources/navigate-ancestor-helper.js"></script> + +<body> +<script> +promise_test(async t => { + await runNavigateAncestorTest("nested fenced frame", "parent"); +}, "Nested fenced frames that navigate _parent end up navigating themselves"); + +promise_test(async t => { + await runNavigateAncestorTest("nested fenced frame", "top"); +}, "Nested fenced frames that navigate _top end up navigating themselves"); +</script> + +</body> diff --git a/testing/web-platform/tests/fenced-frame/navigate-ancestor-nested-iframe.https.html b/testing/web-platform/tests/fenced-frame/navigate-ancestor-nested-iframe.https.html new file mode 100644 index 0000000000..977eae1748 --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/navigate-ancestor-nested-iframe.https.html @@ -0,0 +1,20 @@ +<!DOCTYPE html> +<title>Test navigating an ancestor frame from a iframe in a fenced frame</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/utils.js"></script> +<script src="/common/utils.js"></script> +<script src="resources/navigate-ancestor-helper.js"></script> + +<body> +<script> +promise_test(async t => { + await runNavigateAncestorTest("nested iframe", "parent"); +}, "Iframes nested in fenced frames fail to navigate _parent"); + +promise_test(async t => { + await runNavigateAncestorTest("nested iframe", "top"); +}, "Iframes nested in fenced frames fail to navigate _top"); +</script> + +</body> diff --git a/testing/web-platform/tests/fenced-frame/navigate-ancestor-top-level-fenced-frame.https.html b/testing/web-platform/tests/fenced-frame/navigate-ancestor-top-level-fenced-frame.https.html new file mode 100644 index 0000000000..9907f25292 --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/navigate-ancestor-top-level-fenced-frame.https.html @@ -0,0 +1,20 @@ +<!DOCTYPE html> +<title>Test navigating an ancestor frame from a fenced frame</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/utils.js"></script> +<script src="/common/utils.js"></script> +<script src="resources/navigate-ancestor-helper.js"></script> + +<body> +<script> +promise_test(async t => { + await runNavigateAncestorTest("top-level fenced frame", "parent"); +}, "Top-level fenced frames that navigate _parent end up navigating themselves"); + +promise_test(async t => { + await runNavigateAncestorTest("top-level fenced frame", "top"); +}, "Top-level fenced frames that navigate _top end up navigating themselves"); +</script> + +</body> diff --git a/testing/web-platform/tests/fenced-frame/navigate-by-name-succeed.https.html b/testing/web-platform/tests/fenced-frame/navigate-by-name-succeed.https.html new file mode 100644 index 0000000000..4d558f8d23 --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/navigate-by-name-succeed.https.html @@ -0,0 +1,37 @@ +<!DOCTYPE html> +<title>Test successful named frame navigation.</title> +<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> + +<body> +<script> +promise_test(async () => { + // This test uses the following layout: + // A: Top-level frame + // B: fencedframe + // C: iframe + // + // The purpose is to test that named target lookups of C succeed in A, i.e. + // that lookups work even when the first child of a frame is fenced. + + const fencedframe = attachFencedFrameContext(); + const iframe = attachIFrameContext(); + + // Give the iframe a name. + await iframe.execute(() => { window.name = "target_frame"; }); + + // Modify state in the iframe, using a JS navigation to the target name. + window.open("javascript:window.success=true;", "target_frame"); + + // Check that the navigation happened in the iframe. + await iframe.execute(() => { + // If the JS code didn't run in the iframe, `window.success` would be + // undefined. + assert_true(window.success, 'The JS code ran in the iframe.'); + }); +}, 'navigate iframe sibling of fenced frame'); +</script> +</body> diff --git a/testing/web-platform/tests/fenced-frame/navigate-descendant-by-name.https.html b/testing/web-platform/tests/fenced-frame/navigate-descendant-by-name.https.html new file mode 100644 index 0000000000..08ce4b99f3 --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/navigate-descendant-by-name.https.html @@ -0,0 +1,90 @@ +<!DOCTYPE html> +<title>Test named frame navigation of descendants</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/utils.js"></script> +<script src="/common/utils.js"></script> + +<body> + <!-- This anchor element is clicked via script to navigate a target frame by + name. The target frame will always exist inside a fenced frame tree, and + therefore shouldn't actually work. The expected behavior is that the + navigation should end up in a new top-level browsing context, as per [1], + which will communicate back to the main page (via the server stash) + letting us know that the navigation succeeded, and did not successfully + target a frame inside the fenced frame boundary. + + [1]: https://html.spec.whatwg.org/C/#the-rules-for-choosing-a-browsing-context-given-a-browsing-context-name + --> +<script> + const kAssertion = "The anchor element did not navigate a frame inside the " + + "fenced frame boundary: "; + // This is used by `navigate-by-name-reporting-helper.html` to let us know if + // the navigation correctly opened a new top-level popup window, or + // incorrectly targeted a browsing context inside the fenced frame boundary. + const navigation_success_key = token(); + // This is sent by the `navigate-by-name-inner.html` to indicate that it has + // set up a frame whose name is `target_frame`, that we are supposed to try + // and navigate by name. + const ready_for_navigation_key = token(); + + const a = document.createElement("a"); + a.href = generateURL('resources/navigate-by-name-reporting-helper.html', + [navigation_success_key]); + a.innerText = "Click to navigate target frame"; + a.target = "target_frame"; + document.body.append(a); + +async function runTest(test_type) { + const fenced_frame = + attachFencedFrame(generateURL( + `resources/navigate-by-name-inner.html`, + [ready_for_navigation_key, test_type])); + + // Wait for the fenced frame to say it is ready for us (the outer page) to + // initiate a named frame navigation, targeting a frame inside the fence. + let result = await nextValueFromServer(ready_for_navigation_key); + assert_equals(result, "READY", "The top-level fenced frame is ready for " + + "us to navigate"); + + // Now that the fenced frame has a frame whose name is `target_frame`, let's + // try and navigate it. + a.click(); + result = await nextValueFromServer(navigation_success_key); + assert_equals(result, "PASS", kAssertion + test_type); + + // Get a reference to the window opened up by the anchor navigation, and + // close it. + const win = window.open("", "target_frame"); + win.close(); + + // Clean up the fenced frame + document.body.removeChild(fenced_frame); +} + +promise_test(async() => { + // First just test that when we have no real target frame to navigate, + // everything works as expected. + a.click(); + const result = await nextValueFromServer(navigation_success_key); + assert_equals(result, "PASS", "The initial test works"); + + // Get a reference to the already-opened window and close it. + const win = window.open("", "target_frame"); + win.close(); +}, "setup"); + +promise_test(async () => { + return runTest("top-level fenced frame"); +}, "navigate top-level fenced frame by name"); + +promise_test(async () => { + return runTest("nested iframe"); +}, "navigate iframe nested in a fenced frame by name"); + +promise_test(async () => { + return runTest("nested fenced frame"); +}, "navigate nested fenced frame by name"); + +</script> +</body> diff --git a/testing/web-platform/tests/fenced-frame/navigate-related-page-by-name.https.html b/testing/web-platform/tests/fenced-frame/navigate-related-page-by-name.https.html new file mode 100644 index 0000000000..755f32a5a3 --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/navigate-related-page-by-name.https.html @@ -0,0 +1,60 @@ +<!DOCTYPE html> +<title>Test named frame navigation of related pages.</title> +<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> + +<body> +<script> +promise_test(async () => { + // A: top-level frame + // C: fencedframe + // B: auxiliary browsing context + // + // C should not be able to navigate B. + + // Create an auxiliary browsing context with a particular name. + const second_window = attachWindowContext({target: "target_name"}); + + // Create a fenced frame, and use the same target name inside of it. + const frame = attachFencedFrameContext(); + await frame.execute(async () => { + window.open("resources/dummy.html", "target_name"); + }); + + // Confirm that the top-level frame's related page (`second_window`) + // wasn't navigated by the fenced frame, i.e. that name resolution + // for related pages is fenced. + await second_window.execute(() => {}); +}, 'navigate related pages from inside a fenced frame'); + +promise_test(async () => { + // A: top-level frame + // B: auxiliary browsing context + // C: fencedframe + // + // A should not be able to navigate C. + + // Create an auxiliary browsing context. + const second_window = attachWindowContext(); + await second_window.execute(async () => { + // Create a fenced frame inside that context and give it a particular name. + window.frame = attachFencedFrameContext(); + await window.frame.execute(() => { + window.name = "target_name"; + }); + }); + + // Navigate that target name from the original top-level frame. + window.open("resources/dummy.html", "target_name"); + + // Confirm that the fenced frame wasn't navigated. + await second_window.execute(async () => { + await frame.execute(() => {}); + }); +}, 'navigate fenced frames inside related pages from the embedder'); + +</script> +</body> diff --git a/testing/web-platform/tests/fenced-frame/navigator-keyboard-layout-map.https.html b/testing/web-platform/tests/fenced-frame/navigator-keyboard-layout-map.https.html new file mode 100644 index 0000000000..03fbc062e1 --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/navigator-keyboard-layout-map.https.html @@ -0,0 +1,25 @@ +<!DOCTYPE html> +<title>Test navigator.keyboard.getLayoutMap()</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/utils.js"></script> +<script src="/common/utils.js"></script> + +<body> + +<script> +promise_test(async () => { + const keyboard_layout_key = token(); + + attachFencedFrame(generateURL( + "resources/navigator-keyboard-layout-map-inner.html", + [keyboard_layout_key])); + const actual_result = await nextValueFromServer(keyboard_layout_key); + + assert_equals(actual_result, "rejected", + "The fenced frame is not allowed to fetch keyboard map."); + +}, "keyboard.getLayoutMap()"); +</script> + +</body> diff --git a/testing/web-platform/tests/fenced-frame/navigator-keyboard-lock.https.html b/testing/web-platform/tests/fenced-frame/navigator-keyboard-lock.https.html new file mode 100644 index 0000000000..74092a41d2 --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/navigator-keyboard-lock.https.html @@ -0,0 +1,25 @@ +<!DOCTYPE html> +<title>Test navigator.keyboard.lock</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/utils.js"></script> +<script src="/common/utils.js"></script> + +<body> + +<script> +promise_test(async () => { + const keyboard_lock_key = token(); + + attachFencedFrame(generateURL( + "resources/navigator-keyboard-lock-inner.html", + [keyboard_lock_key])); + const actual_result = await nextValueFromServer(keyboard_lock_key); + + assert_equals(actual_result, "rejected", + "The fenced frame is not allowed to lock keyboard."); + +}, "keyboard.lock"); +</script> + +</body> diff --git a/testing/web-platform/tests/fenced-frame/navigator-subapp.https.html b/testing/web-platform/tests/fenced-frame/navigator-subapp.https.html new file mode 100644 index 0000000000..c5b980537b --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/navigator-subapp.https.html @@ -0,0 +1,30 @@ +<!DOCTYPE html> +<title>navigator.subApp API test</title> +<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> + +<body> +<script> +promise_test(async () => { + const frame = attachFencedFrameContext(); + const result = await frame.execute(async () => { + const expected_error_message = + 'Failed to execute \'list\' on \'SubApps\': API is only supported in primary top-level browsing contexts.'; + try { + const list = await navigator.subApps.list(); + return 'navigator.subApps.list() succeeded'; + } catch (e) { + if (e.name === 'InvalidStateError' && + e.message === expected_error_message) { + return 'navigator.subApps.list() was disallowed'; + } + return 'navigator.subApps.list() failed with unknown error' + + `${e.name} ${e.message}`; + } + }); + assert_equals(result, 'navigator.subApps.list() was disallowed'); +}, 'navigator.subApps.list() should fail in the fenced frame.'); +</script> diff --git a/testing/web-platform/tests/fenced-frame/navigator-vibrate.https.html b/testing/web-platform/tests/fenced-frame/navigator-vibrate.https.html new file mode 100644 index 0000000000..6bcabf4d47 --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/navigator-vibrate.https.html @@ -0,0 +1,60 @@ +<!DOCTYPE html> +<title>Test that navigator.vibrate is disabled in fenced frames.</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.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/utils.js"></script> +<script src="/common/dispatcher/dispatcher.js"></script> +<script src="resources/utils.js"></script> + +<body> +<script> +// Simulate a click in frame context `frame`. +async function click(frame) { + var actions = new test_driver.Actions(); + await actions.pointerMove(0, 0, {origin: frame}) + .pointerDown() + .pointerUp() + .send(); +} + +promise_test(async () => { + // This test ensures that vibration is disabled in fenced frames. + // It uses a top-level frame A and a fenced frame B. + // The structure of the test is as follows: + // - Check that B can't vibrate before user activation. + // - Check that B can't vibrate after user activation. + // - Check that A can't vibrate before user activation. + // - Check that A CAN vibrate after user activation. + + const B = attachFencedFrameContext(); + await B.execute(() => { + assert_false(navigator.userActivation.hasBeenActive); + var success = navigator.vibrate(100); + assert_false(success, + "Vibration failed in fenced frame before user activation"); + }); + + await click(B.element); + await B.execute(() => { + assert_true(navigator.userActivation.hasBeenActive); + var success = navigator.vibrate(100); + assert_false(success, + "Vibration failed in fenced frame, even after user activation"); + }); + + var success = navigator.vibrate(100); + assert_false(navigator.userActivation.hasBeenActive); + assert_false(success, + "Vibration failed in top-level frame before user activation"); + + await click(document.documentElement); + assert_true(navigator.userActivation.hasBeenActive); + var success = navigator.vibrate(100); + assert_true(success, + "Vibration succeeded in top-level frame after user activation"); +}, 'navigator.vibrate'); +</script> +</body> diff --git a/testing/web-platform/tests/fenced-frame/navigator-virtualkeyboard.https.html b/testing/web-platform/tests/fenced-frame/navigator-virtualkeyboard.https.html new file mode 100644 index 0000000000..4f2a17f2ff --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/navigator-virtualkeyboard.https.html @@ -0,0 +1,23 @@ +<!DOCTYPE html> +<title>Test navigator.virtualKeyboard.overlaysContent</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/common/dispatcher/dispatcher.js"></script> +<script src="resources/utils.js"></script> +<script src="/common/utils.js"></script> + +<body> + +<script> +promise_test(async () => { + const frame = attachFencedFrameContext(); + + const response = await frame.execute(() => { + navigator.virtualKeyboard.overlaysContent = true; + return navigator.virtualKeyboard.overlaysContent; + }); + assert_equals(response, false, "overlaysContent had no effect"); +}, "virtualKeyboard.overlaysContent"); +</script> + +</body> diff --git a/testing/web-platform/tests/fenced-frame/nested-opaque-ad-sizes.https.html b/testing/web-platform/tests/fenced-frame/nested-opaque-ad-sizes.https.html new file mode 100644 index 0000000000..2ac8ff003f --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/nested-opaque-ad-sizes.https.html @@ -0,0 +1,44 @@ +<!DOCTYPE html> +<title>Test cases when fenced frame size shouldn't be restricted..</title> +<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> + +<body> +<script> +promise_test(async () => { + const allowed_width = 320; + const allowed_height = 50; + + // Create an opaque-ads fenced frame with an invalid size. + const frame = await attachFencedFrameContext({generator_api: "fledge", + resolve_to_config: true, + attributes: [["width", allowed_width+1], ["height", allowed_height+1]]}); + + await frame.execute(async (allowed_width, allowed_height) => { + // Observe that the size gets coerced to the nearest allowed size. + assert_equals(window.innerWidth, allowed_width, + "The outer opaque-ads fenced frame has its width coerced."); + assert_equals(window.innerHeight, allowed_height, + "The outer opaque-ads fenced frame has its height coerced."); + + // TODO(crbug.com/1427455): This currently fails. Add support for component + // ads being loaded through attachFencedFrameContext(). + // Create a nested (component) opaque-ads fenced frame. + const component_ad_frame = await attachFencedFrameContext({ + attributes: + [["width", allowed_width+1], ["height", allowed_height+1]]}); + + await component_ad_frame.execute((allowed_width, allowed_height) => { + // Observe that the nested frame's size doesn't get coerced. + assert_equals(window.innerWidth, allowed_width+1, + "The nested opaque-ads fenced frame has its requested size."); + assert_equals(window.innerHeight, allowed_height+1, + "The nested opaque-ads fenced frame has its requested size."); + }, [allowed_width, allowed_height]); + }, [allowed_width, allowed_height]); +}, "nested fenced frames don't use the size list"); +</script> +</body> diff --git a/testing/web-platform/tests/fenced-frame/notification.https.html b/testing/web-platform/tests/fenced-frame/notification.https.html new file mode 100644 index 0000000000..636d218e10 --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/notification.https.html @@ -0,0 +1,103 @@ +<!DOCTYPE html> +<title>Test Notification</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="/common/utils.js"></script> +<script src="/common/dispatcher/dispatcher.js"></script> +<script src="resources/utils.js"></script> + +<body> +<script> +promise_test(async () => { + // Notification permission must be allowed or we cannot confirm that + // "new Notification" failed in a fenced frame. + await test_driver.set_permission({name: 'notifications'}, 'granted', true); + assert_equals(Notification.permission, 'granted'); + + const frame = attachFencedFrameContext(); + try { + await frame.execute(() => { + const notification = new Notification('Test notification'); + return new Promise((resolve, reject) => { + // "new Notification" inside the fenced frame should fail even if it is + // allowed in the primary main frame. + notification.addEventListener('error', resolve); + notification.addEventListener('show', reject); + }); + }); + } catch(e) { + assert_unreached('Notification was shown; want not to be shown.' + e); + } +}, 'new Notification should fail inside a fenced frame'); + +promise_test(async () => { + // Notification permission must be allowed or we cannot confirm that + // "new Notification" failed in a fenced frame. + await test_driver.set_permission({name: 'notifications'}, 'granted', true); + assert_equals(Notification.permission, 'granted'); + + const frame = attachFencedFrameContext(); + const message = await frame.execute(async () => { + const getController = () => { + if (navigator.serviceWorker.controller) { + return navigator.serviceWorker.controller; + } + return new Promise(resolve => { + navigator.serviceWorker.addEventListener('controllerchange', () => { + resolve(navigator.serviceWorker.controller); + }); + }); + }; + + await navigator.serviceWorker.register( + 'notification-sw.js', { scope: location.href }); + const ctrl = await getController(); + + return new Promise(resolve => { + ctrl.postMessage('constructor'); + navigator.serviceWorker.onmessage = e => { + resolve(e.data); + }; + }); + }); + assert_equals( + message, "Failed to construct 'Notification': Illegal constructor."); +}, 'new Notification should fail from the service worker in a fenced frame'); + +promise_test(async () => { + // Notification permission must be allowed or we cannot confirm that + // "new Notification" failed in a fenced frame. + await test_driver.set_permission({name: 'notifications'}, 'granted', true); + assert_equals(Notification.permission, 'granted'); + + const frame = attachFencedFrameContext(); + const message = await frame.execute(async () => { + const getController = () => { + if (navigator.serviceWorker.controller) { + return navigator.serviceWorker.controller; + } + return new Promise(resolve => { + navigator.serviceWorker.addEventListener('controllerchange', () => { + resolve(navigator.serviceWorker.controller); + }); + }); + }; + + await navigator.serviceWorker.register( + 'notification-sw.js', { scope: location.href }); + const ctrl = await getController(); + + return new Promise(resolve => { + ctrl.postMessage('showNotification'); + navigator.serviceWorker.onmessage = e => { + resolve(e.data); + }; + }); + }); + assert_equals(message, + "Failed to execute 'showNotification' on 'ServiceWorkerRegistration': " + "showNotification() is not allowed in fenced frames.",); +}, 'showNotification() should fail from the service worker in a fenced frame'); +</script> +</body> diff --git a/testing/web-platform/tests/fenced-frame/opaque-ad-sizes-exact-size.https.html b/testing/web-platform/tests/fenced-frame/opaque-ad-sizes-exact-size.https.html new file mode 100644 index 0000000000..b23d3ab0d7 --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/opaque-ad-sizes-exact-size.https.html @@ -0,0 +1,27 @@ +<!DOCTYPE html> +<title>Test frame size restrictions in FLEDGE.</title> +<meta name="timeout" content="long"> +<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/opaque-ad-sizes-utils.js"></script> + +<body> +<script> + +// Exact size cases. +promise_test(async () => { return runOpaqueAdSizesTest(320, 50, 320, 50); }, '320x50'); +promise_test(async () => { return runOpaqueAdSizesTest(728, 90, 728, 90); }, '728x90'); +promise_test(async () => { return runOpaqueAdSizesTest(970, 90, 970, 90); }, '970x90'); +promise_test(async () => { return runOpaqueAdSizesTest(320, 100, 320, 100); }, '320x100'); +promise_test(async () => { return runOpaqueAdSizesTest(160, 600, 160, 600); }, '160x600'); +promise_test(async () => { return runOpaqueAdSizesTest(300, 250, 300, 250); }, '300x250'); +promise_test(async () => { return runOpaqueAdSizesTest(970, 250, 970, 250); }, '970x250'); +promise_test(async () => { return runOpaqueAdSizesTest(336, 280, 336, 280); }, '336x280'); +promise_test(async () => { return runOpaqueAdSizesTest(320, 480, 320, 480); }, '320x48'); +promise_test(async () => { return runOpaqueAdSizesTest(300, 600, 300, 600); }, '300x600'); +promise_test(async () => { return runOpaqueAdSizesTest(300, 1050, 300, 1050); }, '300x1050'); +</script> +</body> diff --git a/testing/web-platform/tests/fenced-frame/opaque-ad-sizes-special-cases.https.html b/testing/web-platform/tests/fenced-frame/opaque-ad-sizes-special-cases.https.html new file mode 100644 index 0000000000..55cadaeec0 --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/opaque-ad-sizes-special-cases.https.html @@ -0,0 +1,27 @@ +<!DOCTYPE html> +<title>Test frame size restrictions in FLEDGE.</title> +<meta name="timeout" content="long"> +<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/opaque-ad-sizes-utils.js"></script> + +<body> +<script> + +// Rounding cases. +promise_test(async () => { return runOpaqueAdSizesTest(970.1, 250, 970, 250); }, '970.1x250'); +promise_test(async () => { return runOpaqueAdSizesTest(970, 250.1, 970, 250); }, '970x250.1'); +promise_test(async () => { return runOpaqueAdSizesTest(971, 250, 970, 250); }, '971x250'); +promise_test(async () => { return runOpaqueAdSizesTest(970, 251, 970, 250); }, '970x251'); + +// Edge cases. The particular sizes it rounds to aren't important here, just +// that it rounds to one of the sizes on the allowed list. +promise_test(async () => { return runOpaqueAdSizesTest(0, 100, 320, 50); }, '0x100'); +promise_test(async () => { return runOpaqueAdSizesTest(100, 0, 320, 50); }, '100x0'); +promise_test(async () => { return runOpaqueAdSizesTest(Number.MAX_VALUE, Number.MAX_VALUE, 300, 250); }, 'MAXxMAX'); +promise_test(async () => { return runOpaqueAdSizesTest(Number.POSITIVE_INFINITY, Number.POSITIVE_INFINITY, 320, 100); }, 'INFINITYxINFINITY'); +</script> +</body> diff --git a/testing/web-platform/tests/fenced-frame/payment-handler.https.html b/testing/web-platform/tests/fenced-frame/payment-handler.https.html new file mode 100644 index 0000000000..36e5e50726 --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/payment-handler.https.html @@ -0,0 +1,60 @@ +<!DOCTYPE html> +<title>Test Payment Handler API</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="/common/utils.js"></script> +<script src="/common/dispatcher/dispatcher.js"></script> +<script src="resources/utils.js"></script> + +<body> +<script> +promise_test(async () => { + const frame = attachFencedFrameContext(); + const error_name = await frame.execute(async () => { + navigator.serviceWorker.register( + "empty-worker.js", { scope: location.href }); + const registration = await navigator.serviceWorker.ready; + try { + registration.paymentManager; + } catch (e) { + return e.name; + } + }); + assert_equals(error_name, "NotAllowedError", + "paymentManager is not allowed in fenced frames"); +}, 'paymentManager should fail inside a fenced frame'); + +promise_test(async () => { + const frame = attachFencedFrameContext(); + const error_name = await frame.execute(async () => { + const getController = () => { + if (navigator.serviceWorker.controller) { + return navigator.serviceWorker.controller; + } + return new Promise(resolve => { + navigator.serviceWorker.addEventListener('controllerchange', () => { + resolve(navigator.serviceWorker.controller); + }); + }); + }; + + navigator.serviceWorker.register( + "payment-handler-sw.js", { scope: location.href }); + const ctrl = await getController(); + + return new Promise(resolve => { + ctrl.postMessage("test"); + navigator.serviceWorker.onmessage = e => { + const error = e.data; + resolve(error.name); + }; + }); + }); + assert_equals(error_name, "NotAllowedError", + "paymentManager is not allowed from the service worker " + + "in fenced frames"); +}, 'paymentManager should fail from the service worker inside a fenced frame'); +</script> +</body> diff --git a/testing/web-platform/tests/fenced-frame/payment-request.https.html b/testing/web-platform/tests/fenced-frame/payment-request.https.html new file mode 100644 index 0000000000..0d1bbb0113 --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/payment-request.https.html @@ -0,0 +1,37 @@ +<!DOCTYPE html> +<title>Test Payment Rrequest API</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="/common/utils.js"></script> +<script src="/common/dispatcher/dispatcher.js"></script> +<script src="resources/utils.js"></script> + +<body> +<script> +promise_test(async () => { + const frame = attachFencedFrameContext(); + const error_name = await frame.execute(() => { + const methods = [{supportedMethods: ['foo']}]; + const details = { + total: { + label: 'label', + amount: { + currency: 'USD', + value: '5.00' + } + } + }; + + try { + new PaymentRequest(methods, details); + } catch (e) { + return e.name + } + }); + assert_equals(error_name, "SecurityError", + "PaymentRequest is not allowed in fenced frames"); +}, 'new PaymentRequest should fail inside a fenced frame'); +</script> +</body> diff --git a/testing/web-platform/tests/fenced-frame/permission-api-denied-non-standard.https.html b/testing/web-platform/tests/fenced-frame/permission-api-denied-non-standard.https.html new file mode 100644 index 0000000000..ffc06781f6 --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/permission-api-denied-non-standard.https.html @@ -0,0 +1,40 @@ +<!DOCTYPE html> +<title>Permission API in fenced frames should always return denied</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="resources/utils.js"></script> +<script src="/common/utils.js"></script> + +<body> + <script> + // See https://cs.chromium.org/chromium/src/third_party/blink/renderer/modules/permissions/permission_descriptor.idl for valid permission names. + non_standard_permission_list = + ['accessibility-events', + 'clipboard-read', + 'clipboard-write', + 'payment-handler', + 'idle-detection', + 'periodic-background-sync', + 'system-wake-lock', + 'storage-access', + 'window-management', + 'local-fonts']; + + non_standard_permission_list.forEach(function (permission_name) { + promise_test(async t => { + const permission_key = token(); + + attachFencedFrame(generateURL( + 'resources/permission-api-denied-inner.html', + [permission_key, permission_name])); + const actual_result = await nextValueFromServer(permission_key); + assert_equals( + actual_result, 'result: denied', + 'permission API should return denied for ' + permission_name + + ' in fenced frames.'); + }); + }); + </script> +</body> diff --git a/testing/web-platform/tests/fenced-frame/permission-api-denied.https.html b/testing/web-platform/tests/fenced-frame/permission-api-denied.https.html new file mode 100644 index 0000000000..0d193e73cc --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/permission-api-denied.https.html @@ -0,0 +1,46 @@ +<!DOCTYPE html> +<title>Permission API in fenced frames should always return denied</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="resources/utils.js"></script> +<script src="/common/utils.js"></script> + +<body> + <script> + // See https://cs.chromium.org/chromium/src/third_party/blink/renderer/modules/permissions/permission_descriptor.idl for valid permission names. + standard_permission_list = + ['geolocation', + 'notifications', + 'push', + 'midi', + 'camera', + 'microphone', + 'background-fetch', + 'background-sync', + 'persistent-storage', + 'ambient-light-sensor', + 'accelerometer', + 'gyroscope', + 'magnetometer', + 'screen-wake-lock', + 'nfc', + 'display-capture']; + + standard_permission_list.forEach(function (permission_name) { + promise_test(async t => { + const permission_key = token(); + + attachFencedFrame(generateURL( + 'resources/permission-api-denied-inner.html', + [permission_key, permission_name])); + const actual_result = await nextValueFromServer(permission_key); + assert_equals( + actual_result, 'result: denied', + 'permission API should return denied for ' + permission_name + + ' in fenced frames.'); + }); + }); + </script> +</body> diff --git a/testing/web-platform/tests/fenced-frame/permission-geolocation.https.html b/testing/web-platform/tests/fenced-frame/permission-geolocation.https.html new file mode 100644 index 0000000000..98b5a72b3c --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/permission-geolocation.https.html @@ -0,0 +1,73 @@ +<!DOCTYPE html> +<title>Test permission of geolocation</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="resources/utils.js"></script> +<script src="/common/utils.js"></script> + +<body> + +<script> + +async function runTest(policy_header_in_primary_page, + policy_header_in_fenced_frame_page, + allow_attribute_iframe_in_fanced_frame) { + const permission_geolocation_key = token(); + const pipe_for_policy = 'pipe=header(Permissions-Policy,geolocation=self)'; + const test_runner_url = + 'resources/permission-geolocation-test-runner.html?' + + (policy_header_in_primary_page ? pipe_for_policy : ''); + let fenced_frame_url_params = []; + if (policy_header_in_fenced_frame_page) { + fenced_frame_url_params.push(pipe_for_policy); + } + if (allow_attribute_iframe_in_fanced_frame) { + fenced_frame_url_params.push('load_allow_attribute_iframe=true'); + } + const fenced_frame_url = 'permission-geolocation-inner.html?' + + fenced_frame_url_params.join('&'); + + const win = window.open(generateURL(test_runner_url, + [permission_geolocation_key])); + await new Promise(resolve => { + win.onload = resolve; + }); + + const unloadPromise = new Promise(resolve => { + win.onunload = resolve; + }); + + await win.runTest(fenced_frame_url); + win.close(); + await unloadPromise; +} + +promise_test(async t => { + await runTest(false, false, false); +}, 'geolocation permission is not permitted for fenced frames'); + +promise_test(async t => { + await runTest(true, false, false); +}, 'geolocation permission is not permitted for fenced frames, even if a ' + + '`Permissions-Policy` header is sent on the primary page.'); + +promise_test(async t => { + await runTest(false, true, false); +}, 'geolocation permission is not permitted for fenced frames, even if a ' + + '`Permissions-Policy` header is sent on the fenced frame response.'); + +promise_test(async t => { + await runTest(false, false, true); +}, 'geolocation permission is not permitted for fenced frames, even if an ' + + '`allow` attribute is set for an iframe in the fenced frame.'); + +promise_test(async t => { + await runTest(false, true, true); +}, 'geolocation permission is not permitted for fenced frames, even if a ' + + '`Permissions-Policy` header and an `allow` attribute is set for an iframe' + + ' in the fenced frame.'); +</script> + +</body> diff --git a/testing/web-platform/tests/fenced-frame/permission-notification.https.html b/testing/web-platform/tests/fenced-frame/permission-notification.https.html new file mode 100644 index 0000000000..7934125294 --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/permission-notification.https.html @@ -0,0 +1,28 @@ +<!DOCTYPE html> +<title>Test permission of notification</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="resources/utils.js"></script> +<script src="/common/utils.js"></script> + +<body> + +<script> +promise_test(async t => { + const permission_notification_key = token(); + await test_driver.set_permission({name: 'notifications'}, 'granted', true); + + attachFencedFrame(generateURL( + 'resources/permission-notification-inner.html', + [permission_notification_key])); + const actual_result = await nextValueFromServer(permission_notification_key); + + assert_equals( + actual_result, 'result: denied', + 'notification permission should not be granted in the fenced frame.'); +}, 'notification permission should not be granted'); +</script> + +</body> diff --git a/testing/web-platform/tests/fenced-frame/picture-in-picture.https.html b/testing/web-platform/tests/fenced-frame/picture-in-picture.https.html new file mode 100644 index 0000000000..9665f88b9c --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/picture-in-picture.https.html @@ -0,0 +1,31 @@ +<!DOCTYPE html> +<title>Test of picture-in-picture</title> +<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> + +<body> +<script> +promise_test(async () => { + const frame = attachFencedFrameContext(); + const result = await frame.execute(async () => { + const video = document.body.appendChild(document.createElement("video")); + try { + await video.requestPictureInPicture(); + return 'PIP request succeeded.'; + } catch (e) { + if (e.name == 'SecurityError') { + return 'PIP request failed'; + } else { + return `PIP request failed but not with SecurityError - ${e.name}: ${e.message}`; + } + } + }); + assert_equals( + result, 'PIP request failed', + 'PIP request must fail in a fenced frame by permissions policy.'); +}, 'Test HTMLVideoElement.requestPictureInPicture'); +</script> +</body> diff --git a/testing/web-platform/tests/fenced-frame/popup-noopener.https.html b/testing/web-platform/tests/fenced-frame/popup-noopener.https.html new file mode 100644 index 0000000000..9e7e8ce262 --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/popup-noopener.https.html @@ -0,0 +1,57 @@ +<!DOCTYPE html> +<title>Test popup created from a Fenced Frame Tree</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/utils.js"></script> +<script src="/common/utils.js"></script> + +<body> +<script> +// This key is used by `resources/popup-noopener-destination.html` to let us know if a +// a new popup window is correctly opened without an opener. +const popup_noopener_key = token(); +const kAssertion = "window.opener is null "; + +// This key is used by `resources/popup-noopener-inner.html` and +// `resources/create-popup.html to let us know if a +// a new popup window is correctly opened without an openee reference. +const popup_openee_key = token(); +const kAssertion_openee = "openee is null "; + +// This key is used by `resources/popup-noopener-destination.html` to let us know if a +// a new popup window is correctly opened without a name. +const popup_name_key = token(); +const kAssertion_name = "window.name is empty string "; + +async function runTest(test_type) { + const fenced_frame = + attachFencedFrame(generateURL( + `resources/popup-noopener-inner.html`, + [popup_noopener_key, popup_openee_key, popup_name_key, test_type])); + + result = await nextValueFromServer(popup_openee_key); + assert_equals(result, "PASS", kAssertion_openee + test_type); + + result = await nextValueFromServer(popup_noopener_key); + assert_equals(result, "PASS", kAssertion + test_type); + + result = await nextValueFromServer(popup_name_key); + assert_equals(result, "PASS", kAssertion + test_type); + + // Clean up the fenced frame + document.body.removeChild(fenced_frame); +} + +promise_test(async () => { + return runTest("top-level fenced frame"); +}, "Create popup from top-level fenced frame"); + +promise_test(async () => { + return runTest("nested iframe"); +}, "Create popup from iframe nested in a fenced frame"); + +promise_test(async () => { + return runTest("nested fenced frame"); +}, "Create popup from nested fenced frame"); +</script> +</body> diff --git a/testing/web-platform/tests/fenced-frame/prerender.https.html b/testing/web-platform/tests/fenced-frame/prerender.https.html new file mode 100644 index 0000000000..45a39989d2 --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/prerender.https.html @@ -0,0 +1,51 @@ +<!DOCTYPE html> +<title>Test prerendering</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="resources/utils.js"></script> +<script src="/common/utils.js"></script> + +<body> + +<script> +promise_test(async t => { + assert_implements( + 'supports' in HTMLScriptElement, + 'HTMLScriptElement.supports is not supported'); + assert_implements( + HTMLScriptElement.supports('speculationrules'), + '<script type="speculationrules"> is not supported'); + + const prerender_ready_key = token(); + const prerender_loaded_key = token(); + const prerender_activated_key = token(); + const url = generateURL('resources/prerender-inner.html', + [prerender_ready_key, prerender_loaded_key, prerender_activated_key]); + + // TODO: This test expects that the browser always triggers prerendering when + // SpeculationRules is provided. But SpeculationRules allows browsers not to + // trigger the speculative resource loading even if SpeculationRules is + // specified. So we have to use a new WebDriver API that deterministically + // triggers prerendering. + // https://github.com/WICG/nav-speculation/blob/main/speculation-rules-testing.md + const script = document.createElement('script'); + script.type = 'speculationrules'; + script.text = `{"prerender": [{"source": "list", "urls": ["${url}"] }] }`; + document.head.appendChild(script); + + const ready_result = await nextValueFromServer(prerender_ready_key); + assert_equals(ready_result, 'ready'); + + attachFencedFrame(url); + + const loaded_or_acrivated_result = await Promise.race([ + nextValueFromServer(prerender_loaded_key), + nextValueFromServer(prerender_activated_key) + ]); + assert_equals(loaded_or_acrivated_result, 'loaded'); +}, 'Fenced Frame must not load prerendered page.'); +</script> + +</body> diff --git a/testing/web-platform/tests/fenced-frame/presentation-receiver.https.html b/testing/web-platform/tests/fenced-frame/presentation-receiver.https.html new file mode 100644 index 0000000000..c33c72abf0 --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/presentation-receiver.https.html @@ -0,0 +1,26 @@ +<!DOCTYPE html> +<title>Test permission of notification</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="resources/utils.js"></script> +<script src="/common/utils.js"></script> + +<body> + +<script> +promise_test(async t => { + const presentation_receiver_key = token(); + + attachFencedFrame(generateURL('resources/presentation-receiver-inner.html', + [presentation_receiver_key])); + const actual_result = await nextValueFromServer(presentation_receiver_key); + + assert_equals( + actual_result, 'denied', + 'presentation receiver should not be allowed on fenced-frames.'); +}, 'presentation receiver should not be allowed'); +</script> + +</body> diff --git a/testing/web-platform/tests/fenced-frame/reinsert.https.html b/testing/web-platform/tests/fenced-frame/reinsert.https.html new file mode 100644 index 0000000000..b88ff83d00 --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/reinsert.https.html @@ -0,0 +1,32 @@ +<!DOCTYPE html> +<title>Test Content Security Policy</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/utils.js"></script> + +<body> + +<script> +promise_test(async () => { + const frame = attachFencedFrame("resources/dummy.html"); + document.body.removeChild(frame); + document.body.append(frame); + +}, "Fenced frames should not crash and burn when re-inserting a fenced frame"); + +promise_test(async () => { + const frame = document.createElement("iframe"); + frame.sandbox = "allow-scripts allow-same-origin"; + document.body.appendChild(frame); + const fenced_frame = frame.contentDocument.createElement("fencedframe"); + fenced_frame.src = "resources/dummy.html"; + frame.contentDocument.body.appendChild(fenced_frame); + frame.contentDocument.body.removeChild(fenced_frame); + frame.contentDocument.body.append(fenced_frame); +}, "Fenced frames should not crash and burn when re-inserting a fenced frame" + + "in a sandboxed iframe which doesn't support child fenced frames."); + +</script> + +</body> +</html>
\ No newline at end of file diff --git a/testing/web-platform/tests/fenced-frame/resize-lock-input.https.html b/testing/web-platform/tests/fenced-frame/resize-lock-input.https.html new file mode 100644 index 0000000000..261c9a737e --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/resize-lock-input.https.html @@ -0,0 +1,99 @@ +<!DOCTYPE html> + <title>Test FencedFrames Resize Lock</title> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + <script src="/resources/testdriver.js"></script> + <script src="/resources/testdriver-actions.js"></script> + <script src="/resources/testdriver-vendor.js"></script> + <script src="resources/utils.js"></script> + <script src="/common/utils.js"></script> + + <body> + + <script> + promise_test(async t => { + const resize_lock_inner_page_is_ready_key = token(); + const resize_lock_resize_is_done_key = token(); + const resize_lock_report_click_location_key = token(); + const resize_lock_report_click_location_key_after_resize = token(); + const resize_lock_report_click_location_key_after_resize_2 = token(); + + const frame = attachFencedFrame(generateURL( + "resources/resize-lock-inner-input.html", + [resize_lock_inner_page_is_ready_key, + resize_lock_resize_is_done_key, + resize_lock_report_click_location_key, + resize_lock_report_click_location_key_after_resize, + resize_lock_report_click_location_key_after_resize_2])); + + await nextValueFromServer(resize_lock_inner_page_is_ready_key); + + // Send an event to the origin of the frame. + await new test_driver.Actions() + .setContext(window) + .addPointer("finger1", "touch") + .pointerMove(10, 10, {origin: "viewport", sourceName: "finger1"}) + .pointerDown({sourceName: "finger1"}) + .pointerUp({sourceName: "finger1"}) + .send(); + + let result = + await nextValueFromServer(resize_lock_report_click_location_key); + assert_equals(result, "0,0", "fenced frame event before resize"); + + // The frame should be frozen at 300x150. Resize to create a 2x scale + // and a horizontal offset of 50px. + frame.width = "700"; + frame.height = "300"; + writeValueToServer(resize_lock_resize_is_done_key, + "outer_page_attempted_resize"); + + // The hit-test data is replicated in the browser and updated + // asynchronously. Wait to ensure the update has finished. + t.step_timeout(async () => { + // Now send an event to the same location. The event should be + // routed to the main frame. + let promise = new Promise((resolve, reject) => { + window.addEventListener('mousedown', (event) => { + let point = event.clientX + "," + event.clientY; + assert_equals(result, "10,10", "main frame event after resize"); + }); + }); + await new test_driver.Actions() + .setContext(window) + .addPointer("finger1", "touch") + .pointerMove(10, 10, {origin: "viewport", sourceName: "finger1"}) + .pointerDown({sourceName: "finger1"}) + .pointerUp({sourceName: "finger1"}) + .send(); + await promise; + + // Send an event to where the origin of the scaled frame should + // render. + await new test_driver.Actions() + .setContext(window) + .addPointer("finger1", "touch") + .pointerMove(60, 10, {origin: "viewport", sourceName: "finger1"}) + .pointerDown({sourceName: "finger1"}) + .pointerUp({sourceName: "finger1"}) + .send(); + result = await nextValueFromServer(resize_lock_report_click_location_key_after_resize); + assert_equals(result, "0,0", "fenced frame event before resize"); + + // Send an event where the bottom left of the scaled frame should + // render. + await new test_driver.Actions() + .setContext(window) + .addPointer("finger1", "touch") + .pointerMove(660, 310, {origin: "viewport", sourceName: "finger1"}) + .pointerDown({sourceName: "finger1"}) + .pointerUp({sourceName: "finger1"}) + .send(); + result = await nextValueFromServer(resize_lock_report_click_location_key_after_resize_2); + assert_equals(result, "300,150", "fenced frame event before resize"); + }, 1000); + }, "Test Resize Lock"); + </script> + + </body> +</html> diff --git a/testing/web-platform/tests/fenced-frame/resize-lock-zoom.https.html b/testing/web-platform/tests/fenced-frame/resize-lock-zoom.https.html new file mode 100644 index 0000000000..783f51d84e --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/resize-lock-zoom.https.html @@ -0,0 +1,39 @@ +<!DOCTYPE html> + <title>Test FencedFrames does not leak the CSS zoom property</title> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + <script src="resources/utils.js"></script> + <script src="/common/utils.js"></script> + + <body> + + <script> + promise_test(async () => { + const resize_lock_inner_page_is_ready_key = token(); + const resize_lock_resize_is_done_key = token(); + const resize_lock_report_inner_dimensions_key = token(); + + const frame = attachFencedFrame(generateURL( + "resources/resize-lock-inner.html", + [resize_lock_inner_page_is_ready_key, + resize_lock_resize_is_done_key, + resize_lock_report_inner_dimensions_key])); + + await nextValueFromServer(resize_lock_inner_page_is_ready_key); + + document.body.style.zoom = '2'; + + writeValueToServer(resize_lock_resize_is_done_key, + "outer_page_attempted_resize"); + + let result = + await nextValueFromServer(resize_lock_report_inner_dimensions_key); + assert_equals(result, "300x150", + "fenced frame dimensions should not be updated by " + + "parent page"); + + }, "Test Resize Lock"); + </script> + + </body> +</html> diff --git a/testing/web-platform/tests/fenced-frame/resize-lock.https.html b/testing/web-platform/tests/fenced-frame/resize-lock.https.html new file mode 100644 index 0000000000..b7c39f6f3a --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/resize-lock.https.html @@ -0,0 +1,96 @@ +<!DOCTYPE html> +<title>Test cases when fenced frame size shouldn't be restricted..</title> +<meta name="timeout" content="long"> +<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> + +<body> +<script> +async function runTest(api1, + expected_initial_size, + expected_size_after_resize, + api2, + expected_size_after_renavigation, + expected_size_after_reresize) { + const initial_width = 321; + const initial_height = 51; + + const resized_width = 729; + const resized_height = 91; + + const reresized_width = 971; + const reresized_height = 251; + + var frame = await attachFencedFrameContext({ + generator_api: api1, resolve_to_config: true, + attributes: [['width', initial_width], ['height', initial_height]]}); + + const assert_dimensions = + (expected_width, expected_height) => { + getComputedStyle(document.documentElement).width; // Force layout. + assert_equals(window.innerWidth, expected_width, "width"); + assert_equals(window.innerHeight, expected_height, "height"); + } + + // Check that the initial size of the fenced frame is what we expect. + await frame.execute(assert_dimensions, expected_initial_size); + + // Resize the frame, and check that the size is now what we expect. + frame.element.width = resized_width; + frame.element.height = resized_height; + await frame.execute(assert_dimensions, expected_size_after_resize); + + // Perform an embedder-initiated navigation, and check that the size is now + // what we expect (it should be based on the new context, rather than the old + // context). + frame = await replaceFrameContext(frame, {generator_api: api2, + resolve_to_config: true}); + await frame.execute(assert_dimensions, expected_size_after_renavigation); + + // Resize the newly navigated frame, and check the size. + frame.element.width = reresized_width; + frame.element.height = reresized_height; + await frame.execute(assert_dimensions, expected_size_after_reresize); +} + +promise_test(async () => { + return runTest('fledge', [320, 50], [320, 50], + 'fledge', [728, 90], [728, 90]); }, + "FLEDGE->FLEDGE"); +promise_test(async () => { + return runTest('sharedstorage', [321, 51], [729, 91], + 'sharedstorage', [729, 91], [971, 251]); }, + "sharedStorage->sharedStorage"); +promise_test(async () => { + return runTest('default', [321, 51], [729, 91], + 'default', [729, 91], [971, 251]); }, + "default->default"); +promise_test(async () => { + return runTest('default', [321, 51], [729, 91], + 'fledge', [728, 90], [728, 90]); }, + "default->FLEDGE"); +promise_test(async () => { + return runTest('default', [321, 51], [729, 91], + 'sharedStorage', [729, 91], [971, 251]); }, + "default->sharedStorage"); +promise_test(async () => { + return runTest('fledge', [320, 50], [320, 50], + 'default', [729, 91], [971, 251]); }, + "FLEDGE->default"); +promise_test(async () => { + return runTest('sharedstorage', [321, 51], [729, 91], + 'default', [729, 91], [971, 251]); }, + "sharedStorage->default"); +promise_test(async () => { + return runTest('sharedstorage', [321, 51], [729, 91], + 'fledge', [728, 90], [728, 90]); }, + "sharedStorage->FLEDGE"); +promise_test(async () => { + return runTest('fledge', [320, 50], [320, 50], + 'sharedstorage', [729, 91], [971, 251]); }, + "FLEDGE->sharedStorage"); +</script> +</body> diff --git a/testing/web-platform/tests/fenced-frame/resolve-to-config-promise.https.html b/testing/web-platform/tests/fenced-frame/resolve-to-config-promise.https.html new file mode 100644 index 0000000000..97df37ef2b --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/resolve-to-config-promise.https.html @@ -0,0 +1,80 @@ +<!DOCTYPE html> +<title>Test setting auction config's resolveToConfig to a promise</title> +<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> +<script src="resources/default-enabled-features-helper.js"></script> + +<body> +<script> +// To simulate the time it takes for a promise to resolve, we use a simple +// timeout that eventually resolves to a boolean. +function delayValue(value, timeout) { + return new Promise((resolve) => { + setTimeout(() => { + resolve(value); + }, timeout); + }); +} + +promise_test(async(t) => { + const key = token(); + + const urn = await generateURNFromFledge("resources/embeddee.html", [key], + [], delayValue(true, 500)); + assert_true(urn instanceof FencedFrameConfig); + const fencedframe = attachFencedFrame(urn); + + const page1_resp = await nextValueFromServer(key); + assert_equals(page1_resp, "PASS", + "The page should have loaded."); +}, 'resolveToConfig set to a promise that resolves to true'); + +promise_test(async(t) => { + const key = token(); + + const urn = await generateURNFromFledge("resources/embeddee.html", [key], + [], delayValue(false, 500)); + assert_false(urn instanceof FencedFrameConfig); + const fencedframe = attachFencedFrame(urn); + + const page1_resp = await nextValueFromServer(key); + assert_equals(page1_resp, "PASS", + "The page should have loaded."); +}, 'resolveToConfig set to a promise that resolves to false'); + +promise_test(async(t) => { + // This tests the case where the resolveToConfig promise resolves before + // the auction finishes + const key = token(); + + const urn = await generateURNFromFledge("resources/embeddee.html", [key], + [], delayValue(true, 0)); + assert_true(urn instanceof FencedFrameConfig); + const fencedframe = attachFencedFrame(urn); + + const page1_resp = await nextValueFromServer(key); + assert_equals(page1_resp, "PASS", + "The page should have loaded."); +}, 'resolveToConfig set to a promise that immediately resolves'); + +promise_test(async(t) => { + const key = token(); + + // This should still resolve, but resolve to a URN. + const urn = await generateURNFromFledge("resources/embeddee.html", [key], + [], delayValue("invalid", 0)); + assert_false(urn instanceof FencedFrameConfig); + const fencedframe = attachFencedFrame(urn); + + const page1_resp = await nextValueFromServer(key); + assert_equals(page1_resp, "PASS", + "The page should have loaded."); +}, 'resolveToConfig set to a promise that resolves to an invalid value'); + +</script> +</body> +</html> 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-store.py b/testing/web-platform/tests/fenced-frame/resources/automatic-beacon-store.py new file mode 100644 index 0000000000..f783c8530c --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/resources/automatic-beacon-store.py @@ -0,0 +1,25 @@ +"""Automatic beacon store server. + +- When a request body is not specified, serves a 200 response whose body + contains the stored value from the last automatic beacon. If the stored value + doesn't exist, serves a 200 response with an empty body. +- When a request body is specified, stores the data in the body and serves a 200 + response without body. +""" +# Use an arbitrary key since `request.server.stash.put` expects a valid UUID. +BEACON_KEY = "0c02dba4-f01e-11ed-a05b-0242ac120003" + +def main(request, response): + # Requests with a body imply they were sent as an automatic beacon for + # reserved.top_navigation. Note that this only stores the most recent beacon + # that was sent. + if request.body: + request.server.stash.put(BEACON_KEY, request.body) + return (200, [], b"") + + # Requests without a body imply they were sent as the request from + # nextAutomaticBeacon(). + data = request.server.stash.take(BEACON_KEY) + if not data and data != "": + return (200, [], b"<Not set>") + return (200, [], data) 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/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..821ebf69e3 --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/resources/default-enabled-features-helper.js @@ -0,0 +1,45 @@ +// 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_false( + document.featurePolicy.allowsFeature('shared-storage'), + "Shared storage should be disallowed 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."); + } + }, [generator_api]); +} diff --git a/testing/web-platform/tests/fenced-frame/resources/default-enabled-features-redirect.https.html b/testing/web-platform/tests/fenced-frame/resources/default-enabled-features-redirect.https.html new file mode 100644 index 0000000000..000b1eb6dd --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/resources/default-enabled-features-redirect.https.html @@ -0,0 +1,32 @@ +<!DOCTYPE html> +<script src="utils.js"></script> +<script src="/common/get-host-info.sub.js"></script> +<title>Fenced frame attribution reporting redirect test</title> + +<body> +<script> +// This helper function will redirect a fenced frame to a remote origin page +// while embedded in a frame that does not allow attribution reporting on +// remote origins. That redirect should fail to load because of the attribution +// reporting restriction. +const [key1, key2] = parseKeylist(); + +if (location.origin == get_host_info().ORIGIN) { + const result_val = + document.featurePolicy.allowsFeature('attribution-reporting') & + document.featurePolicy.allowsFeature('shared-storage'); + + writeValueToServer(key1, + document.featurePolicy.allowsFeature('attribution-reporting')); + + const next_url = getRemoteOriginURL(generateURL( + "attribution-reporting-redirect.https.html", [key1, key2])); + location.href = next_url; +} else { + // The redirect should have been unsuccessful, so we should not reach this + // point. + writeValueToServer(key2, "FAIL"); +} + +</script> +</body> diff --git a/testing/web-platform/tests/fenced-frame/resources/default-enabled-features-redirect.https.html.headers b/testing/web-platform/tests/fenced-frame/resources/default-enabled-features-redirect.https.html.headers new file mode 100644 index 0000000000..1b63235b7c --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/resources/default-enabled-features-redirect.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-with-size.js b/testing/web-platform/tests/fenced-frame/resources/fledge-bidding-logic-with-size.js new file mode 100644 index 0000000000..1a22bcec3d --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/resources/fledge-bidding-logic-with-size.js @@ -0,0 +1,37 @@ +// 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 do not deal with reporting results, so we leave +// `reportWin` empty. See `generateURNFromFledge` in "utils.js" to see how +// these files are used. + +function generateBid(interestGroup, auctionSignals, perBuyerSignals, + trustedBiddingSignals, browserSignals) { + 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': { url: ad.renderUrl, width: "100px", height: "50px" }, + 'allowComponentAuction': allowComponentAuction + }; + if (interestGroup.adComponents && interestGroup.adComponents.length > 0) + result.adComponents = interestGroup.adComponents.map((component) => { + return { + url: component.renderUrl, + width: "100px", + height: "50px" + }; + }); + return result; +} + +function reportWin( + auctionSignals, perBuyerSignals, sellerSignals, browserSignals) { + return; +} diff --git a/testing/web-platform/tests/fenced-frame/resources/fledge-bidding-logic-with-size.js.headers b/testing/web-platform/tests/fenced-frame/resources/fledge-bidding-logic-with-size.js.headers new file mode 100644 index 0000000000..776bf2987b --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/resources/fledge-bidding-logic-with-size.js.headers @@ -0,0 +1,2 @@ +Content-Type: Application/Javascript +X-Allow-FLEDGE: true
\ No newline at end of file diff --git a/testing/web-platform/tests/fenced-frame/resources/fledge-bidding-logic.js b/testing/web-platform/tests/fenced-frame/resources/fledge-bidding-logic.js new file mode 100644 index 0000000000..ab6685f184 --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/resources/fledge-bidding-logic.js @@ -0,0 +1,34 @@ +// 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 do not deal with reporting results, so we leave +// `reportWin` empty. See `generateURNFromFledge` in "utils.js" to see how +// these files are used. + +function generateBid(interestGroup, auctionSignals, perBuyerSignals, + trustedBiddingSignals, browserSignals) { + 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': ad.renderUrl, + 'allowComponentAuction': allowComponentAuction}; + if (interestGroup.adComponents && interestGroup.adComponents.length > 0) + result.adComponents = interestGroup.adComponents.map((component) => { + return component.renderUrl; + }); + return result; +} + +function reportWin( + auctionSignals, perBuyerSignals, sellerSignals, browserSignals) { + registerAdBeacon({ + 'reserved.top_navigation': + browserSignals.interestGroupOwner + + '/fenced-frame/resources/automatic-beacon-store.py' + }); + return; +} diff --git a/testing/web-platform/tests/fenced-frame/resources/fledge-bidding-logic.js.headers b/testing/web-platform/tests/fenced-frame/resources/fledge-bidding-logic.js.headers new file mode 100644 index 0000000000..776bf2987b --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/resources/fledge-bidding-logic.js.headers @@ -0,0 +1,2 @@ +Content-Type: Application/Javascript +X-Allow-FLEDGE: true
\ No newline at end of file diff --git a/testing/web-platform/tests/fenced-frame/resources/fledge-decision-logic.js b/testing/web-platform/tests/fenced-frame/resources/fledge-decision-logic.js new file mode 100644 index 0000000000..dc92f5a5e1 --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/resources/fledge-decision-logic.js @@ -0,0 +1,15 @@ +// 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 +// these files are used. + +function scoreAd( + adMetadata, bid, auctionConfig, trustedScoringSignals, browserSignals) { + return 2*bid; +} + +function reportResult(auctionConfig, browserSignals) { + return; +} diff --git a/testing/web-platform/tests/fenced-frame/resources/fledge-decision-logic.js.headers b/testing/web-platform/tests/fenced-frame/resources/fledge-decision-logic.js.headers new file mode 100644 index 0000000000..776bf2987b --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/resources/fledge-decision-logic.js.headers @@ -0,0 +1,2 @@ +Content-Type: Application/Javascript +X-Allow-FLEDGE: true
\ No newline at end of file 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..a83ba93aa6 --- /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, configs.length > 0 ? configs[0].url : "N/a"]; +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..6178ea78da --- /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 unloadPromise = new Promise(resolve => { + win.onunload = resolve; + }); + + await win.runTest(test_type, ancestor_type); + win.close(); + await unloadPromise; +} 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/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-input.html b/testing/web-platform/tests/fenced-frame/resources/resize-lock-inner-input.html new file mode 100644 index 0000000000..cb17789b45 --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/resources/resize-lock-inner-input.html @@ -0,0 +1,43 @@ +<!DOCTYPE html> + <script src="utils.js"></script> + <title>Fenced frame content to report any changes in inner dimensions</title> + + <style> + body { + width: 500px; + height: 500px; + background: grey; + } + </style> + <body> + <script> + let eventCount = 0; + async function init() { + const [resize_lock_inner_page_is_ready_key, + resize_lock_resize_is_done_key, + resize_lock_report_click_location_key, + resize_lock_report_click_location_key_after_resize, + resize_lock_report_click_location_key_after_resize_2] = parseKeylist(); + + window.addEventListener('mousedown', async (event) => { + eventCount++; + let point = event.clientX + "," + event.clientY; + if (eventCount == 1) { + writeValueToServer(resize_lock_report_click_location_key, point); + await nextValueFromServer(resize_lock_resize_is_done_key) + } else if (eventCount == 2) { + writeValueToServer( + resize_lock_report_click_location_key_after_resize, point); + } else { + writeValueToServer( + resize_lock_report_click_location_key_after_resize_2, point); + } + }); + + writeValueToServer(resize_lock_inner_page_is_ready_key, "ready"); + } + + init(); + </script> + </body> +</html> diff --git a/testing/web-platform/tests/fenced-frame/resources/resize-lock-inner-input.html.headers b/testing/web-platform/tests/fenced-frame/resources/resize-lock-inner-input.html.headers new file mode 100644 index 0000000000..1b63235b7c --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/resources/resize-lock-inner-input.html.headers @@ -0,0 +1 @@ +Supports-Loading-Mode: fenced-frame 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/utils.js b/testing/web-platform/tests/fenced-frame/resources/utils.js new file mode 100644 index 0000000000..4638f37cbb --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/resources/utils.js @@ -0,0 +1,501 @@ +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'; +const FLEDGE_BIDDING_URL = '/fenced-frame/resources/fledge-bidding-logic.js'; +const FLEDGE_BIDDING_WITH_SIZE_URL = '/fenced-frame/resources/fledge-bidding-logic-with-size.js'; +const FLEDGE_DECISION_URL = '/fenced-frame/resources/fledge-decision-logic.js'; + +// 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}], { + 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) { + 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 } + }); + + const interestGroup = ad_with_size ? + { + name: 'testAd1', + owner: location.origin, + biddingLogicUrl: new URL(FLEDGE_BIDDING_WITH_SIZE_URL, location.origin), + ads: [{ renderUrl: href, sizeGroup: "group1", bid: 1 }], + userBiddingSignals: { biddingToken: bidding_token }, + trustedBiddingSignalsKeys: ['key1'], + adComponents: ad_components_list, + adSizes: { "size1": { width: "100px", height: "50px" } }, + sizeGroups: { "group1": ["size1"] } + } : + { + 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, + }; + + // 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) { + 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. +async function generateURNFromFledge(href, keylist, nested_urls=[], resolve_to_config = false, ad_with_size = false, requested_size = null) { + const full_url = generateURL(href, keylist); + return generateURNFromFledgeRawURL(full_url, nested_urls, resolve_to_config, ad_with_size, requested_size); +} + +// 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, object_constructor, html, headers, origin) { + const [uuid, url] = generateRemoteContextURL(headers, origin); + const id = await (generator_api == 'fledge' ? generateURNFromFledge(url, [], [], resolve_to_config, ad_with_size, requested_size) : 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, frame_constructor, html, headers, origin) { + generator_api = generator_api.toLowerCase(); + if (generator_api == 'fledge' || generator_api == 'sharedstorage') { + return attachOpaqueContext(generator_api, resolve_to_config, ad_with_size, requested_size, frame_constructor, html, headers, origin); + } else { + return attachContext(frame_constructor, html, headers, origin); + } +} + +function attachFrameContext(element_name, generator_api, resolve_to_config, ad_with_size, requested_size, html, headers, attributes, origin) { + 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, frame_constructor, html, headers, origin); +} + +function replaceFrameContext(frame_proxy, {generator_api="", resolve_to_config=false, ad_with_size=false, requested_size=null, 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, 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. +// - 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, html = "", headers=[], attributes=[], origin=""}={}) { + return attachFrameContext('fencedframe', generator_api, resolve_to_config, ad_with_size, requested_size, html, headers, attributes, origin); +} + +// Attach an iframe that waits for scripts to execute. +// See `attachFencedFrameContext` for more details. +function attachIFrameContext({generator_api="", html="", headers=[], attributes=[], origin=""}={}) { + return attachFrameContext('iframe', generator_api, resolve_to_config=false, ad_with_size=false, requested_size=null, html, headers, attributes, origin); +} + +// 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); +} + +// 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; + } +} + +// Reads the data from the latest automatic beacon sent to the server. +async function readAutomaticBeaconDataFromServer() { + const serverUrl = `${BEACON_URL}`; + 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 nextAutomaticBeacon() { + while (true) { + // Fetches the test result from the server. + const { status, value } = await readAutomaticBeaconDataFromServer(); + 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 diff --git a/testing/web-platform/tests/fenced-frame/sandbox-attribute.https.html b/testing/web-platform/tests/fenced-frame/sandbox-attribute.https.html new file mode 100644 index 0000000000..1458145e43 --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/sandbox-attribute.https.html @@ -0,0 +1,63 @@ +<!DOCTYPE html> +<title>Test fenced frame sandbox attribute.</title> +<meta name="timeout" content="long"> +<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> + +<body> +<script> + +async function runTest(t, sandbox_flags, success) { + const frame = await attachFencedFrameContext({ + generator_api: 'fledge', resolve_to_config: true, + attributes: [['sandbox', sandbox_flags]]}); + + assert_equals(frame.element.sandbox.value, sandbox_flags); + if (sandbox_flags) { + assert_equals(frame.element.sandbox.length, sandbox_flags.split(' ').length); + } else { + assert_equals(frame.element.sandbox.length, 0); + } + + const result = await Promise.any([ + frame.execute(() => { return 'success';}), + new Promise(resolve => t.step_timeout(() => resolve('failure'), 2000))]); + if (success) { + assert_equals(result, 'success'); + } else { + assert_equals(result, 'failure'); + } +} + +// We omit test cases that lack the sandbox attribute, because that's covered +// by every other test that doesn't explicitly use the `sandbox` attribute. + +promise_test(async t => { + return runTest(t, '', false); +}, 'Navigation fails with no allowed features'); + +promise_test(async t => { + return runTest(t, 'allow-same-origin allow-forms allow-scripts allow-popups allow-popups-to-escape-sandbox allow-top-navigation-by-user-activation', true); +}, 'Navigation succeeds with exactly the required unsandboxed features'); + +promise_test(async t => { + return runTest(t, 'allow-same-origin allow-forms allow-scripts allow-popups allow-popups-to-escape-sandbox allow-top-navigation-by-user-activation allow-pointer-lock', true); +}, 'Navigation succeeds with extra unsandboxed features'); + +promise_test(async t => { + return runTest(t, 'allow-same-origin allow-forms allow-scripts allow-popups allow-popups-to-escape-sandbox', false); +}, 'Navigation fails with too few unsandboxed features'); + +promise_test(async t => { + return runTest(t, 'foo bar baz', false); +}, 'Navigation fails with malformed sandbox flags'); + +promise_test(async t => { + return runTest(t, 'allow-same-origin allow-forms allow-scripts allow-popups allow-popups-to-escape-sandbox allow-top-navigation-by-user-activation allow-foobarbaz', true); +}, 'Navigation fails with the required unsandboxed features, plus some malformed ones'); + +</script> +</body> diff --git a/testing/web-platform/tests/fenced-frame/sandbox-mandatory-flags.https.html b/testing/web-platform/tests/fenced-frame/sandbox-mandatory-flags.https.html new file mode 100644 index 0000000000..57a5bc49cf --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/sandbox-mandatory-flags.https.html @@ -0,0 +1,136 @@ +<!DOCTYPE html> +<meta name=timeout content=long> +<title>Test of sandbox mandatory flags</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/utils.js"></script> +<script src="/common/utils.js"></script> +<script src="/common/dispatcher/dispatcher.js"></script> + +<body> + +<script> + +const mandatory_flags = [ + 'allow-same-origin', + 'allow-forms', + 'allow-scripts', + 'allow-popups', + 'allow-popups-to-escape-sandbox', + 'allow-top-navigation-by-user-activation']; + +promise_test(async t => { + const key = token(); + const value = 'fenced frame loaded'; + const iframe = document.createElement('iframe'); + iframe.src = + 'resources/sandbox-mandatory-flags-iframe.sub.html?key=' + key + + '&value=' + value; + mandatory_flags.forEach(flag => { + iframe.sandbox.add(flag); + }); + + document.body.appendChild(iframe); + t.add_cleanup(() => { + iframe.remove(); + }); + const result = await nextValueFromServer(key); + assert_equals(result, value, 'The fenced frame must be loaded.'); +}, 'Sandboxed Iframe with mandatory flags can load a fenced frame.'); + +promise_test(async t => { + const key = token(); + // Try to load a fenced frame in a sandboxed iframe like this: + // <iframe sandbox="|mandatory_flags| without |missing_flag|" + // src="sandbox-mandatory-flags-iframe.sub.html"> + // <fencedframe src="sandbox-mandatory-flags-inner.sub.html"> + // <img src="key-value-store.py?key=|key|&value=|value|"> + // <fencedframe> + // </iframe> + // But this should fail because the sandboxed iframe is loaded without + // |missing_flag|. + for (let missing_flag of mandatory_flags) { + const value = + 'a fenced frame was loaded in a sandboxed iframe without ' + + missing_flag + '.'; + const iframe = document.createElement('iframe'); + iframe.src = + 'resources/sandbox-mandatory-flags-iframe.sub.html?key=' + key + + '&value=' + value; + mandatory_flags.forEach(flag => { + if (flag != missing_flag) { + iframe.sandbox.add(flag); + } + }); + document.body.appendChild(iframe); + t.add_cleanup(() => { + iframe.remove(); + }); + } + t.step_timeout(() => t.done(), 3000); + let server_value = await nextValueFromServer(key); + assert_unreached('fenced frame should not be loaded, but ' + server_value); +}, 'Sandboxed Iframe without one of mandatory flag must fail to load a fenced' + + ' frame.'); + +promise_test(async t => { + const key = token(); + // Try to load a fenced frame in a nested sandboxed iframe like this: + // <iframe sandbox="|mandatory_flags| without |missing_flag|" + // src="sandbox-mandatory-flags-looser-restriction.sub.html"> + // <iframe sandbox="|mandatory_flags|" + // src="sandbox-mandatory-flags-iframe.sub.html"> + // <fencedframe src="resources/sandbox-mandatory-flags-inner.sub.html"> + // <img src="key-value-store.py?key=|key|&value=|value|"> + // <fencedframe> + // </iframe> + // </iframe> + // But this should fail because the nested iframe is loaded sandboxed + // without |missing_flag|. + for (let missing_flag of mandatory_flags) { + const value = + 'a fenced frame was loaded in a nested sandboxed iframe without ' + + missing_flag + '.'; + const iframe = document.createElement('iframe'); + iframe.src = + 'resources/sandbox-mandatory-flags-looser-restriction.sub.html?key=' + + key + '&value=' + value; + mandatory_flags.forEach(flag => { + if (flag != missing_flag) { + iframe.sandbox.add(flag); + } + }); + document.body.appendChild(iframe); + t.add_cleanup(() => { + iframe.remove(); + }); + } + t.step_timeout(() => t.done(), 3000); + let server_value = await nextValueFromServer(key); + assert_unreached('fenced frame should not be loaded, but ' + server_value); +}, 'Nested sandboxed iframe without one of mandatory flag must fail to load a' + + 'fenced frame even when the inner nested sandboxed iframe has all ' + + 'mandatory allow- flags.'); + +promise_test(async t => { + const key = token(); + // allow-scripts is needed to run iframe.execute, so we will test every other + // sandbox flag + for (let missing_flag of + mandatory_flags.filter(word => word != "allow-scripts")) { + const value = + 'canLoadOpaqueURL returned true even with flag ' + + missing_flag + ' not set.'; + const flags_to_add = mandatory_flags + .filter(word => word != missing_flag) + .join(" "); + const iframe = attachIFrameContext( + {attributes: [["sandbox", flags_to_add]]}); + await iframe.execute(async (t) => { + assert_false(navigator.canLoadAdAuctionFencedFrame()); + }); + } +}, 'navigator.canLoadAdAuctionFencedFrame considers mandatory sandbox flags'); +</script> + +</body> diff --git a/testing/web-platform/tests/fenced-frame/sandboxed-features-alert.https.html b/testing/web-platform/tests/fenced-frame/sandboxed-features-alert.https.html new file mode 100644 index 0000000000..6d3f83208d --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/sandboxed-features-alert.https.html @@ -0,0 +1,14 @@ +<!DOCTYPE html> +<title>Test of sandboxed features - alert</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/common/utils.js"></script> +<script src="resources/utils.js"></script> +<script src="resources/sandboxed-features.js"></script> + +<body> +<script> +run_sanboxed_feature_test('test_alert', + 'The fenced frame must fail to open an alert dialog.'); +</script> +</body> diff --git a/testing/web-platform/tests/fenced-frame/sandboxed-features-confirm.https.html b/testing/web-platform/tests/fenced-frame/sandboxed-features-confirm.https.html new file mode 100644 index 0000000000..bb55f15ff0 --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/sandboxed-features-confirm.https.html @@ -0,0 +1,15 @@ +<!DOCTYPE html> +<title>Test of sandbox features - confirm</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/common/utils.js"></script> +<script src="resources/utils.js"></script> +<script src="resources/sandboxed-features.js"></script> + +<body> +<script> +run_sanboxed_feature_test('test_confirm', + 'The fenced frame must fail to open a confirm dialog.'); +</script> + +</body> diff --git a/testing/web-platform/tests/fenced-frame/sandboxed-features-documentdomain.https.html b/testing/web-platform/tests/fenced-frame/sandboxed-features-documentdomain.https.html new file mode 100644 index 0000000000..e00ab23980 --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/sandboxed-features-documentdomain.https.html @@ -0,0 +1,14 @@ +<!DOCTYPE html> +<title>Test of sandboxed features - document.domain</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/common/utils.js"></script> +<script src="resources/utils.js"></script> +<script src="resources/sandboxed-features.js"></script> + +<body> +<script> +run_sanboxed_feature_test('test_document_domain', + 'The fenced frame must fail to change Document.domain.'); +</script> +</body> diff --git a/testing/web-platform/tests/fenced-frame/sandboxed-features-pointerlock.https.html b/testing/web-platform/tests/fenced-frame/sandboxed-features-pointerlock.https.html new file mode 100644 index 0000000000..af598798b2 --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/sandboxed-features-pointerlock.https.html @@ -0,0 +1,14 @@ +<!DOCTYPE html> +<title>Test of sandboxed features - pointer lock</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/common/utils.js"></script> +<script src="resources/utils.js"></script> +<script src="resources/sandboxed-features.js"></script> + +<body> +<script> +run_sanboxed_feature_test('test_pointer_lock', + 'The fenced frame must fail to call requestPointerLock().'); +</script> +</body> diff --git a/testing/web-platform/tests/fenced-frame/sandboxed-features-presentation-request.https.html b/testing/web-platform/tests/fenced-frame/sandboxed-features-presentation-request.https.html new file mode 100644 index 0000000000..7f9b1d7bd6 --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/sandboxed-features-presentation-request.https.html @@ -0,0 +1,14 @@ +<!DOCTYPE html> +<title>Test of sandboxed features - PresentationRequest</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/common/utils.js"></script> +<script src="resources/utils.js"></script> +<script src="resources/sandboxed-features.js"></script> + +<body> +<script> +run_sanboxed_feature_test('test_presentation_request', + 'The fenced frame must fail to create a PresentationRequest.'); +</script> +</body> diff --git a/testing/web-platform/tests/fenced-frame/sandboxed-features-printdialog.https.html b/testing/web-platform/tests/fenced-frame/sandboxed-features-printdialog.https.html new file mode 100644 index 0000000000..b03f7a22d8 --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/sandboxed-features-printdialog.https.html @@ -0,0 +1,14 @@ +<!DOCTYPE html> +<title>Test of sandboxed features - print</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/common/utils.js"></script> +<script src="resources/utils.js"></script> +<script src="resources/sandboxed-features.js"></script> + +<body> +<script> +run_sanboxed_feature_test('test_print', + 'The fenced frame must fail to print the page.'); +</script> +</body> diff --git a/testing/web-platform/tests/fenced-frame/sandboxed-features-prompt.https.html b/testing/web-platform/tests/fenced-frame/sandboxed-features-prompt.https.html new file mode 100644 index 0000000000..1207f4a765 --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/sandboxed-features-prompt.https.html @@ -0,0 +1,14 @@ +<!DOCTYPE html> +<title>Test of sandboxed features - prompt</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/common/utils.js"></script> +<script src="resources/utils.js"></script> +<script src="resources/sandboxed-features.js"></script> + +<body> +<script> +run_sanboxed_feature_test('test_prompt', + 'The fenced frame must fail to open a prompt.'); +</script> +</body> diff --git a/testing/web-platform/tests/fenced-frame/sandboxed-features-screen-orientation-lock.https.html b/testing/web-platform/tests/fenced-frame/sandboxed-features-screen-orientation-lock.https.html new file mode 100644 index 0000000000..4e80d92e1d --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/sandboxed-features-screen-orientation-lock.https.html @@ -0,0 +1,14 @@ +<!DOCTYPE html> +<title>Test of sandboxed features - screen.orientation.lock</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/common/utils.js"></script> +<script src="resources/utils.js"></script> +<script src="resources/sandboxed-features.js"></script> + +<body> +<script> +run_sanboxed_feature_test('test_screen_orientation_lock', + 'The fenced frame must fail to call screen.orientation.lock().'); +</script> +</body> diff --git a/testing/web-platform/tests/fenced-frame/script-focus.https.html b/testing/web-platform/tests/fenced-frame/script-focus.https.html new file mode 100644 index 0000000000..4b5fe758b4 --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/script-focus.https.html @@ -0,0 +1,205 @@ +<!DOCTYPE html> +<title>Test Script-Based Focus for Fenced Frames</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.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/utils.js"></script> +<script src="resources/utils.js"></script> +<script src="/common/dispatcher/dispatcher.js"></script> + +<script src="/common/get-host-info.sub.js"></script> + +<body> +<script> +async function AttemptButtonFocus(frame, expecting_focus) { + await frame.execute(async (expecting_focus) => { + const button = document.createElement("button"); + document.body.append(button); + button.focus(); + assert_equals(document.activeElement == button, expecting_focus, + "Button's focus should match expected focus"); + }, [expecting_focus]); +} + +async function ClickOn(element, actions) { + // Wait until the window size is initialized. + while (window.innerWidth == 0) { + await new Promise(resolve => requestAnimationFrame(resolve)); + } + await actions.pointerMove(0, 0, {origin: element}) + .pointerDown() + .pointerUp() + .send(); +} + +async function SetupTest(click=true) { + // Clean up any leftover frames from prior tests. + document.querySelectorAll("fencedframe").forEach(e => { + e.remove(); + }) + + const actions = new test_driver.Actions(); + + const frame = attachFencedFrameContext(); + const fencedframe_element = frame.element; + + if (click) + await ClickOn(document.body, actions); + + return [actions, frame, fencedframe_element]; +} + +promise_test(async () => { + const [actions, ff1, ff1_element] = await SetupTest(false); + + await ClickOn(ff1_element, actions); + await AttemptButtonFocus(ff1, true); + + const button = document.createElement("button"); + document.body.append(button); + button.focus(); + assert_true(document.activeElement == button, + "The button should have focus"); + assert_false(navigator.userActivation.isActive, + "Window should not have user activation"); +}, "An embedder can focus out of a fenced frame"); + +promise_test(async () => { + const [actions, frame, fencedframe_element] = await SetupTest(); + + await AttemptButtonFocus(frame, false); + await ClickOn(fencedframe_element, actions); + await AttemptButtonFocus(frame, true); +}, "Fenced frames can't pull script focus until getting user activation"); + +promise_test(async t => { + const [actions, frame, fencedframe_element] = await SetupTest(); + + await ClickOn(fencedframe_element, actions); + await ClickOn(document.body, actions); + + await AttemptButtonFocus(frame, true); + + // Give the browser time to receive the focus event before attempting + // another focus. + await t.step_timeout(async () => {await AttemptButtonFocus(frame, true);}, 20); +}, "Focused fenced frames can move programmatic focus within frame"); + +promise_test(async () => { + const [actions, frame, fencedframe_element] = await SetupTest(); + + await ClickOn(fencedframe_element, actions); + await ClickOn(document.body, actions); + + // This will pull focus across a frame boundary and consume user activation. + await AttemptButtonFocus(frame, true); + + await ClickOn(document.body, actions); + await AttemptButtonFocus(frame, false); +}, "Script focus into a fenced frame consumes user activation"); + +promise_test(async () => { + const [actions, ff1, ff1_element] = await SetupTest(); + + const ff2 = attachFencedFrameContext(); + const ff2_element = ff2.element; + + await ClickOn(ff1_element, actions); + + await AttemptButtonFocus(ff1, true); + await AttemptButtonFocus(ff2, false); +}, "Another fenced frame cannot pull focus out of a focused fenced frame"); + +promise_test(async () => { + const [actions, ff1, ff1_element] = await SetupTest(); + + await ClickOn(ff1_element, actions); + await AttemptButtonFocus(ff1, true); + + await ff1.execute(async () => { + const ff2 = attachFencedFrameContext(); + + await ff2.execute(async () => { + const button = document.createElement("button"); + document.body.append(button); + button.focus(); + assert_false(document.activeElement == button, + "The button should not have focus"); + assert_false(navigator.userActivation.isActive, + "The fenced frame should not have user activation"); + }); + }); +}, "A fenced frame nested in another fenced frame cannot pull focus"); + +promise_test(async () => { + const [actions, ff1, ff1_element] = await SetupTest(); + + await ClickOn(document.body, actions); + + const button = document.createElement("button"); + document.body.append(button); + button.focus(); + assert_equals(document.activeElement, button, + "The button in the main page should have focus."); + + await ff1.execute(async () => { + assert_false(navigator.userActivation.isActive, + "The fenced frame should not have user activation."); + window.focus(); + }); + + assert_equals(document.activeElement, button, + "The button in the main page should still have focus."); +}, "A fenced frame cannot pull window.focus() without user activation"); + +promise_test(async () => { + const [actions, ff1, ff1_element] = await SetupTest(); + + await ClickOn(ff1_element, actions); + await ClickOn(document.body, actions); + + const button = document.createElement("button"); + document.body.append(button); + button.focus(); + assert_equals(document.activeElement, button, + "The button should have focus."); + + await ff1.execute(async () => { + assert_true(navigator.userActivation.isActive, + "The fenced frame should have user activation."); + window.focus(); + assert_false(navigator.userActivation.isActive, + "The fenced frame's user activation should be consumed by the focus"); + }); + + assert_equals(document.activeElement, document.body, + "The main page's focus should be pulled away from the button."); +}, "A fenced frame can pull window.focus() after user activation"); + +promise_test(async () => { + var actions = new test_driver.Actions(); + + const frame = attachIFrameContext( + {origin: get_host_info().HTTPS_REMOTE_ORIGIN}); + const iframe_element = + document.body.getElementsByTagName('iframe')[0]; + + await frame.execute(async () => { + const button = document.createElement("button"); + document.body.append(button); + button.focus(); + assert_equals(document.activeElement, button, + "The button in the iframe should have focus."); + }, [true]); + + const button = document.createElement("button"); + document.body.append(button); + button.focus(); + assert_equals(document.activeElement, button, + "The button in the main page should have focus."); +}, "An cross-origin iframe can pull focus back and forth without activation"); + +</script> +</body> diff --git a/testing/web-platform/tests/fenced-frame/selecturl-flexible-size.https.html b/testing/web-platform/tests/fenced-frame/selecturl-flexible-size.https.html new file mode 100644 index 0000000000..6b1a07e73a --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/selecturl-flexible-size.https.html @@ -0,0 +1,56 @@ +<!DOCTYPE html> +<title>Test frame size behavior in selectURL fenced frames.</title> +<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> + +<body> +<script> +async function runTest(original_width, original_height, new_width, new_height) { + // Attach a selectURL fenced frame whose outer container has dimensions + // `original_width` by `original_height`. + const frame = await attachFencedFrameContext({ + generator_api: "sharedstorage", resolve_to_config: true, + attributes: [["width", original_width], ["height", original_height]]}); + + const assert_dimensions = + (label, original_width, original_height, + expected_width, expected_height) => { + assert_equals(getComputedStyle(document.documentElement).width, + expected_width+"px", + label + " the computed width (originally " + original_width + + ") should be " + expected_width); + assert_equals(window.innerWidth, expected_width, + label + " the innerWidth (originally " + original_width + + ") should be " + expected_width); + assert_equals(window.innerHeight, expected_height, + label + " the innerHeight (originally " + original_height + + ") should be " + expected_height); + } + + // Assert that the fenced frame sees the original dimensions. + await frame.execute(assert_dimensions, ["After navigation", + original_width, original_height, original_width, original_height]); + + // Assert that the embedder sees the fenced frame's original dimensions. + assert_equals(frame.width, original_width.toString(), + "The outer container width is the requested width."); + assert_equals(frame.height, original_height.toString(), + "The outer container height is the requested height."); + + // Resize the fenced frame's outer container. + frame.width = new_width; + frame.height = new_height; + + // Observe that the selectURL fenced frame sees the new size. + await frame.execute(assert_dimensions, ["After resizing", + original_width, original_height, new_width, new_height]); +} + +// Exact size cases. +promise_test(async () => { return runTest(299, 72, 100, 101); }, '299x72->100x101'); + +</script> +</body> diff --git a/testing/web-platform/tests/fenced-frame/self-urn-navigation.https.html b/testing/web-platform/tests/fenced-frame/self-urn-navigation.https.html new file mode 100644 index 0000000000..4b1e989e8a --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/self-urn-navigation.https.html @@ -0,0 +1,37 @@ +<!DOCTYPE html> +<title>Test that fenced frame-initiated self urn navigations fail.</title> +<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> + +<body> +<script> +function getTimeoutPromise(t) { + return new Promise(resolve => + t.step_timeout(() => resolve("NOT LOADED"), 2000)); +} + +// A fenced frame root should not be able to navigate itself to a urn:uuid. +promise_test(async t => { + const frame = attachFencedFrameContext(); + urn = await runSelectURL(frame.src); + + // Send the urn:uuid to the fenced frame over the network, and attempt to + // self-"refresh" to that urn. + await frame.execute(async (urn) => { + window.executor.suspend(() => { + location.href = urn; + }); + }, [urn]); + + // The navigation should fail as intended. + const urn_load_success_promise = frame.execute(() => {}); + const urn_load_failure_promise = getTimeoutPromise(t); + const result = + await Promise.any([urn_load_success_promise, urn_load_failure_promise]); + assert_equals(result, "NOT LOADED"); +}, 'fenced frame-initiated self urn navigation'); +</script> +</body> diff --git a/testing/web-platform/tests/fenced-frame/serviceWorker-dedicated-worker.https.html b/testing/web-platform/tests/fenced-frame/serviceWorker-dedicated-worker.https.html new file mode 100644 index 0000000000..92533e3873 --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/serviceWorker-dedicated-worker.https.html @@ -0,0 +1,71 @@ +<!DOCTYPE html> +<title>Service Worker: Check if dedicated workers are controlled</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/utils.js"></script> +<script src="/common/utils.js"></script> + +<body> + <script> + const frameUrl = './resources/serviceWorker-dedicated-worker-inner.html'; + const unregisterAllSW = async () => { + const regs = await navigator.serviceWorker.getRegistrations(); + return Promise.all(regs.map(reg => reg.unregister())); + }; + + promise_test(async t => { + t.add_cleanup(unregisterAllSW); + const key = token(); + + // Inside the fenced frame, the service worker is registered and fetch + // request is triggered from the dedicated worker to the url that is + // handled in the service worker. + const url = `${frameUrl}?useServiceWorkerInFencedFrame=true`; + attachFencedFrame(generateURL(url, [key])); + const result = await nextValueFromServer(key); + assert_equals(result, "OK"); + }, "Fenced frame's service workers can control fenced frame's dedicated workers"); + + promise_test(async t => { + t.add_cleanup(unregisterAllSW); + const key = token(); + + // Set a service worker in the fenced frame. Inside the fenced frame, a + // dedicated worker is created and triggers a fetch request. But we don't + // use the fetch request result in this test. This test will check if the + // dedicated worker in the parent frame is controlled by the SW in FF. + const url = `${frameUrl}?useServiceWorkerInFencedFrame=true`; + attachFencedFrame(generateURL(url, [key])); + await nextValueFromServer(key); + + const checkIfWorkerIsControlled = async () => { + const dedicated_worker = new Worker('resources/serviceWorker-dedicated-worker.js'); + return new Promise((resolve, reject) => { + dedicated_worker.addEventListener('message', e => { + resolve(e.data) + }); + dedicated_worker.postMessage('fetch'); + }); + } + + const result = await checkIfWorkerIsControlled() + assert_equals(result, "Not Found"); + }, "Fenced frame's service workers can not control the dedicated workers in the parent frame"); + + promise_test(async t => { + t.add_cleanup(unregisterAllSW); + const key = token(); + + // Register a service worker in the parent frame. + await navigator.serviceWorker.register('resources/serviceWorker-dedicated-worker-sw.js', { scope: '/' }); + await navigator.serviceWorker.ready; + + // Inside the fenced frame, fetch request to unexisting URL is triggered + // from the dedicated worker. + attachFencedFrame(generateURL(frameUrl, [key])); + + const result = await nextValueFromServer(key); + assert_equals(result, "Not Found"); + }, "Service workers in the parent frame of fenced frames can not control dedicated workers in fenced frames"); + </script> +</body> diff --git a/testing/web-platform/tests/fenced-frame/serviceWorker-frameType.https.html b/testing/web-platform/tests/fenced-frame/serviceWorker-frameType.https.html new file mode 100644 index 0000000000..ac0bc07f3d --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/serviceWorker-frameType.https.html @@ -0,0 +1,33 @@ +<!DOCTYPE html> +<title>Service Worker: Clients.matchAll with includeUncontrolled</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/utils.js"></script> +<script src="/common/utils.js"></script> +<body> +<script> + +promise_test(async t => { + const frame_type_key = token(); + const frame_type_ack_key = token(); + + attachFencedFrame(generateURL('resources/serviceWorker-frameType-inner.html', + [frame_type_key, frame_type_ack_key])); + + const frame_type_result = await nextValueFromServer(frame_type_key); + assert_equals(frame_type_result, "top-level", + "The service worker for the top-level fenced frame has the " + + "right value for `serviceWorker.frameType`"); + + // Write an ACK, so that the fenced frame knows it can send message over the + // `serviceWorker.frameType` channel again. + writeValueToServer(frame_type_ack_key, "ACK"); + + const nested_frame_type_result = await nextValueFromServer(frame_type_key); + assert_equals(nested_frame_type_result, "nested", + "The service worker for the iframe inside the top-level " + + "fenced frame has the right value for `serviceWorker.frameType`"); +}, 'serviceWorker.frameType'); + +</script> +</body> diff --git a/testing/web-platform/tests/fenced-frame/serviceWorker-push.https.html b/testing/web-platform/tests/fenced-frame/serviceWorker-push.https.html new file mode 100644 index 0000000000..cb460d161e --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/serviceWorker-push.https.html @@ -0,0 +1,62 @@ +<!doctype html> +<html> +<head> +<title>Service Worker: Push Messaging Test</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="/common/utils.js"></script> +<script src="/common/dispatcher/dispatcher.js"></script> +<script src="resources/utils.js"></script> +</head> +<body> +<script> + promise_test(async () => { + const frame = attachFencedFrameContext(); + try { + await frame.execute(async () => { + await navigator.serviceWorker.register( + 'empty-worker.js', { scope: location.href }); + const registration= await navigator.serviceWorker.ready; + return await registration.pushManager.subscribe({ + userVisibleOnly: true + }); + }); + assert_unreached('subscribe() executed without error; want error'); + } catch(e) { + assert_equals(e.message, + "Failed to execute 'subscribe' on 'PushManager': subscribe() is not " + + "allowed in fenced frames."); + } + }, 'subscribe() should fail inside a fenced frame'); + + promise_test(async () => { + const frame = attachFencedFrameContext(); + const message = await frame.execute(async () => { + const getController = () => { + if (navigator.serviceWorker.controller) { + return navigator.serviceWorker.controller; + } + return new Promise(resolve => { + navigator.serviceWorker.addEventListener('controllerchange', () => { + resolve(navigator.serviceWorker.controller); + }); + }); + }; + await navigator.serviceWorker.register( + 'serviceWorker-push-sw.js', { scope: location.href }); + return new Promise(async resolve => { + const ctrl = await getController(); + ctrl.postMessage('subscribe'); + navigator.serviceWorker.onmessage = e => { + resolve(e.data); + } + }); + }); + assert_equals(message, "Failed to execute 'subscribe' on " + + "'PushManager': subscribe() is not allowed in fenced frames."); + }, 'subscribe() should fail from the service worker inside a fenced frame'); +</script> +</body> +</html> diff --git a/testing/web-platform/tests/fenced-frame/set-automatic-beacon.https.html b/testing/web-platform/tests/fenced-frame/set-automatic-beacon.https.html new file mode 100644 index 0000000000..f231018b85 --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/set-automatic-beacon.https.html @@ -0,0 +1,54 @@ +<!DOCTYPE html> +<title>Test window.fence.setReportEventDataForAutomaticBeacons</title> +<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> + +<body> +<script> +promise_test(async(t) => { + const fencedframe = await attachFencedFrameContext( + {generator_api: 'fledge'}); + await fencedframe.execute(() => { + let event = { + eventType: "reserved.top_navigation", + eventData: "a".repeat(64000), + destination: ["buyer"], + } + window.fence.setReportEventDataForAutomaticBeacons(event); + }); +}, 'setReportEventDataForAutomaticBeacons works at the size limit'); + +promise_test(async(t) => { + const fencedframe = await attachFencedFrameContext( + {generator_api: 'fledge'}); + await fencedframe.execute(() => { + let event = { + eventType: "reserved.top_navigation", + eventData: "a".repeat(64001), + destination: ["buyer"], + } + assert_throws_dom("SecurityError", () => { + window.fence.setReportEventDataForAutomaticBeacons(event); + }); + }); +}, 'setReportEventDataForAutomaticBeacons fails over the size limit'); + +promise_test(async(t) => { + const fencedframe = await attachFencedFrameContext( + {generator_api: 'fledge'}); + await fencedframe.execute(() => { + let event = { + eventType: "reserved.top_navigation", + eventData: "a".repeat(32), + destination: ["invalid"], + } + assert_throws_js(TypeError, () => { + window.fence.setReportEventDataForAutomaticBeacons(event); + }); + }); +}, 'setReportEventDataForAutomaticBeacons fails for invalid destination'); +</script> +</body> diff --git a/testing/web-platform/tests/fenced-frame/show-directory-picker.https.html b/testing/web-platform/tests/fenced-frame/show-directory-picker.https.html new file mode 100644 index 0000000000..ae4494f51b --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/show-directory-picker.https.html @@ -0,0 +1,31 @@ +<!DOCTYPE html> +<title>showDirectoryPicker API test</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="resources/utils.js"></script> +<script src="/common/utils.js"></script> +<script src="/common/dispatcher/dispatcher.js"></script> +<script src="/resources/testdriver-actions.js"></script> + +<body> +<script> +promise_test(async () => { + const frame = attachFencedFrameContext(); + const result = await frame.execute(async () => { + await simulateGesture(); + try { + const dir = await window.showDirectoryPicker(); + return 'opened directory picker'; + } catch (e) { + if (e.name === 'SecurityError' && + e.message === "Failed to execute 'showDirectoryPicker' on 'Window': Cross origin sub frames aren't allowed to show a file picker.") { + return 'Access to the directory picker was disallowed'; + } + return `showDirectoryPicker failed with unknown error ${e.name} ${e.message}`; + } + }); + assert_equals(result, 'Access to the directory picker was disallowed'); +}, 'Directory information should not be read in the fenced frame.'); +</script> diff --git a/testing/web-platform/tests/fenced-frame/show-open-file-picker.https.html b/testing/web-platform/tests/fenced-frame/show-open-file-picker.https.html new file mode 100644 index 0000000000..2f99fdfb3b --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/show-open-file-picker.https.html @@ -0,0 +1,32 @@ +<!DOCTYPE html> +<title>showOpenFilePicker API test</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="resources/utils.js"></script> +<script src="/common/utils.js"></script> +<script src="/common/dispatcher/dispatcher.js"></script> +<script src="/resources/testdriver-actions.js"></script> + +<body> +<script> +promise_test(async () => { + const frame = attachFencedFrameContext(); + const result = await frame.execute(async () => { + await simulateGesture(); + try { + const dir = await window.showOpenFilePicker(); + return 'opened file picker'; + } catch (e) { + if (e.name === 'SecurityError' && + e.message === "Failed to execute 'showOpenFilePicker' on 'Window': Cross origin sub frames aren't allowed to show a file picker.") { + return 'Access to the open file picker was disallowed'; + } + return `showOpenFilePicker failed with unknown error ${e.name} ${e.message}`; + } + }); + assert_equals(result, 'Access to the open file picker was disallowed'); +}, 'Directory information should not be read in the fenced frame.'); +</script> +</body> diff --git a/testing/web-platform/tests/fenced-frame/storage-partitioning.https.html b/testing/web-platform/tests/fenced-frame/storage-partitioning.https.html new file mode 100644 index 0000000000..36b4395339 --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/storage-partitioning.https.html @@ -0,0 +1,188 @@ +<!DOCTYPE html> +<title>Test storage partitioning in fenced frames</title> +<meta name="timeout" content="long"> +<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> + +<body> +<script> + +// `getter(key)` : reads the value of `key`, null if not set +// `setter(key, value)`: sets `key` to `value` +async function runTest(getter, setter) { + const key = "key"; + const outer_value = "outer"; + const inner_value = "inner"; + + // Set the value in the top-level frame, and check that it worked. + await setter(key, outer_value); + assert_equals(await getter(key), outer_value, + "Stored the value in the top-level frame."); + + // Attach a fenced frame. + const frame = attachFencedFrameContext(); + + // Check that the outer value isn't visible. + const inner_before_set = await frame.execute(getter, [key]); + assert_equals(inner_before_set, null, + "The outer value isn't visible inside the fenced frame."); + + // Set the value inside the fenced frame, and check that it worked. + await frame.execute(setter, [key, inner_value]); + const inner_after_set = await frame.execute(getter, [key]); + assert_equals(inner_after_set, inner_value, + "Stored the value in the fenced frame."); + + // Check that the inner value isn't visible in the top-level frame. + assert_equals(await getter(key), outer_value, + "The inner value isn't visible outside the fenced frame."); + + // Perform an embedder-initiated navigation that will fail. + const original_config = frame.config; + frame.config = new FencedFrameConfig("resources/response-204.py"); + await step_timeout(() => {}, 1000); + + // Check that the failed navigation didn't change the storage partition. + // (The partition nonce should be reinitialized on navigation commit.) + const inner_after_failure = await frame.execute(getter, [key]); + assert_equals(inner_after_failure, inner_value, + "The inner value is still present after the failed navigation."); + + // Refresh the fenced frame from within. + await frame.execute(() => { + window.executor.suspend(() => { location.href = location.href; }); + }); + + // Check that the storage partition is the same. + const inner_after_inner_refresh = await frame.execute(getter, [key]); + assert_equals(inner_after_inner_refresh, inner_value, + "The inner value is the same after a fencedframe-initiated refresh."); + + // Refresh the fenced frame from the embedder. + await frame.execute(() => window.executor.suspend(() => {})); + frame.element.config = original_config; + + // Check that there is a blank slate. + const inner_after_embedder_refresh = await frame.execute(getter, [key]); + assert_equals(inner_after_embedder_refresh, null, + "The inner value is gone after an embedder-initiated refresh."); +} + +promise_test(async () => { + return runTest( + (_) => { return document.cookie || null; }, + (_, value) => { document.cookie = value;} + ); +}, 'document.cookie'); + +promise_test(async () => { + return runTest( + (key) => { return localStorage.getItem(key); }, + (key, value) => { return localStorage.setItem(key, value); } + ); +}, 'localStorage'); + +promise_test(async () => { + return runTest( + (key) => { return sessionStorage.getItem(key); }, + (key, value) => { return sessionStorage.setItem(key, value); } + ); +}, 'sessionStorage'); + +promise_test(async () => { + return runTest( + async (key) => { + const newCache = await caches.open('test-cache'); + const response = await newCache.match(key); + if (!response) { + return null; + } + return response.text(); + }, + async (key, value) => { + const newCache = await caches.open('test-cache'); + return newCache.put(key, new Response(value)); + } + ); +}, 'Cache API'); + +promise_test(async () => { + return runTest( + async (key) => { + const root = await navigator.storage.getDirectory(); + const draftHandle = await root.getFileHandle(key, { create: true }); + const file = await draftHandle.getFile(); + const text = await file.text(); + return text || null; + }, + async (key, value) => { + const root = await navigator.storage.getDirectory(); + const draftHandle = await root.getFileHandle(key, { create: true }); + const writable = await draftHandle.createWritable() + await writable.truncate(0); + await writable.write(value); + await writable.close(); + } + ); +}, 'File System Access API'); + +promise_test(async () => { + return runTest( + async (key) => { + const openRequest = indexedDB.open('test-db', 2); + const db = await new Promise((resolve) => { + openRequest.onsuccess = (event) => { + resolve(event.target.result); + }; + openRequest.onupgradeneeded = (event) => { + const db = event.target.result; + const objStore = db.createObjectStore('test-tbl', {keyPath: 'key'}); + objStore.transaction.oncomplete = (event) => { + resolve(db); + }; + }; + }); + const readRequest = db.transaction(['test-tbl']) + .objectStore('test-tbl') + .get(key); + return new Promise((resolve) => { + readRequest.onsuccess = (event) => { + if (!event.target.result) { + resolve(null); + } else { + resolve(event.target.result['value']); + } + }; + }); + }, + async (key, value) => { + const openRequest = indexedDB.open('test-db', 2); + const db = await new Promise((resolve) => { + openRequest.onsuccess = (event) => { + resolve(event.target.result); + }; + openRequest.onupgradeneeded = (event) => { + const db = event.target.result; + const objStore = db.createObjectStore('test-tbl', {keyPath: 'key'}); + objStore.transaction.oncomplete = (event) => { + resolve(db); + }; + }; + }); + const writeRequest = db.transaction(['test-tbl'], 'readwrite') + .objectStore('test-tbl') + .add({'key': key, 'value': value}); + return new Promise((resolve) => { + writeRequest.onsuccess = (event) => { + resolve(event.target.result); + }; + }); + } + ); +}, 'IndexedDB'); + +</script> +</body> diff --git a/testing/web-platform/tests/fenced-frame/subframe-loading.https.html b/testing/web-platform/tests/fenced-frame/subframe-loading.https.html new file mode 100644 index 0000000000..758bdd87fa --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/subframe-loading.https.html @@ -0,0 +1,30 @@ +<!DOCTYPE html> +<title>Test Subframe Loading Disabler</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> + +<body> +<script> + +promise_test(async () => { + const container = document.body.appendChild(document.createElement("div")); + + // Create a helper iframe that triggers loading the new fenced frame + // simultaneously when the parent container is removed. + const helperFrame = container.appendChild(document.createElement("iframe")); + + helperFrame.contentWindow.onunload = function() { + const fenced_frame = document.createElement("fencedframe"); + fenced_frame.src = "resources/dummy.https.html"; + container.appendChild(fenced_frame); + }; + + // If the fenced frame loads when it's not supposed to, + // a DCHECK will catch that and cause the test to crash. + document.body.removeChild(container); + +}, "Fenced frames should not load if its parent has subframe loading disabled"); + +</script> +</body> +</html> diff --git a/testing/web-platform/tests/fenced-frame/unique-cookie-partition.https.html b/testing/web-platform/tests/fenced-frame/unique-cookie-partition.https.html new file mode 100644 index 0000000000..8ecd56ffc3 --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/unique-cookie-partition.https.html @@ -0,0 +1,55 @@ +<!DOCTYPE html> +<title>Test cookies accessed from a Fenced Frame Tree</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/utils.js"></script> +<script src="/common/utils.js"></script> + +<body> +<script> +const cookie_value_key = token(); +const kAssertion = "Cookie accessed from unique fenced frame tree partition "; +const kAssertion_outer = "Cookie values changed in the fenced frame tree should not impact the outer frame"; + +async function runTest(test_type) { + document.cookie = 'A=outer; SameSite=Lax'; + document.cookie = 'B=outer; SameSite=None; Secure'; + const fenced_frame = + attachFencedFrame(generateURL( + `resources/unique-cookie-partition-inner.https.html`, + [cookie_value_key, test_type])); + + result = await nextValueFromServer(cookie_value_key); + switch (test_type) { + case "top-level fenced frame": + assert_equals(result, "F=fenced; C=fenced; D=fenced; E=fenced", kAssertion + test_type); + break; + case "nested iframe": + assert_equals(result, "F=fenced; C=fenced; D=fenced; G=nested_in_fenced_frame; E=fenced", kAssertion + test_type); + break; + case "nested fenced frame": + assert_equals(result, "G=nested_in_fenced_frame", kAssertion + test_type); + break; + } + + // The cookie values changed in the fenced frame tree should not impact the outer frame. + const result_outer_frame = document.cookie; + assert_equals(result_outer_frame, "A=outer; B=outer", kAssertion_outer); + + // Clean up the fenced frame + document.body.removeChild(fenced_frame); +} + +promise_test(async () => { + return runTest("top-level fenced frame"); +}, "Cookie access from top-level fenced frame"); + +promise_test(async () => { + return runTest("nested iframe"); +}, "Cookie access from iframe nested in a fenced frame"); + +promise_test(async () => { + return runTest("nested fenced frame"); +}, "Cookie access from nested fenced frame"); +</script> +</body> diff --git a/testing/web-platform/tests/fenced-frame/user-activation.https.html b/testing/web-platform/tests/fenced-frame/user-activation.https.html new file mode 100644 index 0000000000..3ca0dca49b --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/user-activation.https.html @@ -0,0 +1,95 @@ +<!DOCTYPE html> +<title>Test that user activation propagation is fenced.</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.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/utils.js"></script> +<script src="/common/dispatcher/dispatcher.js"></script> +<script src="resources/utils.js"></script> + +<body> +<script> +// Simulate a click in frame context `frame`. +async function click(frame) { + var actions = new test_driver.Actions(); + await actions.pointerMove(0, 0, {origin: frame}) + .pointerDown() + .pointerUp() + .send(); +} + +assert_activation = (should_be_active, frame_name) => { + if (should_be_active) { + assert_true(navigator.userActivation.hasBeenActive, + frame_name + " has been activated."); + assert_true(navigator.userActivation.isActive, + frame_name + " is currently active."); + } else { + assert_false(navigator.userActivation.hasBeenActive, + frame_name + " has not been activated yet."); + assert_false(navigator.userActivation.isActive, + frame_name + " is not currently active."); + } +}; + +promise_test(async () => { + // This test ensures that user activations (e.g. click events) don't + // propagate across fenced frame boundaries. Specifically, activations + // are visible through the `navigator.userActivation` object. + // + // The layout of the page is as follows: + // A: top-level frame + // B: iframe + // C: fencedframe + // D: iframe + // E: fencedframe + // + // This order is chosen to test all kinds of fenced tree traversal. We: + // - Click in C and check that only C gets activated (not A, B, D, or E) + // - Click in A and check that only A, B, and D get activated (not E) + // - Click in B and D and check that E wasn't activated + + const B = attachIFrameContext(); + const C = attachFencedFrameContext(); + const D = attachIFrameContext(); + const E = attachFencedFrameContext(); + + // Define some helpers to check activations more concisely. + const frames = [[B, "B"], [C, "C"], [D, "D"], [E, "E"]]; + const assert_activations = async (should_be_actives) => { + assert_equals(frames.length, should_be_actives.length); + for ([i, [frame, frame_name]] of frames.entries()) { + await frame.execute(assert_activation, [should_be_actives[i], frame_name]); + } + }; + + // Check that all the frames are inactive before we start. + assert_activation(false, "A"); + await assert_activations([false/*B*/, false/*C*/, false/*D*/, false/*E*/]); + + // Simulate a click in C (the first fenced frame). + await click(C.element); + + // Check that only C has been activated. + assert_activation(false, "A"); + await assert_activations([false/*B*/, true/*C*/, false/*D*/, false/*E*/]); + + // Simulate a click in A (the top-level site). + await click(document.documentElement); + + // Check that A, B, and D were activated. + assert_activation(true, "A"); + await assert_activations([true/*B*/, true/*C*/, true/*D*/, false/*E*/]); + + // Simulate a click in B and D (the two iframes). + await click(B.element); + await click(D.element); + + // Check that E has still not been activated. + assert_activation(true, "A"); + await assert_activations([true/*B*/, true/*C*/, true/*D*/, false/*E*/]); +}, 'user-activation'); +</script> +</body> diff --git a/testing/web-platform/tests/fenced-frame/visual-viewport.https.html b/testing/web-platform/tests/fenced-frame/visual-viewport.https.html new file mode 100644 index 0000000000..7870f11e8c --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/visual-viewport.https.html @@ -0,0 +1,82 @@ +<!DOCTYPE html> +<title>Test visualViewport inside a fenced frame.</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.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/utils.js"></script> +<script src="/common/dispatcher/dispatcher.js"></script> +<script src="resources/utils.js"></script> + +<body> +<script> +function pinch_zoom_in() { + return new test_driver.Actions() + .setContext(window) + .addPointer("finger1", "touch") + .addPointer("finger2", "touch") + .pointerMove(400, 250, {origin: "viewport", sourceName: "finger1"}) + .pointerMove(400, 350, {origin: "viewport", sourceName: "finger2"}) + .pointerDown({sourceName: "finger1"}) + .pointerDown({sourceName: "finger2"}) + .pointerMove(400, 200, {origin: "viewport", sourceName: "finger1"}) + .pointerMove(400, 400, {origin: "viewport", sourceName: "finger2"}) + .pointerUp({sourceName: "finger1"}) + .pointerUp({sourceName: "finger2"}) + .send(); +} + +promise_test(async () => { + // Create a fenced frame, and use the same target name inside of it. + const frame = attachFencedFrameContext({html: ` + <!DOCTYPE html> + <style> + body { + /* Make fenced frame scrollable */ + width: 200vw; + height: 200vh; + } + + ::-webkit-scrollbar { + display: none; + } + </style>`}); + + const is_mac = navigator.platform.indexOf('Mac') == 0; + + // Mac doesn't support pinch zooming via test driver so just avoid trying. + if (!is_mac) { + await pinch_zoom_in(); + + // Run the test zoomed in to ensure the fenced frame doesn't incorrectly + // bring values in from its embedder. + assert_greater_than(window.visualViewport.scale, 1, + '[PRECONDITION] outer window pinch-zoomed in'); + } + + await frame.execute(async (width, height) => { + window.scrollTo(30, 40); + assert_equals(window.scrollX, 30, '[PRECONDITION] document scrolled x'); + assert_equals(window.scrollY, 40, '[PRECONDITION] document scrolled y'); + + assert_equals(window.visualViewport.width, width, + 'visualViewport.width matches fencedframe width'); + assert_equals(window.visualViewport.height, height, + 'visualViewport.height matches fencedframe height'); + assert_equals(window.visualViewport.scale, 1, + 'visualViewport.scale is 1'); + assert_equals(window.visualViewport.offsetLeft, 0, + 'visualViewport.offsetLeft is 0'); + assert_equals(window.visualViewport.offsetTop, 0, + 'visualViewport.offsetTop is 0'); + assert_equals(window.visualViewport.pageLeft, window.scrollX, + 'visualViewport.pageLeft reflects only window scroll offset'); + assert_equals(window.visualViewport.pageTop, window.scrollY, + 'visualViewport.pageTop reflects only window scroll offset'); + }, [frame.clientWidth, frame.clientHeight]); + +}, 'visualViewport values inside fenced frame'); + +</script> +</body> diff --git a/testing/web-platform/tests/fenced-frame/web-bluetooth.https.html b/testing/web-platform/tests/fenced-frame/web-bluetooth.https.html new file mode 100644 index 0000000000..88bbd6ec37 --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/web-bluetooth.https.html @@ -0,0 +1,26 @@ +<!DOCTYPE html> +<title>Test of Web Bluetooth API</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="resources/utils.js"></script> +<script src="/common/utils.js"></script> + +<body> + +<script> +promise_test(async t => { + const bluetooth_request_device_key = token(); + + attachFencedFrame(generateURL('resources/web-bluetooth-inner.html', + [bluetooth_request_device_key])); + const result = await nextValueFromServer(bluetooth_request_device_key); + + assert_equals( + result, 'Web Bluetooth requestDevice() failed', + 'Web Bluetooth requestDevice() must fail in a fenced frame.'); +}, 'Web Bluetooth requestDevice() must fail in a fenced frame'); +</script> + +</body> diff --git a/testing/web-platform/tests/fenced-frame/web-nfc.https.html b/testing/web-platform/tests/fenced-frame/web-nfc.https.html new file mode 100644 index 0000000000..c7de9d81f9 --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/web-nfc.https.html @@ -0,0 +1,28 @@ +<!DOCTYPE html> +<title>Test Web NFC API</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/utils.js"></script> +<script src="/common/utils.js"></script> + +<body> + +<script> +promise_test(async () => { + const ndef_write_key = token(); + const ndef_scan_key = token(); + + attachFencedFrame(generateURL("resources/web-nfc-inner.https.html", + [ndef_write_key, ndef_scan_key])); + + let result = await nextValueFromServer(ndef_write_key); + assert_equals(result, "rejected", + "The fenced frame is not allowed to NDEFReader.write()."); + result = await nextValueFromServer(ndef_scan_key); + assert_equals(result, "rejected", + "The fenced frame is not allowed to NDEFReader.scan()."); + +}, "Test Web NFC API"); +</script> + +</body> diff --git a/testing/web-platform/tests/fenced-frame/web-share.https.html b/testing/web-platform/tests/fenced-frame/web-share.https.html new file mode 100644 index 0000000000..13d182b2d1 --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/web-share.https.html @@ -0,0 +1,26 @@ +<!DOCTYPE html> +<title>Test of Web Share</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="resources/utils.js"></script> +<script src="/common/utils.js"></script> + +<body> + +<script> +promise_test(async t => { + const navigator_share_key = token(); + + attachFencedFrame(generateURL('resources/web-share-inner.html', + [navigator_share_key])); + const result = await nextValueFromServer(navigator_share_key); + + assert_equals( + result, 'Web Share failed', + 'Web Share in a fenced frame must fail.'); +}, 'Web Sharelock in a fenced frame must fail'); +</script> + +</body> diff --git a/testing/web-platform/tests/fenced-frame/web-usb.https.html b/testing/web-platform/tests/fenced-frame/web-usb.https.html new file mode 100644 index 0000000000..3156f68ea7 --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/web-usb.https.html @@ -0,0 +1,29 @@ +<!DOCTYPE html> +<title>Test WebUSB navigator.usb.requestDevice()</title> +<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> + +<body> +<script> +promise_test(async () => { + const frame = attachFencedFrameContext(); + await frame.execute(async () => { + // Request USB access inside the fenced frame. It should fail. + // https://github.com/shivanigithub/fenced-frame#security-considerations. + try { + await navigator.usb.requestDevice({ filters: [{ vendorId: 0}] }); + throw 'The USB request should not succeed.'; + } catch (e) { + assert_equals(e.name, 'SecurityError'); + assert_equals(e.message, + "Failed to execute 'requestDevice' on 'USB': " + + 'Access to the feature "usb" is disallowed by permissions policy.', + 'Fenced frame has the right error for usb.requestDevice.'); + } + }); +}, 'navigator.usb.requestDevice'); +</script> +</body> diff --git a/testing/web-platform/tests/fenced-frame/window-close.https.html b/testing/web-platform/tests/fenced-frame/window-close.https.html new file mode 100644 index 0000000000..b581a0324c --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/window-close.https.html @@ -0,0 +1,21 @@ +<!DOCTYPE html> +<title>Test window.close has no effect</title> +<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> + +<body> +<script> +promise_test(async () => { + const frame = attachFencedFrameContext(); + await frame.execute(async () => { + // This should have no effect for fenced frames. + window.close(); + // window.closed will be true if the window closing steps have begun. + assert_false(window.closed); + }); +}, 'window.close'); +</script> +</body> diff --git a/testing/web-platform/tests/fenced-frame/window-frameElement.https.html b/testing/web-platform/tests/fenced-frame/window-frameElement.https.html new file mode 100644 index 0000000000..bd4532b1eb --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/window-frameElement.https.html @@ -0,0 +1,21 @@ +<!DOCTYPE html> +<title>Test window.parent</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/common/utils.js"></script> +<script src="resources/utils.js"></script> + +<body> + +<script> +promise_test(async () => { + const frame_element_key = token(); + + attachFencedFrame(generateURL("resources/window-frameElement-inner.html", + [frame_element_key])); + const fenced_frame_result = await nextValueFromServer(frame_element_key); + assert_equals(fenced_frame_result, "PASS"); +}, "window.frameElement null for same-origin fenced frames"); +</script> + +</body> diff --git a/testing/web-platform/tests/fenced-frame/window-navigation-204.https.html b/testing/web-platform/tests/fenced-frame/window-navigation-204.https.html new file mode 100644 index 0000000000..6722060330 --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/window-navigation-204.https.html @@ -0,0 +1,24 @@ +<!DOCTYPE html> +<title>Test window.navigation.204</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/utils.js"></script> +<script src="/common/utils.js"></script> + +<body> + +<script> +promise_test(async () => { + const window_data_key = token(); + + attachFencedFrame(generateURL("resources/window-navigation-204-inner.html", + [window_data_key])); + + const actual_result = await nextValueFromServer(window_data_key); + assert_equals(actual_result, "still in page", + "The fenced frame has the right value for `204 response` upon " + + "subsequent navigation resulting in HTTP 204"); +}, "window.navigation.204"); +</script> + +</body> diff --git a/testing/web-platform/tests/fenced-frame/window-outer-dimensions.https.html b/testing/web-platform/tests/fenced-frame/window-outer-dimensions.https.html new file mode 100644 index 0000000000..c6a64ef193 --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/window-outer-dimensions.https.html @@ -0,0 +1,53 @@ +<!DOCTYPE html> +<title>Test window.prompt</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/common/utils.js"></script> +<script src="resources/utils.js"></script> + +<body> + <script> + async function runTest(dimension, extra_children) { + const window_outer_size_key = token(); + const window_inner_size_key = token(); + + const window_outer_size_from_top_frame = (dimension == "width" ? + window.outerWidth : window.outerHeight).toString(); + + const frame_url = generateURL( + 'resources/window-outer-dimensions-inner.html', + [window_outer_size_key, window_inner_size_key, dimension, extra_children]); + const new_frame = attachFencedFrame(frame_url); + + // Get the outer and inner width/height from either the top level + // fencedframe or the nested iframe. + const result_outer = await nextValueFromServer(window_outer_size_key); + const result_inner = await nextValueFromServer(window_inner_size_key); + + assert_not_equals(result_outer, + window_outer_size_from_top_frame, + "Outer " + dimension + " not read in fenced frame" + + " tree."); + + assert_equals(result_outer, + result_inner, + "Fencedframe's outer/inner " + dimension + " match."); + } + + promise_test(async () => { + return runTest("width", 0); + }, "window.outerWidth"); + + promise_test(async () => { + return runTest("height", 0); + }, "window.outerHeight"); + + promise_test(async () => { + return runTest("width", 1); + }, "window.outerWidth nested iframe"); + + promise_test(async () => { + return runTest("height", 1); + }, "window.outerHeight nested iframe"); + </script> +</body> diff --git a/testing/web-platform/tests/fenced-frame/window-parent.https.html b/testing/web-platform/tests/fenced-frame/window-parent.https.html new file mode 100644 index 0000000000..c2f3604050 --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/window-parent.https.html @@ -0,0 +1,44 @@ +<!DOCTYPE html> +<title>Test window.parent</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/common/utils.js"></script> +<script src="resources/utils.js"></script> + +<body> + +<script> +promise_test(async () => { + const window_parent_key = token(); + const window_parent_ack_key = token(); + + attachFencedFrame(generateURL("resources/window-parent-inner.html", + [window_parent_key, window_parent_ack_key])); + + // Get the result for the top-level fenced frame. + const fenced_frame_result = await nextValueFromServer(window_parent_key); + assert_equals(fenced_frame_result, "pass: fenced frame", "The top-level " + + "fenced frame has the right value for " + + "`window.parent`"); + + // Write an ACK, so that the fenced frame knows it can send message over the + // `window_parent_key` channel again. + writeValueToServer(window_parent_ack_key, "ACK"); + + // Get the result for the iframe inside the fenced frame. + const iframe_in_fenced_frame_result = await nextValueFromServer(window_parent_key); + assert_equals(iframe_in_fenced_frame_result, "pass: fenced frame > iframe", + "The iframe inside the top-level fenced frame has the right " + + "value for `window.parent`"); + + writeValueToServer(window_parent_ack_key, "ACK"); + + // Get the result for the nested fenced frame. + const nested_fenced_frame_result = await nextValueFromServer(window_parent_key); + assert_equals(nested_fenced_frame_result, "pass: fenced frame > fenced frame", + "The nested fenced frame inside the top-level fenced frame " + + "has the right value for `window.parent`"); +}, "window.parent"); +</script> + +</body> diff --git a/testing/web-platform/tests/fenced-frame/window-top.https.html b/testing/web-platform/tests/fenced-frame/window-top.https.html new file mode 100644 index 0000000000..9a8f39f08b --- /dev/null +++ b/testing/web-platform/tests/fenced-frame/window-top.https.html @@ -0,0 +1,44 @@ +<!DOCTYPE html> +<title>Test window.top</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/common/utils.js"></script> +<script src="resources/utils.js"></script> + +<body> + +<script> +promise_test(async () => { + const window_top_key = token(); + const window_top_ack_key = token(); + + attachFencedFrame(generateURL("resources/window-top-inner.html", + [window_top_key, window_top_ack_key, "not nested"])); + + // Get the result for the top-level fenced frame. + const fenced_frame_result = await nextValueFromServer(window_top_key); + assert_equals(fenced_frame_result, "pass: fenced frame", "The top-level " + + "fenced frame has the right value for " + + "`window.top`"); + + // Write an ACK, so that the fenced frame knows it can send message over the + // `window_top_key` channel again. + writeValueToServer(window_top_ack_key, "ACK"); + + // Get the result for the iframe inside the fenced frame. + const iframe_in_fenced_frame_result = await nextValueFromServer(window_top_key); + assert_equals(iframe_in_fenced_frame_result, "pass: fenced frame > iframe", + "The iframe inside the top-level fenced frame has the right " + + "value for `window.top`"); + + writeValueToServer(window_top_ack_key, "ACK"); + + // Get the result for the nested fenced frame. + const nested_fenced_frame_result = await nextValueFromServer(window_top_key); + assert_equals(nested_fenced_frame_result, "pass: fenced frame > fenced frame", + "The nested fenced frame inside the top-level fenced frame " + + "has the right value for `window.top`"); +}, "window.top"); +</script> + +</body> |