diff options
Diffstat (limited to 'testing/web-platform/tests/html/anonymous-iframe')
23 files changed, 1490 insertions, 0 deletions
diff --git a/testing/web-platform/tests/html/anonymous-iframe/anonymous-iframe-popup.tentative.https.window.js b/testing/web-platform/tests/html/anonymous-iframe/anonymous-iframe-popup.tentative.https.window.js new file mode 100644 index 0000000000..fbdeeab4ed --- /dev/null +++ b/testing/web-platform/tests/html/anonymous-iframe/anonymous-iframe-popup.tentative.https.window.js @@ -0,0 +1,67 @@ +// META: timeout=long +// META: script=/common/get-host-info.sub.js +// META: script=/common/utils.js +// META: script=/common/dispatcher/dispatcher.js +// META: script=/html/cross-origin-embedder-policy/credentialless/resources/common.js + +const {ORIGIN, REMOTE_ORIGIN} = get_host_info(); +const control_iframe = document.createElement('iframe'); +const iframe_credentialless = document.createElement('iframe'); + +promise_setup(async t => { + const createControlIframe = new Promise(async resolve => { + control_iframe.onload = resolve; + control_iframe.src = ORIGIN + `/common/blank.html`; + document.body.append(control_iframe); + }); + + const createIframeCredentialless = new Promise(async resolve => { + iframe_credentialless.onload = resolve; + iframe_credentialless.src = ORIGIN + `/common/blank.html`; + iframe_credentialless.credentialless = true; + document.body.append(iframe_credentialless); + }); + + await Promise.all([createControlIframe, createIframeCredentialless]); +}); + +// Create cross-origin popup from iframes. The opener should be blocked for +// credentialless iframe and work for normal iframe. +promise_test(async t => { + const control_token = token(); + const control_src = REMOTE_ORIGIN + executor_path + `&uuid=${control_token}`; + const control_popup = control_iframe.contentWindow.open(control_src); + add_completion_callback(() => send(control_token, "close();")); + assert_equals( + control_popup.opener, control_iframe.contentWindow, + "Opener from normal iframe should be available."); + + const credentialless_token = token(); + const credentialless_src = + REMOTE_ORIGIN + executor_path + `&uuid=${credentialless_token}`; + const credentialless_popup = + iframe_credentialless.contentWindow.open(credentialless_src); + add_completion_callback(() => send(credentialless_token, "close();")); + assert_equals(credentialless_popup, null, + "Opener from credentialless iframe should be blocked."); +}, 'Cross-origin popup from normal/credentiallessiframes.'); + +// Create a same-origin popup from iframes. The opener should be blocked for +// credentialless iframe and work for normal iframe. +promise_test(async t => { + const control_token = token(); + const control_src = ORIGIN + executor_path + `&uuid=${control_token}`; + const control_popup = control_iframe.contentWindow.open(control_src); + add_completion_callback(() => send(control_token, "close();")); + assert_equals( + control_popup.opener, control_iframe.contentWindow, + "Opener from normal iframe should be available."); + + const credentialless_token = token(); + const credentialless_src = + ORIGIN + executor_path + `&uuid=${credentialless_token}`; + const credentialless_popup = iframe_credentialless.contentWindow.open(credentialless_src); + add_completion_callback(() => send(credentialless_token, "close();")); + assert_equals(credentialless_popup, null, + "Opener from credentialless iframe should be blocked."); +}, 'Same-origin popup from normal/credentialless iframes.'); diff --git a/testing/web-platform/tests/html/anonymous-iframe/anonymous-window.tentative.https.window.js b/testing/web-platform/tests/html/anonymous-iframe/anonymous-window.tentative.https.window.js new file mode 100644 index 0000000000..8c9c103429 --- /dev/null +++ b/testing/web-platform/tests/html/anonymous-iframe/anonymous-window.tentative.https.window.js @@ -0,0 +1,50 @@ +// META: script=/common/get-host-info.sub.js +// META: script=/common/dispatcher/dispatcher.js +// META: script=/html/cross-origin-embedder-policy/credentialless/resources/common.js + +const {ORIGIN} = get_host_info(); + +promise_test_parallel(async t => { + const iframe = document.createElement("iframe"); + iframe.src = ORIGIN + "/common/blank.html?pipe=status(204)"; + iframe.credentialless = false; + document.body.appendChild(iframe); + iframe.credentialless = true; + iframe.contentWindow.modified = true; + iframe.src = ORIGIN + "/common/blank.html"; + // Wait for navigation to complete. + await new Promise(resolve => iframe.onload = resolve); + assert_true(iframe.credentialless); + assert_true(iframe.contentWindow.credentialless); + assert_equals(undefined, iframe.contentWindow.modified); +}, "Credentialless (false => true) => window not reused."); + +promise_test_parallel(async t => { + const iframe = document.createElement("iframe"); + iframe.src = ORIGIN + "/common/blank.html?pipe=status(204)"; + iframe.credentialless = true; + document.body.appendChild(iframe); + iframe.credentialless = false; + iframe.contentWindow.modified = true; + iframe.src = ORIGIN + "/common/blank.html"; + // Wait for navigation to complete. + await new Promise(resolve => iframe.onload = resolve); + assert_false(iframe.credentialless); + assert_false(iframe.contentWindow.credentialless); + assert_equals(undefined, iframe.contentWindow.modified); +}, "Credentialless (true => false) => window not reused."); + +promise_test_parallel(async t => { + const iframe = document.createElement("iframe"); + iframe.credentialless = true; + iframe.src = ORIGIN + "/common/blank.html?pipe=status(204)"; + document.body.appendChild(iframe); + iframe.credentialless = true; + iframe.contentWindow.modified = true; + iframe.src = ORIGIN + "/common/blank.html"; + // Wait for navigation to complete. + await new Promise(resolve => iframe.onload = resolve); + assert_true(iframe.credentialless); + assert_true(iframe.contentWindow.credentialless); + assert_true(iframe.contentWindow.modified); +}, "Credentialless (true => true) => window reused."); diff --git a/testing/web-platform/tests/html/anonymous-iframe/cache-storage.tentative.https.window.js b/testing/web-platform/tests/html/anonymous-iframe/cache-storage.tentative.https.window.js new file mode 100644 index 0000000000..b816de4cd5 --- /dev/null +++ b/testing/web-platform/tests/html/anonymous-iframe/cache-storage.tentative.https.window.js @@ -0,0 +1,60 @@ +// META: script=/common/get-host-info.sub.js +// META: script=/common/utils.js +// META: script=/common/dispatcher/dispatcher.js +// META: script=/html/cross-origin-embedder-policy/credentialless/resources/common.js +// META: script=./resources/common.js + +// A script storing a value into the CacheStorage. +const store_script = (key, value, done) => ` + const request = new Request("/${key}.txt"); + const response = new Response("${value}", { + headers: { "content-type": "plain/txt" } + }); + const cache = await caches.open("v1"); + const value = await cache.put(request, response.clone()); + send("${done}", "stored"); +`; + +// A script loading a value from the CacheStorage. +const load_script = (key, done) => ` + const cache = await caches.open("v1"); + const request = new Request("/${key}.txt"); + try { + const response = await cache.match(request); + const value = await response.text(); + send("${done}", value); + } catch (error) { + send("${done}", "not found"); + } +`; + +promise_test(async test => { + const origin = get_host_info().HTTPS_REMOTE_ORIGIN; + const key_1 = token(); + const key_2 = token(); + + // 2 actors: A credentialless iframe and a normal one. + const iframe_credentialless = newIframeCredentialless(origin); + const iframe_normal = newIframe(origin); + const response_queue_1 = token(); + const response_queue_2 = token(); + + // 1. Each of them store a value in CacheStorage with different keys. + send(iframe_credentialless , store_script(key_1, "value_1", response_queue_1)); + send(iframe_normal, store_script(key_2, "value_2", response_queue_2)); + assert_equals(await receive(response_queue_1), "stored"); + assert_equals(await receive(response_queue_2), "stored"); + + // 2. Each of them tries to retrieve the value from opposite side, without + // success. + send(iframe_credentialless , load_script(key_2, response_queue_1)); + send(iframe_normal, load_script(key_1, response_queue_2)); + assert_equals(await receive(response_queue_1), "not found"); + assert_equals(await receive(response_queue_2), "not found"); + + // 3. Each of them tries to retrieve the value from their side, with success: + send(iframe_credentialless , load_script(key_1, response_queue_1)); + send(iframe_normal, load_script(key_2, response_queue_2)); + assert_equals(await receive(response_queue_1), "value_1"); + assert_equals(await receive(response_queue_2), "value_2"); +}) diff --git a/testing/web-platform/tests/html/anonymous-iframe/cookie-store.tentative.https.window.js b/testing/web-platform/tests/html/anonymous-iframe/cookie-store.tentative.https.window.js new file mode 100644 index 0000000000..ddad924ea0 --- /dev/null +++ b/testing/web-platform/tests/html/anonymous-iframe/cookie-store.tentative.https.window.js @@ -0,0 +1,94 @@ +// META: timeout=long +// META: script=/common/get-host-info.sub.js +// META: script=/common/utils.js +// META: script=/common/dispatcher/dispatcher.js +// META: script=/html/cross-origin-embedder-policy/credentialless/resources/common.js +// META: script=./resources/common.js + +// A set of tests, checking cookies defined from within a credentialless iframe +// continue to work. + +const same_origin = get_host_info().HTTPS_ORIGIN; +const cross_origin = get_host_info().HTTPS_REMOTE_ORIGIN; +const cookie_key = token() + +const credentialless_iframe = newIframeCredentialless(cross_origin); + +// Install some helper functions in the child to observe Cookies: +promise_setup(async () => { + await send(credentialless_iframe, ` + window.getMyCookie = () => { + const value = "; " + document.cookie; + const parts = value.split("; ${cookie_key}="); + if (parts.length !== 2) + return undefined + return parts.pop().split(';').shift(); + }; + + window.nextCookieValue = () => { + return new Promise(resolve => { + const old_cookie = getMyCookie(); + let timeToLive = 40; // 40 iterations of 100ms = 4s; + const interval = setInterval(() => { + const next_cookie_value = getMyCookie(); + timeToLive--; + if (old_cookie !== next_cookie_value || timeToLive <= 0) { + clearInterval(interval); + resolve(next_cookie_value) + } + }, 100) + }); + }; + `); +}, "Setup"); + +promise_test(async test => { + const this_token = token(); + send(credentialless_iframe, ` + document.cookie = "${cookie_key}=cookie_value_1"; + send("${this_token}", getMyCookie()); + `); + + assert_equals(await receive(this_token), "cookie_value_1"); +}, "Set/Get cookie via JS API"); + +promise_test(async test => { + const resource_token = token(); + send(credentialless_iframe, ` + fetch("${showRequestHeaders(cross_origin, resource_token)}"); + `); + + const request_headers = JSON.parse(await receive(resource_token)); + const cookie_value = parseCookies(request_headers)[cookie_key]; + assert_equals(cookie_value, "cookie_value_1"); +}, "Get Cookie via subresource requests"); + +promise_test(async test => { + const resource_token = token(); + const resource_url = cross_origin + "/common/blank.html?pipe=" + + `|header(Set-Cookie,${cookie_key}=cookie_value_2;Path=/common/dispatcher)`; + const this_token = token(); + send(credentialless_iframe, ` + const next_cookie_value = nextCookieValue(); + fetch("${resource_url}"); + send("${this_token}", await next_cookie_value); + `); + + assert_equals(await receive(this_token), "cookie_value_2"); +}, "Set Cookie via subresource requests"); + +promise_test(async test => { + const resource_token = token(); + const resource_url = cross_origin + "/common/blank.html?pipe=" + + `|header(Set-Cookie,${cookie_key}=cookie_value_3;Path=/common/dispatcher)`; + const this_token = token(); + send(credentialless_iframe, ` + const next_cookie_value = nextCookieValue(); + const iframe = document.createElement("iframe"); + iframe.src = "${resource_url}"; + document.body.appendChild(iframe); + send("${this_token}", await next_cookie_value); + `); + + assert_equals(await receive(this_token), "cookie_value_3"); +}, "Set Cookie via navigation requests"); diff --git a/testing/web-platform/tests/html/anonymous-iframe/cookie.tentative.https.window.js b/testing/web-platform/tests/html/anonymous-iframe/cookie.tentative.https.window.js new file mode 100644 index 0000000000..d6889ae52d --- /dev/null +++ b/testing/web-platform/tests/html/anonymous-iframe/cookie.tentative.https.window.js @@ -0,0 +1,128 @@ +// META: script=/common/get-host-info.sub.js +// META: script=/common/utils.js +// META: script=/common/dispatcher/dispatcher.js +// META: script=/html/cross-origin-embedder-policy/credentialless/resources/common.js +// META: script=./resources/common.js + +const same_origin = get_host_info().HTTPS_ORIGIN; +const cross_origin = get_host_info().HTTPS_REMOTE_ORIGIN; +const cookie_key = "credentialless_iframe_load_cookie"; +const cookie_same_origin = "same_origin"; +const cookie_cross_origin = "cross_origin"; + +const cookieFromResource = async resource_token => { + let headers = JSON.parse(await receive(resource_token)); + return parseCookies(headers)[cookie_key]; +}; + +// Load a credentialless iframe, return the HTTP request cookies. +const cookieFromCredentiallessIframeRequest = async (iframe_origin) => { + const resource_token = token(); + let iframe = document.createElement("iframe"); + iframe.src = `${showRequestHeaders(iframe_origin, resource_token)}`; + iframe.credentialless = true; + document.body.appendChild(iframe); + return await cookieFromResource(resource_token); +}; + +// Load a resource `type` from the iframe with `document_token`, +// return the HTTP request cookies. +const cookieFromResourceInIframe = + async (document_token, resource_origin, type = "img") => { + const resource_token = token(); + send(document_token, ` + let el = document.createElement("${type}"); + el.src = "${showRequestHeaders(resource_origin, resource_token)}"; + document.body.appendChild(el); + `); + return await cookieFromResource(resource_token); +}; + +promise_test_parallel(async test => { + await Promise.all([ + setCookie(same_origin, cookie_key, cookie_same_origin), + setCookie(cross_origin, cookie_key, cookie_cross_origin), + ]); + + promise_test_parallel(async test => { + assert_equals( + await cookieFromCredentiallessIframeRequest(same_origin), + undefined + ); + }, "Credentialless same-origin iframe is loaded without credentials"); + + promise_test_parallel(async test => { + assert_equals( + await cookieFromCredentiallessIframeRequest(cross_origin), + undefined + ); + }, "Credentialless cross-origin iframe is loaded without credentials"); + + const iframe_same_origin = newIframeCredentialless(same_origin); + const iframe_cross_origin = newIframeCredentialless(cross_origin); + + promise_test_parallel(async test => { + assert_equals( + await cookieFromResourceInIframe(iframe_same_origin, same_origin), + undefined + ); + }, "same_origin credentialless iframe can't send same_origin credentials"); + + promise_test_parallel(async test => { + assert_equals( + await cookieFromResourceInIframe(iframe_same_origin, cross_origin), + undefined + ); + }, "same_origin credentialless iframe can't send cross_origin credentials"); + + promise_test_parallel(async test => { + assert_equals( + await cookieFromResourceInIframe(iframe_cross_origin, cross_origin), + undefined + ); + }, "cross_origin credentialless iframe can't send cross_origin credentials"); + + promise_test_parallel(async test => { + assert_equals( + await cookieFromResourceInIframe(iframe_cross_origin, same_origin), + undefined + ); + }, "cross_origin credentialless iframe can't send same_origin credentials"); + + promise_test_parallel(async test => { + assert_equals( + await cookieFromResourceInIframe(iframe_same_origin, same_origin, + "iframe"), + undefined + ); + }, "same_origin credentialless iframe can't send same_origin credentials " + + "on child iframe"); + + promise_test_parallel(async test => { + assert_equals( + await cookieFromResourceInIframe(iframe_same_origin, cross_origin, + "iframe"), + undefined + ); + }, "same_origin credentialless iframe can't send cross_origin credentials " + + "on child iframe"); + + promise_test_parallel(async test => { + assert_equals( + await cookieFromResourceInIframe(iframe_cross_origin, cross_origin, + "iframe"), + undefined + ); + }, "cross_origin credentialless iframe can't send cross_origin credentials " + + "on child iframe"); + + promise_test_parallel(async test => { + assert_equals( + await cookieFromResourceInIframe(iframe_cross_origin, same_origin, + "iframe"), + undefined + ); + }, "cross_origin credentialless iframe can't send same_origin credentials " + + "on child iframe"); + +}, "Setup") diff --git a/testing/web-platform/tests/html/anonymous-iframe/embedding.tentative.https.window.js b/testing/web-platform/tests/html/anonymous-iframe/embedding.tentative.https.window.js new file mode 100644 index 0000000000..8c5e2fa60f --- /dev/null +++ b/testing/web-platform/tests/html/anonymous-iframe/embedding.tentative.https.window.js @@ -0,0 +1,124 @@ +// META: variant=?1-1 +// META: variant=?2-2 +// META: variant=?3-3 +// META: variant=?4-4 +// META: variant=?5-5 +// META: variant=?6-6 +// META: variant=?7-7 +// META: variant=?8-8 +// META: variant=?9-9 +// META: variant=?10-10 +// META: variant=?11-11 +// META: variant=?12-12 +// META: variant=?13-last +// META: timeout=long +// META: script=/common/get-host-info.sub.js +// META: script=/common/utils.js +// META: script=/common/subset-tests.js +// META: script=/common/dispatcher/dispatcher.js +// META: script=/html/cross-origin-embedder-policy/credentialless/resources/common.js +// META: script=./resources/common.js +// META: script=./resources/embedding-test.js + +const {REMOTE_ORIGIN} = get_host_info(); + +// variant = 1 +subsetTest(embeddingTest, + "Parent embeds same-origin credentialless iframe", { + expectation: EXPECT_LOAD, +}); + +// variant = 2 +subsetTest(embeddingTest, + "Parent embeds cross-origin credentialless iframe", { + child_origin: REMOTE_ORIGIN, + expectation: EXPECT_LOAD, +}); + +// variant = 3 +subsetTest(embeddingTest, + "COEP:require-corp parent embeds same-origin credentialless iframe", { + parent_headers: coep_require_corp, + expectation: EXPECT_LOAD, +}); + +// variant = 4 +subsetTest(embeddingTest, + "COEP:require-corp parent embeds cross-origin credentialless iframe", { + parent_headers: coep_require_corp, + child_origin: REMOTE_ORIGIN, + expectation: EXPECT_LOAD, +}); + +// variant = 5 +subsetTest(embeddingTest, + "COEP:credentialless parent embeds same-origin credentialless iframe", { + parent_headers: coep_credentialless, + expectation: EXPECT_LOAD, +}); + +// variant = 6 +subsetTest(embeddingTest, + "COEP:credentialless parent embeds cross-origin credentialless iframe", { + parent_headers: coep_credentialless, + child_origin: REMOTE_ORIGIN, + expectation: EXPECT_LOAD, +}); + +// variant = 7 +// Regression test for https://crbug.com/1314369 +subsetTest(embeddingTest, + "COOP:same-origin + COEP:require-corp embeds same-origin credentialless iframe", { + parent_headers: coop_same_origin + coep_require_corp, + expectation: EXPECT_LOAD, +}); + +// variant = 8 +// Regression test for https://crbug.com/1314369 +subsetTest(embeddingTest, + "COOP:same-origin + COEP:require-corp embeds cross-origin credentialless iframe", { + parent_headers: coop_same_origin + coep_require_corp, + child_origin: REMOTE_ORIGIN, + expectation: EXPECT_LOAD, +}); + +// variant = 9 +// Regression test for https://crbug.com/1314369 +subsetTest(embeddingTest, + "COOP:same-origin + COEP:credentialless embeds same-origin credentialless iframe", { + parent_headers: coop_same_origin + coep_credentialless, + expectation: EXPECT_LOAD, +}); + +// variant = 10 +// Regression test for https://crbug.com/1314369 +subsetTest(embeddingTest, + "COOP:same-origin + COEP:credentialless embeds cross-origin credentialless iframe", { + parent_headers: coop_same_origin + coep_credentialless, + child_origin: REMOTE_ORIGIN, + expectation: EXPECT_LOAD, +}); + +// variant = 11 +subsetTest(embeddingTest, + "Parents embeds a CSP:frame-ancestors credentialless iframe", { + child_headers: "|header(Content-Security-Policy,frame-ancestors 'none')", + expectation: EXPECT_BLOCK, +}); + +// variant = 12 +subsetTest(embeddingTest, + "Cross-Origin-Isolated parent embeds same-origin COEP credentialless iframe", { + parent_headers: coop_same_origin + coep_require_corp, + child_headers: coop_same_origin + coep_require_corp, + expectation: EXPECT_LOAD, +}); + +// variant = 13 +subsetTest(embeddingTest, + "Cross-Origin-Isolated parent embeds cross-origin COEP credentialless iframe", { + parent_headers: coop_same_origin + coep_require_corp, + child_headers: coop_same_origin + coep_require_corp, + child_origin: REMOTE_ORIGIN, + expectation: EXPECT_LOAD, +}); diff --git a/testing/web-platform/tests/html/anonymous-iframe/fenced-frame-bypass.tentative.https.window.js b/testing/web-platform/tests/html/anonymous-iframe/fenced-frame-bypass.tentative.https.window.js new file mode 100644 index 0000000000..354abffb9c --- /dev/null +++ b/testing/web-platform/tests/html/anonymous-iframe/fenced-frame-bypass.tentative.https.window.js @@ -0,0 +1,63 @@ +// META: script=/common/get-host-info.sub.js +// META: script=/common/utils.js +// META: script=/common/dispatcher/dispatcher.js +// META: script=/html/cross-origin-embedder-policy/credentialless/resources/common.js +// META: script=./resources/common.js +// META: timeout=long + +setup(() => { + assert_implements(window.HTMLFencedFrameElement, + "HTMLFencedFrameElement is not supported."); +}) + +// 4 actors: +// A (this document) +// ┌─────────────────────┴───────┐ +// ┌─┼────────────────────────┐ D (credentialless-iframe) +// │ B (fenced-frame) │ +// │ │ │ +// │ C (credentialless-iframe)│ +// └──────────────────────────┘ +// +// This test whether the two credentialless iframe can communicate and bypass the +// fencedframe boundary. This shouldn't happen. +promise_test(async test => { + const cross_origin = get_host_info().HTTPS_REMOTE_ORIGIN; + const msg_queue = token(); + + // Create the the 3 actors. + const iframe_credentialless_1 = newIframeCredentialless(cross_origin); + const fenced_frame = newFencedFrame(cross_origin); + send(fenced_frame, ` + const importScript = ${importScript}; + await importScript("/common/utils.js"); + await importScript("/html/cross-origin-embedder-policy/credentialless" + + "/resources/common.js"); + await importScript("/html/anonymous-iframe/resources/common.js"); + const support_loading_mode_fenced_frame = + "|header(Supports-Loading-Mode,fenced-frame)"; + const iframe_credentialless_2 = newIframeCredentialless("${cross_origin}", + support_loading_mode_fenced_frame); + send("${msg_queue}", iframe_credentialless_2); + `); + const iframe_credentialless_2 = await receive(msg_queue); + + // Try to communicate using BroadCastChannel, in between the credentialless + // iframes. + const bc_key = token(); + send(iframe_credentialless_1, ` + const bc = new BroadcastChannel("${bc_key}"); + bc.onmessage = event => send("${msg_queue}", event.data); + send("${msg_queue}", "BroadcastChannel registered"); + `); + assert_equals(await receive(msg_queue), "BroadcastChannel registered"); + await send(iframe_credentialless_2, ` + const bc = new BroadcastChannel("${bc_key}"); + bc.postMessage("Can communicate"); + `); + test.step_timeout(() => { + send(msg_queue, "Cannot communicate"); + }, 4000); + + assert_equals(await receive(msg_queue), "Cannot communicate"); +}) diff --git a/testing/web-platform/tests/html/anonymous-iframe/fenced-frame.tentative.https.window.js b/testing/web-platform/tests/html/anonymous-iframe/fenced-frame.tentative.https.window.js new file mode 100644 index 0000000000..675c136606 --- /dev/null +++ b/testing/web-platform/tests/html/anonymous-iframe/fenced-frame.tentative.https.window.js @@ -0,0 +1,40 @@ +// META: script=/common/get-host-info.sub.js +// META: script=/common/utils.js +// META: script=/common/dispatcher/dispatcher.js +// META: script=/html/cross-origin-embedder-policy/credentialless/resources/common.js +// META: script=./resources/common.js +// META: timeout=long + +setup(() => { + assert_implements(window.HTMLFencedFrameElement, + "HTMLFencedFrameElement is not supported."); +}) + +// Check whether this credentialless bit propagates toward FencedFrame. It +// shouldn't. +promise_test(async test => { + const origin = get_host_info().HTTPS_ORIGIN; + const msg_queue = token(); + + // 1. Create a credentialless iframe. + const iframe_credentialless = newIframeCredentialless(origin); + + // 2. Create a FencedFrame within it. + send(iframe_credentialless, ` + const importScript = ${importScript}; + await importScript("/common/utils.js"); + await importScript("/html/cross-origin-embedder-policy/credentialless" + + "/resources/common.js"); + await importScript("/html/anonymous-iframe/resources/common.js"); + const frame_fenced = newFencedFrame("${origin}"); + send("${msg_queue}", frame_fenced); + `); + const frame_fenced = await receive(msg_queue); + + // 3. Expect it not to be considered credentialless. + send(frame_fenced, ` + send("${msg_queue}", window.credentialless); + `); + assert_equals(await receive(msg_queue), "false", + "Check window.credentialless in FencedFrame"); +}, 'FencedFrame within a credentialless iframe is not credentialless') diff --git a/testing/web-platform/tests/html/anonymous-iframe/indexeddb.tentative.https.window.js b/testing/web-platform/tests/html/anonymous-iframe/indexeddb.tentative.https.window.js new file mode 100644 index 0000000000..d2749c6be2 --- /dev/null +++ b/testing/web-platform/tests/html/anonymous-iframe/indexeddb.tentative.https.window.js @@ -0,0 +1,104 @@ +// META: timeout=long +// META: script=/common/get-host-info.sub.js +// META: script=/common/utils.js +// META: script=/common/dispatcher/dispatcher.js +// META: script=/html/cross-origin-embedder-policy/credentialless/resources/common.js +// META: script=./resources/common.js + +// "token()" is used to get unique value for every execution of the test. This +// avoids potential side effects of one run toward the second. +const g_db_store = token(); +const g_db_name = token(); +const g_db_version = 1; + +// A script storing "|id|=|value|" in IndexedDB. +const write_script = (id, value, done) => ` + // Open the database: + const request = indexedDB.open("${g_db_name}", "${g_db_version}"); + request.onupgradeneeded = () => { + request.result.createObjectStore("${g_db_store}", {keyPath: "id"}); + }; + await new Promise(r => request.onsuccess = r); + const db = request.result; + + // Write the value: + const transaction_write = db.transaction("${g_db_store}", "readwrite"); + transaction_write.objectStore("${g_db_store}").add({ + id: "${id}", + value: "${value}", + }); + await transaction_write.complete; + + db.close(); + send("${done}", "Done"); +`; + +// A script retrieving what was stored inside IndexedDB. +const read_script = (done) => ` + // Open the database: + const request = indexedDB.open("${g_db_name}", "${g_db_version}"); + await new Promise(r => request.onsuccess = r); + const db = request.result; + + // Read: + const transaction_read = db.transaction("${g_db_store}", "readonly"); + const get_all = transaction_read.objectStore("${g_db_store}").getAll(); + await new Promise(r => transaction_read.oncomplete = r); + + db.close(); + send("${done}", JSON.stringify(get_all.result)); +`; + +promise_test(async test => { + // 4 actors: 2 credentialless iframe and 2 normal iframe. + const origin = get_host_info().HTTPS_REMOTE_ORIGIN; + const iframes = [ + newIframeCredentialless(origin), + newIframeCredentialless(origin), + newIframe(origin), + newIframe(origin), + ]; + + // 1. Write a different key-value pair from the iframes in IndexedDB: + const keys = iframes.map(token); + const values = iframes.map(token); + const response_queues = iframes.map(token); + await Promise.all(iframes.map(async (_, i) => { + send(iframes[i], write_script(keys[i], values[i], response_queues[i])); + assert_equals(await receive(response_queues[i]), "Done"); + })); + + // 2. Read the state from every iframes: + const states = await Promise.all(iframes.map(async (_, i) => { + send(iframes[i], read_script(response_queues[i])); + const reply = JSON.parse(await receive(response_queues[i])); + + const state = {} + for(entry of reply) + state[entry.id] = entry.value; + return state; + })); + + + // Verify the two credentialless iframe share the same state and the normal + // iframe share a second state + assert_equals(states[0][keys[0]], values[0]); + assert_equals(states[0][keys[1]], values[1]); + assert_equals(states[0][keys[2]], undefined); + assert_equals(states[0][keys[3]], undefined); + + assert_equals(states[1][keys[0]], values[0]); + assert_equals(states[1][keys[1]], values[1]); + assert_equals(states[1][keys[2]], undefined); + assert_equals(states[1][keys[3]], undefined); + + assert_equals(states[2][keys[0]], undefined); + assert_equals(states[2][keys[1]], undefined); + assert_equals(states[2][keys[2]], values[2]); + assert_equals(states[2][keys[3]], values[3]); + + assert_equals(states[3][keys[0]], undefined); + assert_equals(states[3][keys[1]], undefined); + assert_equals(states[3][keys[2]], values[2]); + assert_equals(states[3][keys[3]], values[3]); +}) diff --git a/testing/web-platform/tests/html/anonymous-iframe/initial-empty-document.tentative.https.window.js b/testing/web-platform/tests/html/anonymous-iframe/initial-empty-document.tentative.https.window.js new file mode 100644 index 0000000000..8d8b095588 --- /dev/null +++ b/testing/web-platform/tests/html/anonymous-iframe/initial-empty-document.tentative.https.window.js @@ -0,0 +1,34 @@ +// META: script=/common/get-host-info.sub.js +// META: script=/html/cross-origin-embedder-policy/credentialless/resources/common.js + +const {ORIGIN} = get_host_info(); + +promise_test_parallel(async t => { + const parent = document.createElement("iframe"); + parent.credentialless = true; + document.body.appendChild(parent); + parent.src = ORIGIN + "/common/blank.html"; + // Wait for navigation to complete. + await new Promise(resolve => parent.onload = resolve); + assert_true(parent.credentialless); + + const child = document.createElement("iframe"); + parent.contentDocument.body.appendChild(child); + assert_false(child.credentialless); + assert_true(child.contentWindow.credentialless); +}, "Initial empty document inherits from parent's document."); + +promise_test_parallel(async t => { + const parent = document.createElement("iframe"); + document.body.appendChild(parent); + parent.src = ORIGIN + "/common/blank.html"; + // Wait for navigation to complete. + await new Promise(resolve => parent.onload = resolve); + assert_false(parent.credentialless); + + const child = document.createElement("iframe"); + child.credentialless = true; + parent.contentDocument.body.appendChild(child); + assert_true(child.credentialless); + assert_true(child.contentWindow.credentialless); +}, "Initial empty document inherits from its's iframe's credentialless attribute."); diff --git a/testing/web-platform/tests/html/anonymous-iframe/local-storage-initial-empty-document.tentative.https.window.js b/testing/web-platform/tests/html/anonymous-iframe/local-storage-initial-empty-document.tentative.https.window.js new file mode 100644 index 0000000000..37678ff12b --- /dev/null +++ b/testing/web-platform/tests/html/anonymous-iframe/local-storage-initial-empty-document.tentative.https.window.js @@ -0,0 +1,74 @@ +// META: script=/common/get-host-info.sub.js +// META: script=/common/utils.js +// META: script=/common/dispatcher/dispatcher.js +// META: script=/html/cross-origin-embedder-policy/credentialless/resources/common.js +// META: script=./resources/common.js + +// This test verifies the behavior of the initial empty document nested inside +// credentialless iframes. +// +// The following tree of frames and documents is used: +// A +// ├──B (credentialless) +// │ └──D (initial empty document) +// └──C (control) +// └──E (initial empty document) +// +// Storage used for D and E must be different. +promise_test(async test => { + const iframe_B = newIframeCredentialless(origin); + const iframe_C = newIframe(origin); + + // Create iframe_D and store a value in localStorage. + const key_D = token(); + const value_D = "value_D"; + const queue_B = token(); + send(iframe_B, ` + const iframe_D = document.createElement("iframe"); + document.body.appendChild(iframe_D); + iframe_D.contentWindow.localStorage.setItem("${key_D}","${value_D}"); + send("${queue_B}", "Done"); + `); + + // Create iframe_E and store a value in localStorage. + const key_E = token(); + const value_E = "value_E"; + const queue_C = token(); + send(iframe_C, ` + const iframe_E = document.createElement("iframe"); + document.body.appendChild(iframe_E); + iframe_E.contentWindow.localStorage.setItem("${key_E}","${value_E}"); + send("${queue_C}", "Done"); + `); + + assert_equals(await receive(queue_B), "Done"); + assert_equals(await receive(queue_C), "Done"); + + // Try to load both values from both contexts: + send(iframe_B, ` + const iframe_D = document.querySelector("iframe"); + const value_D = iframe_D.contentWindow.localStorage.getItem("${key_D}"); + const value_E = iframe_D.contentWindow.localStorage.getItem("${key_E}"); + send("${queue_B}", value_D); + send("${queue_B}", value_E); + `); + send(iframe_C, ` + const iframe_E = document.querySelector("iframe"); + const value_D = iframe_E.contentWindow.localStorage.getItem("${key_D}"); + const value_E = iframe_E.contentWindow.localStorage.getItem("${key_E}"); + send("${queue_C}", value_D); + send("${queue_C}", value_E); + `); + + // Verify the credentialless iframe and the normal one do not have access to + // each other. + assert_equals(await receive(queue_B), value_D, // key_D + "Credentialless iframe can access credentialless context"); + assert_equals(await receive(queue_B), "", // key_E + "Credentialless iframe can't access credentialled context"); + assert_equals(await receive(queue_C), "", // key_D + "Credentialled iframe can't access credentialless context"); + assert_equals(await receive(queue_C), value_E, // key_E + "Credentialled iframe can access credentialled context"); +}, "Local storage is correctly partitioned with regards to credentialless " + + "iframe in initial empty documents."); diff --git a/testing/web-platform/tests/html/anonymous-iframe/local-storage.tentative.https.window.js b/testing/web-platform/tests/html/anonymous-iframe/local-storage.tentative.https.window.js new file mode 100644 index 0000000000..bd80ff4729 --- /dev/null +++ b/testing/web-platform/tests/html/anonymous-iframe/local-storage.tentative.https.window.js @@ -0,0 +1,57 @@ +// META: script=/common/get-host-info.sub.js +// META: script=/common/utils.js +// META: script=/common/dispatcher/dispatcher.js +// META: script=/html/cross-origin-embedder-policy/credentialless/resources/common.js +// META: script=./resources/common.js + +// Make |iframe| to store |key|=|value| into LocalStorage. +const store = async (iframe, key, value) => { + const response_queue = token(); + send(iframe, ` + localStorage.setItem("${key}", "${value}"); + send("${response_queue}", "stored"); + `); + assert_equals(await receive(response_queue), "stored"); +}; + +// Make |iframe| to load |key| in LocalStorage. Check it matches the +// |expected_value|. +const load = async (iframe, key, expected_value) => { + const response_queue = token(); + send(iframe, ` + const value = localStorage.getItem("${key}"); + send("${response_queue}", value || "not found"); + `); + assert_equals(await receive(response_queue), expected_value); +}; + +promise_test(async test => { + const origin = get_host_info().HTTPS_REMOTE_ORIGIN; + const key_1 = token(); + const key_2 = token(); + + // 4 actors: 2 credentialless iframe and 2 normal iframe. + const iframe_credentialless_1 = newIframeCredentialless(origin); + const iframe_credentialless_2 = newIframeCredentialless(origin); + const iframe_normal_1 = newIframe(origin); + const iframe_normal_2 = newIframe(origin); + + // 1. Store a value in one credentialless iframe and one normal iframe. + await Promise.all([ + store(iframe_credentialless_1, key_1, "value_1"), + store(iframe_normal_1, key_2, "value_2"), + ]); + + // 2. Check what each of them can retrieve. + await Promise.all([ + load(iframe_credentialless_1, key_1, "value_1"), + load(iframe_credentialless_2, key_1, "value_1"), + load(iframe_credentialless_1, key_2, "not found"), + load(iframe_credentialless_2, key_2, "not found"), + + load(iframe_normal_1, key_1, "not found"), + load(iframe_normal_2, key_1, "not found"), + load(iframe_normal_1, key_2, "value_2"), + load(iframe_normal_2, key_2, "value_2"), + ]); +}, "Local storage is correctly partitioned with regards to credentialless iframe"); diff --git a/testing/web-platform/tests/html/anonymous-iframe/require-corp-embed-anonymous-iframe.tentative.https.window.js b/testing/web-platform/tests/html/anonymous-iframe/require-corp-embed-anonymous-iframe.tentative.https.window.js new file mode 100644 index 0000000000..ccaa41f9ef --- /dev/null +++ b/testing/web-platform/tests/html/anonymous-iframe/require-corp-embed-anonymous-iframe.tentative.https.window.js @@ -0,0 +1,49 @@ +// META: script=/common/utils.js + +promise_test(async t => { + let iframe_allowed = (iframe) => new Promise(async resolve => { + window.addEventListener("message", t.step_func(msg => { + if (msg.source !== iframe.contentWindow) return; + assert_equals(msg.data, "loaded", + "Unexpected message from broadcast channel."); + resolve(true); + })); + + // To see whether the iframe was blocked, we check whether it + // becomes cross-origin (since error pages are loaded cross-origin). + await t.step_wait(() => { + try { + // Accessing contentWindow.location.href cross-origin throws. + iframe.contentWindow.location.href === null; + return false; + } catch { + return true; + } + }); + resolve(false); + }); + + // Create a credentialless child iframe. + const child = document.createElement("iframe"); + child.credentialless = true; + t.add_cleanup(() => child.remove()); + + child.src = "/html/cross-origin-embedder-policy/resources/" + + "navigate-none.sub.html?postMessageTo=top"; + document.body.append(child); + + assert_true(await iframe_allowed(child), + "The credentialless iframe should be allowed."); + + // Create a child of the credentialless iframe. Even if the grandchild + // does not have the 'credentialless' attribute set, it inherits the + // credentialless property from the parent. + const grandchild = child.contentDocument.createElement("iframe"); + + grandchild.src = "/html/cross-origin-embedder-policy/resources/" + + "navigate-none.sub.html?postMessageTo=top"; + child.contentDocument.body.append(grandchild); + + assert_true(await iframe_allowed(grandchild), + "The child of the credentialless iframe should be allowed."); +}, 'Loading a credentialless iframe with COEP: require-corp is allowed.'); diff --git a/testing/web-platform/tests/html/anonymous-iframe/require-corp-embed-anonymous-iframe.tentative.https.window.js.headers b/testing/web-platform/tests/html/anonymous-iframe/require-corp-embed-anonymous-iframe.tentative.https.window.js.headers new file mode 100644 index 0000000000..6604450991 --- /dev/null +++ b/testing/web-platform/tests/html/anonymous-iframe/require-corp-embed-anonymous-iframe.tentative.https.window.js.headers @@ -0,0 +1 @@ +Cross-Origin-Embedder-Policy: require-corp diff --git a/testing/web-platform/tests/html/anonymous-iframe/resources/common.js b/testing/web-platform/tests/html/anonymous-iframe/resources/common.js new file mode 100644 index 0000000000..241df1df24 --- /dev/null +++ b/testing/web-platform/tests/html/anonymous-iframe/resources/common.js @@ -0,0 +1,56 @@ +// Create a credentialless iframe. The new document will execute any scripts +// sent toward the token it returns. +const newIframeCredentialless = (child_origin, opt_headers) => { + opt_headers ||= ""; + const sub_document_token = token(); + let iframe = document.createElement('iframe'); + iframe.src = child_origin + executor_path + opt_headers + + `&uuid=${sub_document_token}`; + iframe.credentialless = true; + document.body.appendChild(iframe); + return sub_document_token; +}; + +// Create a normal iframe. The new document will execute any scripts sent +// toward the token it returns. +const newIframe = (child_origin) => { + const sub_document_token = token(); + let iframe = document.createElement('iframe'); + iframe.src = child_origin + executor_path + `&uuid=${sub_document_token}`; + iframe.credentialless = false + document.body.appendChild(iframe); + return sub_document_token; +}; + +// Create a popup. The new document will execute any scripts sent toward the +// token it returns. +const newPopup = (test, origin) => { + const popup_token = token(); + const popup = window.open(origin + executor_path + `&uuid=${popup_token}`); + test.add_cleanup(() => popup.close()); + return popup_token; +} + +// Create a fenced frame. The new document will execute any scripts sent +// toward the token it returns. +const newFencedFrame = (child_origin) => { + const support_loading_mode_fenced_frame = + "|header(Supports-Loading-Mode,fenced-frame)"; + const sub_document_token = token(); + const fencedframe = document.createElement('fencedframe'); + const url = child_origin + executor_path + + support_loading_mode_fenced_frame + + `&uuid=${sub_document_token}`; + fencedframe.config = new FencedFrameConfig(url); + document.body.appendChild(fencedframe); + return sub_document_token; +}; + +const importScript = (url) => { + const script = document.createElement("script"); + script.type = "text/javascript"; + script.src = url; + const loaded = new Promise(resolve => script.onload = resolve); + document.body.appendChild(script); + return loaded; +} diff --git a/testing/web-platform/tests/html/anonymous-iframe/resources/embedding-test.js b/testing/web-platform/tests/html/anonymous-iframe/resources/embedding-test.js new file mode 100644 index 0000000000..fb5d11efa0 --- /dev/null +++ b/testing/web-platform/tests/html/anonymous-iframe/resources/embedding-test.js @@ -0,0 +1,72 @@ +// One document embeds another in an iframe. Both are loaded from the network. +// Check whether or not the child can load. + +// There are no interoperable ways to check an iframe failed to load. So a +// timeout is being used. See https://github.com/whatwg/html/issues/125 +// Moreover, we want to track progress, managing timeout explicitly allows to +// get a per-test results, even in case of failure of one. +setup({ explicit_timeout: true }); + +const EXPECT_LOAD = "load"; +const EXPECT_BLOCK = "block"; + +// Load a credentialless iframe. Control both the parent and the child headers. +// Check whether it loaded or not. +const embeddingTest = (description, { + parent_headers, + child_headers, + child_origin, + expectation, +}) => { + // Default values: + child_origin ||= globalThis.origin; + parent_headers ||= ""; + child_headers||= ""; + + const parent_origin = window.origin; + + promise_test_parallel(async test => { + const parent_token = token(); + const parent_url = parent_origin + executor_path + parent_headers + + `&uuid=${parent_token}`; + + const child_token = token(); + const child_url = child_origin + executor_path + child_headers + + `&uuid=${child_token}`; + + // Create the parent: + window.open(parent_url); + add_completion_callback(() => send(parent_token, "close()")); + + // The parent creates its child: + await send(parent_token, ` + const iframe = document.createElement("iframe"); + iframe.credentialless = true; + iframe.src = "${child_url}"; + document.body.appendChild(iframe); + `); + + // Ping the child to know whether it was allowed to load or not: + const reply_token = token(); + await send(child_token, ` + send("${reply_token}", "load"); + `); + + // There are no interoperable ways to check an iframe failed to load. So a + // timeout is being used. + // See https://github.com/whatwg/html/issues/125 + // Use a shorter timeout when it is expected to be reached. + // - The long delay reduces the false-positive rate. False-positive causes + // stability problems on bot, so a big delay is used to vanish them. + // https://crbug.com/1215956. + // - The short delay avoids delaying too much the test(s) for nothing and + // timing out. False-negative are not a problem, they just need not to + // overwhelm the true-negative, which is trivial to get. + step_timeout(() => send(reply_token, "block"), expectation == EXPECT_BLOCK + ? 1500 + : 3500 + ); + + assert_equals(await receive(reply_token), expectation); + }, description); +}; diff --git a/testing/web-platform/tests/html/anonymous-iframe/resources/serviceworker-partitioning-helper.js b/testing/web-platform/tests/html/anonymous-iframe/resources/serviceworker-partitioning-helper.js new file mode 100644 index 0000000000..288ad5954e --- /dev/null +++ b/testing/web-platform/tests/html/anonymous-iframe/resources/serviceworker-partitioning-helper.js @@ -0,0 +1,16 @@ +let messages = {}; +let ports = {}; + +self.addEventListener("message", e => { + const from = e.data.from; + const check = e.data.check; + + if (from) { + messages[from] = true; + ports[from] = e.ports[0]; + } + + if (check) { + ports[check].postMessage(messages); + } +}); diff --git a/testing/web-platform/tests/html/anonymous-iframe/resources/sharedworker-partitioning-helper.js b/testing/web-platform/tests/html/anonymous-iframe/resources/sharedworker-partitioning-helper.js new file mode 100644 index 0000000000..8b416c33d7 --- /dev/null +++ b/testing/web-platform/tests/html/anonymous-iframe/resources/sharedworker-partitioning-helper.js @@ -0,0 +1,21 @@ +let messages = {}; + +onconnect = function(e) { + let port = e.ports[0]; + + port.addEventListener('message', function(e) { + const action = e.data.action; + const from = e.data.from; + + if (action === 'record') { + messages[from] = true; + port.postMessage({ack: from}); + } + + if (action === 'retrieve') { + port.postMessage({ack: from, messages: messages}); + } + }); + + port.start(); +}; diff --git a/testing/web-platform/tests/html/anonymous-iframe/serviceworker-partitioning.tentative.https.window.js b/testing/web-platform/tests/html/anonymous-iframe/serviceworker-partitioning.tentative.https.window.js new file mode 100644 index 0000000000..14997a4754 --- /dev/null +++ b/testing/web-platform/tests/html/anonymous-iframe/serviceworker-partitioning.tentative.https.window.js @@ -0,0 +1,85 @@ +// META: script=/common/utils.js + +const sw_url = location.pathname.replace(/[^/]*$/, '') + + "./resources/serviceworker-partitioning-helper.js"; + +promise_test(async t => { + // Create 4 iframes (two normal and two credentialless ones) and register + // a serviceworker with the same scope and url in all of them. + // + // Registering the same service worker again with the same url and + // scope is a no-op. However, credentialless iframes get partitioned + // service workers, so we should have a total of 2 service workers + // at the end (one for the normal iframes and one for the credentialless + // ones). + let iframes = await Promise.all([ + { name: "normal", credentialless: false}, + { name: "normal_control", credentialless: false}, + { name: "credentialless", credentialless: true}, + { name: "credentialless_control", credentialless: true}, + ].map(async ({name, credentialless}) => { + + let iframe = await new Promise(resolve => { + let iframe = document.createElement('iframe'); + iframe.onload = () => resolve(iframe); + iframe.src = '/common/blank.html'; + if (credentialless) iframe.credentialless = true; + document.body.append(iframe); + }); + + let sw = await new Promise(resolve => { + iframe.contentWindow.navigator.serviceWorker.register(sw_url) + .then(r => { + add_completion_callback(_ => r.unregister()); + resolve(r.active || r.installing || r.waiting); + }); + }); + return { iframe: iframe, name: name, sw: sw }; + })); + + // Setup a MessageChannel for each pair (iframe, serviceworker). + // Ping each serviceworker telling him which iframe it belongs to. + iframes.forEach((iframe, i) => { + iframe.channel = new MessageChannel(); + iframe.sw.postMessage({ from: iframe.name }, [iframe.channel.port2]); + }); + + let msg_promises = iframes.map(iframe => new Promise(resolve => { + iframe.channel.port1.onmessage = event => resolve(event.data); + })); + + // Ping each (iframe, serviceworker) asking for which messages it got. + iframes.map(iframe => iframe.sw.postMessage({ check: iframe.name })); + + // Collect all replies. + let msgs = await Promise.all(msg_promises); + + // The "normal" iframe serviceworker belongs to the "normal" and the + // "normal_control" iframes. + assert_true(!!msgs[0]["normal"]); + assert_true(!!msgs[0]["normal_control"]); + assert_false(!!msgs[0]["credentialless"]); + assert_false(!!msgs[0]["credentialless_control"]); + + // The "normal_control" iframe shares the same serviceworker as the "normal" + // iframe. + assert_true(!!msgs[1]["normal"]); + assert_true(!!msgs[1]["normal_control"]); + assert_false(!!msgs[1]["credentialless"]); + assert_false(!!msgs[1]["credentialless_control"]); + + // The "credentialless" iframe serviceworker belongs to the "credentialless" + // and the "credentialless_control" iframes. + assert_false(!!msgs[2]["normal"]); + assert_false(!!msgs[2]["normal_control"]); + assert_true(!!msgs[2]["credentialless"]); + assert_true(!!msgs[2]["credentialless_control"]); + + // The "credentialless_control" iframe shares the same serviceworker as the + // "credentialless" iframe. + assert_false(!!msgs[3]["normal"]); + assert_false(!!msgs[3]["normal_control"]); + assert_true(!!msgs[3]["credentialless"]); + assert_true(!!msgs[3]["credentialless_control"]); + +}, "credentialless iframes get partitioned service workers."); diff --git a/testing/web-platform/tests/html/anonymous-iframe/session-storage.tentative.https.window.js b/testing/web-platform/tests/html/anonymous-iframe/session-storage.tentative.https.window.js new file mode 100644 index 0000000000..425886ce38 --- /dev/null +++ b/testing/web-platform/tests/html/anonymous-iframe/session-storage.tentative.https.window.js @@ -0,0 +1,57 @@ +// META: script=/common/get-host-info.sub.js +// META: script=/common/utils.js +// META: script=/common/dispatcher/dispatcher.js +// META: script=/html/cross-origin-embedder-policy/credentialless/resources/common.js +// META: script=./resources/common.js + +// Make |iframe| to store |key|=|value| into sessionStorage. +const store = async (iframe, key, value) => { + const response_queue = token(); + send(iframe, ` + sessionStorage.setItem("${key}", "${value}"); + send("${response_queue}", "stored"); + `); + assert_equals(await receive(response_queue), "stored"); +}; + +// Make |iframe| to load |key| in sessionStorage. Check it matches the +// |expected_value|. +const load = async (iframe, key, expected_value) => { + const response_queue = token(); + send(iframe, ` + const value = sessionStorage.getItem("${key}"); + send("${response_queue}", value || "not found"); + `); + assert_equals(await receive(response_queue), expected_value); +}; + +promise_test(async test => { + const origin = get_host_info().HTTPS_REMOTE_ORIGIN; + const key_1 = token(); + const key_2 = token(); + + // 4 actors: 2 credentialless iframe and 2 normal iframe. + const iframe_credentialless_1 = newIframeCredentialless(origin); + const iframe_credentialless_2 = newIframeCredentialless(origin); + const iframe_normal_1 = newIframe(origin); + const iframe_normal_2 = newIframe(origin); + + // 1. Store a value in one credentialless iframe and one normal iframe. + await Promise.all([ + store(iframe_credentialless_1, key_1, "value_1"), + store(iframe_normal_1, key_2, "value_2"), + ]); + + // 2. Check what each of them can retrieve. + await Promise.all([ + load(iframe_credentialless_1, key_1, "value_1"), + load(iframe_credentialless_2, key_1, "value_1"), + load(iframe_credentialless_1, key_2, "not found"), + load(iframe_credentialless_2, key_2, "not found"), + + load(iframe_normal_1, key_1, "not found"), + load(iframe_normal_2, key_1, "not found"), + load(iframe_normal_1, key_2, "value_2"), + load(iframe_normal_2, key_2, "value_2"), + ]); +}, "Session storage is correctly partitioned with regards to credentialless iframe"); diff --git a/testing/web-platform/tests/html/anonymous-iframe/sharedworker-partitioning.tentative.https.window.js b/testing/web-platform/tests/html/anonymous-iframe/sharedworker-partitioning.tentative.https.window.js new file mode 100644 index 0000000000..6c373ee490 --- /dev/null +++ b/testing/web-platform/tests/html/anonymous-iframe/sharedworker-partitioning.tentative.https.window.js @@ -0,0 +1,93 @@ +// META: script=/common/utils.js + +const sw_url = location.pathname.replace(/[^/]*$/, '') + + "./resources/sharedworker-partitioning-helper.js"; + +promise_test(async t => { + // Create 4 iframes (two normal and two credentialless ones) and create + // a shared worker with the same url in all of them. + // + // Creating the same shared worker again with the same url is a + // no-op. However, credentialless iframes get partitioned shared workers, + // so we should have a total of 2 shared workers at the end (one for + // the normal iframes and one for the credentialless ones). + let iframes = await Promise.all([ + { name: "normal", credentialless: false}, + { name: "normal_control", credentialless: false}, + { name: "credentialless", credentialless: true}, + { name: "credentialless_control", credentialless: true}, + ].map(async ({name, credentialless}) => { + + let iframe = await new Promise(resolve => { + let iframe = document.createElement('iframe'); + iframe.onload = () => resolve(iframe); + iframe.src = '/common/blank.html'; + if (credentialless) iframe.credentialless = true; + document.body.append(iframe); + }); + + let sw = new iframe.contentWindow.SharedWorker(sw_url); + return { iframe: iframe, name: name, sw: sw }; + })); + + // Ping each worker telling him which iframe it belongs to. + await Promise.all(iframes.map(iframe => { + iframe.sw.port.postMessage({ action: 'record', from: iframe.name}); + return new Promise(resolve => { + iframe.sw.port.onmessage = event => { + if (event.data.ack === iframe.name) resolve(); + } + }); + })); + + // Ping each (iframe, sharedworker) asking for which messages it got. + let msgs = await Promise.all(iframes.map(iframe => { + iframe.sw.port.postMessage({ action: 'retrieve', from: iframe.name }); + return new Promise(resolve => { + iframe.sw.port.onmessage = event => { + if (event.data.ack === iframe.name) resolve(event.data.messages); + } + }); + })); + + // The "normal" iframe sharedworker belongs to the "normal" and the + // "normal_control" iframes. + assert_true(!!msgs[0]["normal"] && + !!msgs[0]["normal_control"] && + !msgs[0]["credentialless"] && + !msgs[0]["credentialless_control"], + 'The "normal" iframe\'s sharedworker should return ' + + '{"normal": true, "normal_control": true}, ' + + 'but instead returned ' + JSON.stringify(msgs[0])); + + // The "normal_control" iframe shares the same sharedworker as the "normal" + // iframe. + assert_true(!!msgs[1]["normal"] && + !!msgs[1]["normal_control"] && + !msgs[1]["credentialless"] && + !msgs[1]["credentialless_control"], + 'The "normal_control" iframe\'s sharedworker should return ' + + '{"normal": true, "normal_control": true}, ' + + 'but instead returned ' + JSON.stringify(msgs[1])); + + // The "credentialless" iframe sharedworker belongs to the "credentialless" and the + // "credentialless_control" iframes. + assert_true(!msgs[2]["normal"] && + !msgs[2]["normal_control"] && + !!msgs[2]["credentialless"] && + !!msgs[2]["credentialless_control"], + 'The "credentialless" iframe\'s sharedworker should return ' + + '{"credentialless": true, "credentialless_control": true}, ' + + 'but instead returned ' + JSON.stringify(msgs[2])); + + // The "credentialless_control" iframe shares the same sharedworker as + // the "credentialless" iframe. + assert_true(!msgs[3]["normal"] && + !msgs[3]["normal_control"] && + !!msgs[3]["credentialless"] && + !!msgs[3]["credentialless_control"], + 'The "credentialless_control" iframe\'s sharedworker should return ' + + '{"credentialless": true, "credentialless_control": true}, ' + + 'but instead returned ' + JSON.stringify(msgs[3])); + +}, "credentialless iframes get partitioned shared workers."); diff --git a/testing/web-platform/tests/html/anonymous-iframe/web-lock.tentative.https.window.js b/testing/web-platform/tests/html/anonymous-iframe/web-lock.tentative.https.window.js new file mode 100644 index 0000000000..fbee4ad28e --- /dev/null +++ b/testing/web-platform/tests/html/anonymous-iframe/web-lock.tentative.https.window.js @@ -0,0 +1,75 @@ +// META: script=/common/get-host-info.sub.js +// META: script=/common/utils.js +// META: script=/common/dispatcher/dispatcher.js +// META: script=/html/cross-origin-embedder-policy/credentialless/resources/common.js +// META: script=./resources/common.js + +// A script acquiring a lock. It can be released using window.releaseLocks +const acquire_script = (key, response) => ` + window.releaseLocks ||= []; + navigator.locks.request("${key}", async lock => { + send("${response}", "locked") + await new Promise(r => releaseLocks.push(r)); + send("${response}", "unlocked"); + }); +`; + +const release_script = (response) => ` + for (release of releaseLocks) + release(); +`; + +// Assert that |context| holds |expected_keys|. +const assertHeldKeys = async (context, expected_keys) => { + const queue = token(); + send(context, ` + const list = await navigator.locks.query(); + send("${queue}", JSON.stringify(list)); + `); + const state = JSON.parse(await receive(queue)); + const held = state.held.map(x => x.name); + assert_equals(held.length, expected_keys.length); + assert_array_equals(held.sort(), expected_keys.sort()); +} + +promise_test(async test => { + const origin = get_host_info().HTTPS_REMOTE_ORIGIN; + const key_1 = token(); + const key_2 = token(); + + // 2 actors: A credentialless iframe and a normal one. + const iframe_credentialless = newIframeCredentialless(origin); + const iframe_normal = newIframe(origin); + const response_queue_1 = token(); + const response_queue_2 = token(); + + // 1. Hold two different locks on both sides. + send(iframe_credentialless, acquire_script(key_1, response_queue_1)); + send(iframe_normal, acquire_script(key_2, response_queue_2)); + assert_equals(await receive(response_queue_1), "locked"); + assert_equals(await receive(response_queue_2), "locked"); + await assertHeldKeys(iframe_credentialless, [key_1]); + await assertHeldKeys(iframe_normal, [key_2]); + + // 2. Try to acquire the lock with the same key on the opposite side. It + // shouldn't block, because they are partitioned. + send(iframe_credentialless , acquire_script(key_2, response_queue_1)); + send(iframe_normal, acquire_script(key_1, response_queue_2)); + assert_equals(await receive(response_queue_1), "locked"); + assert_equals(await receive(response_queue_2), "locked"); + await assertHeldKeys(iframe_credentialless, [key_1, key_2]); + await assertHeldKeys(iframe_normal, [key_1, key_2]); + + // 3. Cleanup: release the 4 locks (2 on each sides). + send(iframe_credentialless, release_script(response_queue_1)); + assert_equals(await receive(response_queue_1), "unlocked"); + assert_equals(await receive(response_queue_1), "unlocked"); + await assertHeldKeys(iframe_credentialless, []); + await assertHeldKeys(iframe_normal, [key_1, key_2]); + + send(iframe_normal, release_script(response_queue_2)); + assert_equals(await receive(response_queue_2), "unlocked"); + assert_equals(await receive(response_queue_2), "unlocked"); + await assertHeldKeys(iframe_credentialless, []); + await assertHeldKeys(iframe_normal, []); +}) diff --git a/testing/web-platform/tests/html/anonymous-iframe/worker-cookies.tentative.https.window.js b/testing/web-platform/tests/html/anonymous-iframe/worker-cookies.tentative.https.window.js new file mode 100644 index 0000000000..8c25306baf --- /dev/null +++ b/testing/web-platform/tests/html/anonymous-iframe/worker-cookies.tentative.https.window.js @@ -0,0 +1,70 @@ +// META: timeout=long +// META: variant=?worker=dedicated_worker +// META: variant=?worker=shared_worker +// META: variant=?worker=service_worker +// META: script=/common/get-host-info.sub.js +// META: script=/common/utils.js +// META: script=/common/dispatcher/dispatcher.js +// META: script=/html/cross-origin-embedder-policy/credentialless/resources/common.js +// META: script=./resources/common.js + +// Execute the same set of tests for every type of worker. +// - DedicatedWorkers +// - SharedWorkers +// - ServiceWorkers. +const params = new URLSearchParams(document.location.search); +const worker_param = params.get("worker") || "dedicated_worker"; + +const cookie_key = token(); +const cookie_value = "cookie_value"; +const cookie_origin = get_host_info().HTTPS_REMOTE_ORIGIN; + +// Create worker spawned from `context` and return its uuid. +const workerFrom = context => { + const reply = token(); + send(context, ` + for(deps of [ + "/common/utils.js", + "/resources/testharness.js", + "/html/cross-origin-embedder-policy/credentialless/resources/common.js", + ]) { + await new Promise(resolve => { + const script = document.createElement("script"); + script.src = deps; + script.onload = resolve; + document.body.appendChild(script); + }); + } + + const worker_constructor = environments["${worker_param}"]; + const headers = ""; + const [worker, error] = worker_constructor(headers); + send("${reply}", worker); + `); + return receive(reply); +}; + +// Set a cookie from a top-level document. +promise_test(async test => { + await setCookie(cookie_origin, cookie_key, cookie_value); +}, "set cookies"); + +// Control: iframe is not credentialless. The worker can access cookies. +promise_test(async test => { + const headers = token(); + send(await workerFrom(newIframe(cookie_origin)), ` + fetch("${showRequestHeaders(cookie_origin, headers)}"); + `); + const cookie = parseCookies(JSON.parse(await receive(headers))); + assert_equals(cookie[cookie_key], cookie_value) +}, "Worker spawned from normal iframe can access global cookies"); + +// Experiment: iframe is credentialless. +promise_test(async test => { + const headers = token(); + send(await workerFrom(newIframeCredentialless(cookie_origin)), ` + fetch("${showRequestHeaders(cookie_origin, headers)}"); + `); + const cookie = parseCookies(JSON.parse(await receive(headers))); + assert_equals(cookie[cookie_key], undefined) +}, "Worker spawned from credentialless iframe can't access global cookies"); |