summaryrefslogtreecommitdiffstats
path: root/testing/web-platform/tests/html/cross-origin-embedder-policy
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
commit26a029d407be480d791972afb5975cf62c9360a6 (patch)
treef435a8308119effd964b339f76abb83a57c29483 /testing/web-platform/tests/html/cross-origin-embedder-policy
parentInitial commit. (diff)
downloadfirefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz
firefox-26a029d407be480d791972afb5975cf62c9360a6.zip
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'testing/web-platform/tests/html/cross-origin-embedder-policy')
-rw-r--r--testing/web-platform/tests/html/cross-origin-embedder-policy/META.yml9
-rw-r--r--testing/web-platform/tests/html/cross-origin-embedder-policy/README.md1
-rw-r--r--testing/web-platform/tests/html/cross-origin-embedder-policy/about-blank-popup.https.html59
-rw-r--r--testing/web-platform/tests/html/cross-origin-embedder-policy/about-blank-popup.https.html.headers1
-rw-r--r--testing/web-platform/tests/html/cross-origin-embedder-policy/blob.https.html44
-rw-r--r--testing/web-platform/tests/html/cross-origin-embedder-policy/blob.https.html.headers1
-rw-r--r--testing/web-platform/tests/html/cross-origin-embedder-policy/block-local-documents-inheriting-none.https.html112
-rw-r--r--testing/web-platform/tests/html/cross-origin-embedder-policy/cache-storage-reporting-dedicated-worker.https.html51
-rw-r--r--testing/web-platform/tests/html/cross-origin-embedder-policy/cache-storage-reporting-document.https.html58
-rw-r--r--testing/web-platform/tests/html/cross-origin-embedder-policy/cache-storage-reporting-service-worker.https.html64
-rw-r--r--testing/web-platform/tests/html/cross-origin-embedder-policy/cache-storage-reporting-shared-worker.https.html49
-rw-r--r--testing/web-platform/tests/html/cross-origin-embedder-policy/coep-frame-javascript.https.html25
-rw-r--r--testing/web-platform/tests/html/cross-origin-embedder-policy/coep-on-response-from-service-worker.https.html111
-rw-r--r--testing/web-platform/tests/html/cross-origin-embedder-policy/credentialless/META.yml7
-rw-r--r--testing/web-platform/tests/html/cross-origin-embedder-policy/credentialless/README.md3
-rw-r--r--testing/web-platform/tests/html/cross-origin-embedder-policy/credentialless/cache-storage.https.window.js150
-rw-r--r--testing/web-platform/tests/html/cross-origin-embedder-policy/credentialless/cache.window.js84
-rw-r--r--testing/web-platform/tests/html/cross-origin-embedder-policy/credentialless/cross-origin-isolated.window.js49
-rw-r--r--testing/web-platform/tests/html/cross-origin-embedder-policy/credentialless/dedicated-worker.https.window.js137
-rw-r--r--testing/web-platform/tests/html/cross-origin-embedder-policy/credentialless/fetch.https.window.js127
-rw-r--r--testing/web-platform/tests/html/cross-origin-embedder-policy/credentialless/iframe-coep-credentialless.https.window.js37
-rw-r--r--testing/web-platform/tests/html/cross-origin-embedder-policy/credentialless/iframe-coep-none.https.window.js22
-rw-r--r--testing/web-platform/tests/html/cross-origin-embedder-policy/credentialless/iframe-coep-redirect.https.window.js41
-rw-r--r--testing/web-platform/tests/html/cross-origin-embedder-policy/credentialless/iframe-coep-require-corp.https.window.js38
-rw-r--r--testing/web-platform/tests/html/cross-origin-embedder-policy/credentialless/iframe.window.js47
-rw-r--r--testing/web-platform/tests/html/cross-origin-embedder-policy/credentialless/image.https.window.js97
-rw-r--r--testing/web-platform/tests/html/cross-origin-embedder-policy/credentialless/link.https.window.js99
-rw-r--r--testing/web-platform/tests/html/cross-origin-embedder-policy/credentialless/redirect.window.js55
-rw-r--r--testing/web-platform/tests/html/cross-origin-embedder-policy/credentialless/reporting-navigation.https.window.js139
-rw-r--r--testing/web-platform/tests/html/cross-origin-embedder-policy/credentialless/reporting-subresource-corp.https.window.js74
-rw-r--r--testing/web-platform/tests/html/cross-origin-embedder-policy/credentialless/resources/common.js134
-rw-r--r--testing/web-platform/tests/html/cross-origin-embedder-policy/credentialless/resources/iframeTest.js85
-rw-r--r--testing/web-platform/tests/html/cross-origin-embedder-policy/credentialless/resources/redirect_none_to_credentialless.py14
-rw-r--r--testing/web-platform/tests/html/cross-origin-embedder-policy/credentialless/script.https.window.js99
-rw-r--r--testing/web-platform/tests/html/cross-origin-embedder-policy/credentialless/service-worker-coep-credentialless-proxy.https.window.js85
-rw-r--r--testing/web-platform/tests/html/cross-origin-embedder-policy/credentialless/service-worker-coep-none-proxy.https.window.js87
-rw-r--r--testing/web-platform/tests/html/cross-origin-embedder-policy/credentialless/service-worker.https.window.js113
-rw-r--r--testing/web-platform/tests/html/cross-origin-embedder-policy/credentialless/shared-worker.https.window.js93
-rw-r--r--testing/web-platform/tests/html/cross-origin-embedder-policy/credentialless/video.https.window.js53
-rw-r--r--testing/web-platform/tests/html/cross-origin-embedder-policy/credentialless/video.https.window.js.headers1
-rw-r--r--testing/web-platform/tests/html/cross-origin-embedder-policy/credentialless/websocket.https.window.js77
-rw-r--r--testing/web-platform/tests/html/cross-origin-embedder-policy/cross-origin-isolated-permission-iframe.https.window.js74
-rw-r--r--testing/web-platform/tests/html/cross-origin-embedder-policy/cross-origin-isolated-permission-iframe.https.window.js.headers2
-rw-r--r--testing/web-platform/tests/html/cross-origin-embedder-policy/cross-origin-isolated-permission-worker.https.window.js170
-rw-r--r--testing/web-platform/tests/html/cross-origin-embedder-policy/cross-origin-isolated-permission-worker.https.window.js.headers2
-rw-r--r--testing/web-platform/tests/html/cross-origin-embedder-policy/data.https.html20
-rw-r--r--testing/web-platform/tests/html/cross-origin-embedder-policy/data.https.html.headers1
-rw-r--r--testing/web-platform/tests/html/cross-origin-embedder-policy/dedicated-worker-cache-storage.https.html128
-rw-r--r--testing/web-platform/tests/html/cross-origin-embedder-policy/dedicated-worker.https.html214
-rw-r--r--testing/web-platform/tests/html/cross-origin-embedder-policy/header-parsing.https.html85
-rw-r--r--testing/web-platform/tests/html/cross-origin-embedder-policy/iframe-history-none-require-corp.https.html54
-rw-r--r--testing/web-platform/tests/html/cross-origin-embedder-policy/javascript.https.html21
-rw-r--r--testing/web-platform/tests/html/cross-origin-embedder-policy/javascript.https.html.headers1
-rw-r--r--testing/web-platform/tests/html/cross-origin-embedder-policy/meta-http-equiv.https.html20
-rw-r--r--testing/web-platform/tests/html/cross-origin-embedder-policy/multi-globals/current/current.html3
-rw-r--r--testing/web-platform/tests/html/cross-origin-embedder-policy/multi-globals/current/current.html.headers1
-rw-r--r--testing/web-platform/tests/html/cross-origin-embedder-policy/multi-globals/current/worker.js1
-rw-r--r--testing/web-platform/tests/html/cross-origin-embedder-policy/multi-globals/incumbent/incumbent.html14
-rw-r--r--testing/web-platform/tests/html/cross-origin-embedder-policy/multi-globals/incumbent/incumbent.html.headers1
-rw-r--r--testing/web-platform/tests/html/cross-origin-embedder-policy/multi-globals/incumbent/worker.js1
-rw-r--r--testing/web-platform/tests/html/cross-origin-embedder-policy/multi-globals/worker.js1
-rw-r--r--testing/web-platform/tests/html/cross-origin-embedder-policy/multi-globals/workers-coep-report.https.html49
-rw-r--r--testing/web-platform/tests/html/cross-origin-embedder-policy/multi-globals/workers-coep-report.https.html.headers1
-rw-r--r--testing/web-platform/tests/html/cross-origin-embedder-policy/no-secure-context.html19
-rw-r--r--testing/web-platform/tests/html/cross-origin-embedder-policy/no-secure-context.html.headers1
-rw-r--r--testing/web-platform/tests/html/cross-origin-embedder-policy/non-initial-about-blank.https.html21
-rw-r--r--testing/web-platform/tests/html/cross-origin-embedder-policy/non-initial-about-blank.https.html.headers1
-rw-r--r--testing/web-platform/tests/html/cross-origin-embedder-policy/none-load-from-cache-storage.https.html173
-rw-r--r--testing/web-platform/tests/html/cross-origin-embedder-policy/none-sw-from-none.https.html89
-rw-r--r--testing/web-platform/tests/html/cross-origin-embedder-policy/none-sw-from-require-corp.https.html93
-rw-r--r--testing/web-platform/tests/html/cross-origin-embedder-policy/none-sw-from-require-corp.https.html.headers1
-rw-r--r--testing/web-platform/tests/html/cross-origin-embedder-policy/none.https.html91
-rw-r--r--testing/web-platform/tests/html/cross-origin-embedder-policy/none.https.html.headers1
-rw-r--r--testing/web-platform/tests/html/cross-origin-embedder-policy/reflection-credentialless.tentative.https.any.js2
-rw-r--r--testing/web-platform/tests/html/cross-origin-embedder-policy/reflection-credentialless.tentative.https.any.js.headers1
-rw-r--r--testing/web-platform/tests/html/cross-origin-embedder-policy/reflection-require-corp.tentative.https.any.js2
-rw-r--r--testing/web-platform/tests/html/cross-origin-embedder-policy/reflection-require-corp.tentative.https.any.js.headers1
-rw-r--r--testing/web-platform/tests/html/cross-origin-embedder-policy/reflection-unsafe-none.tentative.https.any.js2
-rw-r--r--testing/web-platform/tests/html/cross-origin-embedder-policy/report-only-require-corp.https.html86
-rw-r--r--testing/web-platform/tests/html/cross-origin-embedder-policy/report-only-require-corp.https.html.headers1
-rw-r--r--testing/web-platform/tests/html/cross-origin-embedder-policy/reporting-navigation.https.html170
-rw-r--r--testing/web-platform/tests/html/cross-origin-embedder-policy/reporting-subresource-corp.https.html206
-rw-r--r--testing/web-platform/tests/html/cross-origin-embedder-policy/reporting-to-document-reporting-endpoint.https.window.js140
-rw-r--r--testing/web-platform/tests/html/cross-origin-embedder-policy/reporting-to-endpoint.https.html209
-rw-r--r--testing/web-platform/tests/html/cross-origin-embedder-policy/reporting-to-endpoint.https.html.sub.headers1
-rw-r--r--testing/web-platform/tests/html/cross-origin-embedder-policy/reporting-to-frame-owner.https.html87
-rw-r--r--testing/web-platform/tests/html/cross-origin-embedder-policy/reporting-to-worker-owner.https.html89
-rw-r--r--testing/web-platform/tests/html/cross-origin-embedder-policy/require-corp-about-blank.https.html48
-rw-r--r--testing/web-platform/tests/html/cross-origin-embedder-policy/require-corp-about-blank.https.html.headers1
-rw-r--r--testing/web-platform/tests/html/cross-origin-embedder-policy/require-corp-about-srcdoc.https.html48
-rw-r--r--testing/web-platform/tests/html/cross-origin-embedder-policy/require-corp-about-srcdoc.https.html.headers1
-rw-r--r--testing/web-platform/tests/html/cross-origin-embedder-policy/require-corp-cached-images.https.html74
-rw-r--r--testing/web-platform/tests/html/cross-origin-embedder-policy/require-corp-cached-images.https.html.headers1
-rw-r--r--testing/web-platform/tests/html/cross-origin-embedder-policy/require-corp-load-from-cache-storage.https.html179
-rw-r--r--testing/web-platform/tests/html/cross-origin-embedder-policy/require-corp-load-from-cache-storage.https.html.headers1
-rw-r--r--testing/web-platform/tests/html/cross-origin-embedder-policy/require-corp-revalidated-images.https.html76
-rw-r--r--testing/web-platform/tests/html/cross-origin-embedder-policy/require-corp-sw-from-none.https.html92
-rw-r--r--testing/web-platform/tests/html/cross-origin-embedder-policy/require-corp-sw-from-require-corp.https.html93
-rw-r--r--testing/web-platform/tests/html/cross-origin-embedder-policy/require-corp-sw-from-require-corp.https.html.headers1
-rw-r--r--testing/web-platform/tests/html/cross-origin-embedder-policy/require-corp-sw.https.html53
-rw-r--r--testing/web-platform/tests/html/cross-origin-embedder-policy/require-corp-worker-script-revalidation.html25
-rw-r--r--testing/web-platform/tests/html/cross-origin-embedder-policy/require-corp.https.html251
-rw-r--r--testing/web-platform/tests/html/cross-origin-embedder-policy/require-corp.https.html.headers1
-rw-r--r--testing/web-platform/tests/html/cross-origin-embedder-policy/resources/blob-url-factory.html24
-rw-r--r--testing/web-platform/tests/html/cross-origin-embedder-policy/resources/blob-url-factory.html.headers2
-rw-r--r--testing/web-platform/tests/html/cross-origin-embedder-policy/resources/cache-storage-reporting.js63
-rw-r--r--testing/web-platform/tests/html/cross-origin-embedder-policy/resources/coep-frame.html2
-rw-r--r--testing/web-platform/tests/html/cross-origin-embedder-policy/resources/coep-frame.html.headers1
-rw-r--r--testing/web-platform/tests/html/cross-origin-embedder-policy/resources/common.js19
-rw-r--r--testing/web-platform/tests/html/cross-origin-embedder-policy/resources/corp-image.py31
-rwxr-xr-xtesting/web-platform/tests/html/cross-origin-embedder-policy/resources/dedicated-worker-supporting-revalidation.py15
-rw-r--r--testing/web-platform/tests/html/cross-origin-embedder-policy/resources/dedicated-worker.js7
-rw-r--r--testing/web-platform/tests/html/cross-origin-embedder-policy/resources/empty-coep.py7
-rw-r--r--testing/web-platform/tests/html/cross-origin-embedder-policy/resources/fetch-and-create-url.html91
-rw-r--r--testing/web-platform/tests/html/cross-origin-embedder-policy/resources/fetch-in-dedicated-worker.js6
-rw-r--r--testing/web-platform/tests/html/cross-origin-embedder-policy/resources/iframe.html3
-rw-r--r--testing/web-platform/tests/html/cross-origin-embedder-policy/resources/load-corp-images.html38
-rw-r--r--testing/web-platform/tests/html/cross-origin-embedder-policy/resources/load-corp-images.html.headers1
-rw-r--r--testing/web-platform/tests/html/cross-origin-embedder-policy/resources/navigate-none.sub.html34
-rw-r--r--testing/web-platform/tests/html/cross-origin-embedder-policy/resources/navigate-require-corp-same-site.sub.html29
-rw-r--r--testing/web-platform/tests/html/cross-origin-embedder-policy/resources/navigate-require-corp-same-site.sub.html.headers2
-rw-r--r--testing/web-platform/tests/html/cross-origin-embedder-policy/resources/navigate-require-corp.sub.html24
-rw-r--r--testing/web-platform/tests/html/cross-origin-embedder-policy/resources/navigate-require-corp.sub.html.headers1
-rw-r--r--testing/web-platform/tests/html/cross-origin-embedder-policy/resources/nothing-cross-origin-corp.js1
-rw-r--r--testing/web-platform/tests/html/cross-origin-embedder-policy/resources/nothing-cross-origin-corp.js.headers1
-rw-r--r--testing/web-platform/tests/html/cross-origin-embedder-policy/resources/nothing-same-origin-corp.txt1
-rw-r--r--testing/web-platform/tests/html/cross-origin-embedder-policy/resources/nothing-same-origin-corp.txt.headers1
-rw-r--r--testing/web-platform/tests/html/cross-origin-embedder-policy/resources/postmessage-ready.html4
-rw-r--r--testing/web-platform/tests/html/cross-origin-embedder-policy/resources/report.py41
-rw-r--r--testing/web-platform/tests/html/cross-origin-embedder-policy/resources/reporting-empty-frame-multiple-headers.html.asis9
-rw-r--r--testing/web-platform/tests/html/cross-origin-embedder-policy/resources/reporting-empty-frame.html5
-rw-r--r--testing/web-platform/tests/html/cross-origin-embedder-policy/resources/reporting-worker.js25
-rw-r--r--testing/web-platform/tests/html/cross-origin-embedder-policy/resources/require-corp-sw-import-scripts.js23
-rw-r--r--testing/web-platform/tests/html/cross-origin-embedder-policy/resources/require-corp-sw-import-scripts.js.headers1
-rw-r--r--testing/web-platform/tests/html/cross-origin-embedder-policy/resources/require-corp-sw.js27
-rw-r--r--testing/web-platform/tests/html/cross-origin-embedder-policy/resources/require-corp-sw.js.headers1
-rw-r--r--testing/web-platform/tests/html/cross-origin-embedder-policy/resources/script-factory.js30
-rw-r--r--testing/web-platform/tests/html/cross-origin-embedder-policy/resources/shared-worker-fetch.js.py24
-rw-r--r--testing/web-platform/tests/html/cross-origin-embedder-policy/resources/shared-worker.js7
-rw-r--r--testing/web-platform/tests/html/cross-origin-embedder-policy/resources/sw-store-to-cache-storage.js31
-rw-r--r--testing/web-platform/tests/html/cross-origin-embedder-policy/resources/sw.js12
-rw-r--r--testing/web-platform/tests/html/cross-origin-embedder-policy/resources/universal-worker.js1
-rw-r--r--testing/web-platform/tests/html/cross-origin-embedder-policy/resources/worker-owner-frame.html2
-rw-r--r--testing/web-platform/tests/html/cross-origin-embedder-policy/resources/worker-owner.js36
-rw-r--r--testing/web-platform/tests/html/cross-origin-embedder-policy/resources/worker-support.js81
-rw-r--r--testing/web-platform/tests/html/cross-origin-embedder-policy/sandbox.https.html40
-rw-r--r--testing/web-platform/tests/html/cross-origin-embedder-policy/sandbox.https.html.headers1
-rw-r--r--testing/web-platform/tests/html/cross-origin-embedder-policy/service-worker-cache-storage.https.html117
-rw-r--r--testing/web-platform/tests/html/cross-origin-embedder-policy/shared-workers.https.html228
-rw-r--r--testing/web-platform/tests/html/cross-origin-embedder-policy/srcdoc.https.html21
-rw-r--r--testing/web-platform/tests/html/cross-origin-embedder-policy/srcdoc.https.html.headers1
-rw-r--r--testing/web-platform/tests/html/cross-origin-embedder-policy/worker-inheritance.sub.https.html61
-rw-r--r--testing/web-platform/tests/html/cross-origin-embedder-policy/worker-inheritance.sub.https.html.headers1
153 files changed, 7162 insertions, 0 deletions
diff --git a/testing/web-platform/tests/html/cross-origin-embedder-policy/META.yml b/testing/web-platform/tests/html/cross-origin-embedder-policy/META.yml
new file mode 100644
index 0000000000..dc7010880b
--- /dev/null
+++ b/testing/web-platform/tests/html/cross-origin-embedder-policy/META.yml
@@ -0,0 +1,9 @@
+spec: https://html.spec.whatwg.org/multipage/origin.html#coep
+suggested_reviewers:
+ - mikewest
+ - jugglinmike
+ - arturjanc
+ - lweichselbaum
+ - hemeryar
+ - ParisMeuleman
+ - valenting
diff --git a/testing/web-platform/tests/html/cross-origin-embedder-policy/README.md b/testing/web-platform/tests/html/cross-origin-embedder-policy/README.md
new file mode 100644
index 0000000000..16179eb013
--- /dev/null
+++ b/testing/web-platform/tests/html/cross-origin-embedder-policy/README.md
@@ -0,0 +1 @@
+See `../cross-origin-opener-policy/README.md`.
diff --git a/testing/web-platform/tests/html/cross-origin-embedder-policy/about-blank-popup.https.html b/testing/web-platform/tests/html/cross-origin-embedder-policy/about-blank-popup.https.html
new file mode 100644
index 0000000000..2dc73c7561
--- /dev/null
+++ b/testing/web-platform/tests/html/cross-origin-embedder-policy/about-blank-popup.https.html
@@ -0,0 +1,59 @@
+<!doctype html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/script-factory.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+<script src="/common/utils.js"></script>
+<script>
+ const origins = get_host_info();
+
+ promise_test(async t => {
+ const popup = window.open();
+ t.add_cleanup(() => { popup.close(); });
+
+ let data_from_popup = () => new Promise(resolve =>
+ window.addEventListener("message", (({ data }) => resolve(data))));
+
+ let check_result = (data, text) => {
+ assert_equals(data.origin, origin);
+ assert_true(data.sameOriginNoCORPSuccess,
+ text + ": Same-origin without CORP did not succeed");
+ assert_true(data.crossOriginNoCORPFailure,
+ text + ": Cross-origin without CORP did not fail");
+ };
+
+ // Check if COEP is inherited by the popup.
+ let script = popup.document.createElement('script');
+ script.innerHTML =
+ `${createScript(window.origin, origins.HTTPS_REMOTE_ORIGIN, "opener")}`;
+ popup.document.body.appendChild(script);
+ check_result(await data_from_popup(), "Initial empty document");
+
+ // Navigate the popup away.
+ popup.location = origins.HTTPS_REMOTE_ORIGIN +
+ "/html/cross-origin-embedder-policy/resources/postmessage-ready.html";
+ assert_equals(await new Promise(resolve =>
+ window.addEventListener("message", msg => resolve(msg.data))),
+ "ready");
+
+ // Navigate the popup to about:blank and wait for it.
+ popup.location = "about:blank";
+ await t.step_wait(
+ condition = () => {
+ try {
+ return popup.location.href === "about:blank";
+ } catch {}
+ return false;
+ },
+ description = "Wait for the popup to navigate.",
+ timeout=3000,
+ interval=50);
+
+ // Check again if COEP is inherited.
+ script = popup.document.createElement('script');
+ script.innerHTML =
+ `${createScript(window.origin, origins.HTTPS_REMOTE_ORIGIN, "opener")}`;
+ popup.document.body.appendChild(script);
+ check_result(await data_from_popup(), "Non-initial about:blank document");
+ }, `Cross-Origin-Embedder-Policy is inherited by about:blank popup.`);
+</script>
diff --git a/testing/web-platform/tests/html/cross-origin-embedder-policy/about-blank-popup.https.html.headers b/testing/web-platform/tests/html/cross-origin-embedder-policy/about-blank-popup.https.html.headers
new file mode 100644
index 0000000000..6604450991
--- /dev/null
+++ b/testing/web-platform/tests/html/cross-origin-embedder-policy/about-blank-popup.https.html.headers
@@ -0,0 +1 @@
+Cross-Origin-Embedder-Policy: require-corp
diff --git a/testing/web-platform/tests/html/cross-origin-embedder-policy/blob.https.html b/testing/web-platform/tests/html/cross-origin-embedder-policy/blob.https.html
new file mode 100644
index 0000000000..ce72f247ef
--- /dev/null
+++ b/testing/web-platform/tests/html/cross-origin-embedder-policy/blob.https.html
@@ -0,0 +1,44 @@
+<!doctype html>
+<meta charset=utf-8>
+<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>
+<div id=log></div>
+<script>
+const origins = get_host_info();
+[
+ {
+ "origin": origins.HTTPS_ORIGIN,
+ "crossOrigin": origins.HTTPS_REMOTE_ORIGIN
+ },
+ {
+ "origin": origins.HTTPS_REMOTE_ORIGIN,
+ "crossOrigin": origins.HTTPS_NOTSAMESITE_ORIGIN
+ },
+ {
+ "origin": origins.HTTPS_NOTSAMESITE_ORIGIN,
+ "crossOrigin": origins.HTTPS_ORIGIN
+ }
+].forEach(({ origin, crossOrigin }) => {
+ ["subframe", "navigate", "popup"].forEach(variant => {
+ async_test(t => {
+ const id = token();
+ const frame = document.createElement("iframe");
+ t.add_cleanup(() => { frame.remove(); });
+ const path = new URL("resources/blob-url-factory.html", window.location).pathname;
+ frame.src = `${origin}${path}?id=${id}&variant=${variant}&crossOrigin=${crossOrigin}`;
+ window.addEventListener("message", t.step_func(({ data }) => {
+ if (data.id !== id) {
+ return;
+ }
+ assert_equals(data.origin, origin);
+ assert_true(data.sameOriginNoCORPSuccess, "Same-origin without CORP did not succeed");
+ assert_true(data.crossOriginNoCORPFailure, "Cross-origin without CORP did not fail");
+ t.done();
+ }));
+ document.body.append(frame);
+ }, `Cross-Origin-Embedder-Policy and blob: URL from ${origin} in subframe via ${variant}`);
+ });
+});
+</script>
diff --git a/testing/web-platform/tests/html/cross-origin-embedder-policy/blob.https.html.headers b/testing/web-platform/tests/html/cross-origin-embedder-policy/blob.https.html.headers
new file mode 100644
index 0000000000..6604450991
--- /dev/null
+++ b/testing/web-platform/tests/html/cross-origin-embedder-policy/blob.https.html.headers
@@ -0,0 +1 @@
+Cross-Origin-Embedder-Policy: require-corp
diff --git a/testing/web-platform/tests/html/cross-origin-embedder-policy/block-local-documents-inheriting-none.https.html b/testing/web-platform/tests/html/cross-origin-embedder-policy/block-local-documents-inheriting-none.https.html
new file mode 100644
index 0000000000..cf5176606e
--- /dev/null
+++ b/testing/web-platform/tests/html/cross-origin-embedder-policy/block-local-documents-inheriting-none.https.html
@@ -0,0 +1,112 @@
+<!doctype html>
+<meta charset=utf-8>
+<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>
+<div id=log></div>
+
+<script>
+const script = `
+ <script>
+ top.postMessage({event: "loaded", type: location.protocol}, "*");
+ <\/script>`;
+
+const test_cases = [
+ {name: "data", url: encodeURI(`data:text/html,${script}`)},
+ {name: "blob", url: URL.createObjectURL(new Blob([script], { type: "text/html" }))},
+ {name: "about", url: "about:blank"},
+ ];
+
+const observeReports = async (frame) => {
+ const reports = [];
+
+ const observer = new frame.contentWindow.ReportingObserver(
+ rs => reports.push(...rs.map(r => r.toJSON()))
+ );
+ observer.observe();
+
+ // Wait for reports. Use a timeout to catch both expected and unexpected
+ // reports.
+ await new Promise(resolve => step_timeout(resolve, 3000));
+ return reports;
+};
+
+promise_test(async t => {
+ const this_window_token = token();
+
+ // Expect the nested frame to not load, since they inherit COEP: none from the
+ // top frame, which is incompatible with first_frame's COEP: require-corp.
+ const received_events = [];
+ addEventListener("message", event => {
+ if(event.data.event == "loaded")
+ received_events.push(`Nested ${event.data.type} loaded!`);
+ });
+
+ // Create an iframe with COEP: require-corp
+ const first_iframe = document.createElement("iframe");
+ t.add_cleanup( () => first_iframe.remove() );
+ first_iframe.src = "/common/blank.html?pipe=header(cross-origin-embedder-policy,require-corp)";
+ let iframe_load_promise = new Promise( resolve => first_iframe.addEventListener("load", resolve) );
+
+ document.body.append(first_iframe);
+ await iframe_load_promise;
+
+ const reportPromise = observeReports(first_iframe);
+ // 1. Create nested frames.
+ // They initially navigate to blank.html and have COEP: require-corp set.
+ // This initial navigation is required because it uses the parent frame as the
+ // initiator. That is first_iframe is the initiator, while we want top to be
+ // the initiator for this test, which will be done in step 4 with a second
+ // navigation from that blank.html document to the local scheme one.
+ const nested_frames = {};
+ const nested_frames_promises = [];
+ test_cases.forEach(test => {
+ nested_frame = document.createElement("iframe");
+ nested_frame.src = "/common/blank.html?pipe=header(cross-origin-embedder-policy,require-corp)";
+ t.add_cleanup( () => nested_frame.remove() );
+ nested_frames_promises.push(new Promise( resolve => nested_frame.addEventListener("load", resolve) ) );
+ first_iframe.contentDocument.body.append(nested_frame);
+ nested_frames[test.name] = nested_frame;
+ });
+
+ // 2. Wait for the loads of all iframes to complete.
+ await Promise.all(nested_frames_promises);
+
+ // 3. Navigate a dummy frame. This is required because some browsers (Chrome)
+ // might consider the first navigation in 4. as a redirect otherwise.
+ const dummy_Frame = document.createElement("iframe");
+ t.add_cleanup( () => dummy_Frame.remove() );
+ dummy_Frame.src = "/common/blank.html";
+ iframe_load_promise = new Promise( resolve => dummy_Frame.addEventListener("load", resolve) );
+ document.body.append(dummy_Frame);
+ await iframe_load_promise;
+
+ // 4. Navigate nested frames to a local scheme document.
+ // COEP should be inherited from the initiator or blobURL's creator (top in both
+ // cases), this results in COEP being none and the documents not being allowed
+ // to load under the COEP: require-corp iframe (first_iframe).
+ test_cases.forEach(test => {
+ // Top navigates nested_frame_[test.name] to a test.url
+ const frame = nested_frames[test.name];
+ // Use frame.contentDocument.location to ensure the initiator is this (top)
+ // frame. frame.src is not used here as this makes the parent of the nested
+ // frame (first_iframe) the initiator.
+ frame.contentDocument.location = test.url;
+ });
+
+ // 5. Wait and validate reports.
+ const reports = await reportPromise;
+ assert_equals(reports.length, test_cases.length);
+ test_cases.forEach(test => {
+ assert_true(reports.some( report => {
+ return report.type == 'coep' &&
+ report.body.type == 'navigation' &&
+ report.body.blockedURL == test.url;
+ }), `No report matched for test "${test.name}"`);
+ });
+ // Also verify that no message was sent by the nested frames and stored in
+ // received_events.
+ assert_equals(received_events.length, 0);
+}, "Prevent local scheme documents from loading within a COEP: require-corp iframe if they inherit COEP: none");
+</script>
diff --git a/testing/web-platform/tests/html/cross-origin-embedder-policy/cache-storage-reporting-dedicated-worker.https.html b/testing/web-platform/tests/html/cross-origin-embedder-policy/cache-storage-reporting-dedicated-worker.https.html
new file mode 100644
index 0000000000..f4b2599141
--- /dev/null
+++ b/testing/web-platform/tests/html/cross-origin-embedder-policy/cache-storage-reporting-dedicated-worker.https.html
@@ -0,0 +1,51 @@
+<!doctype html>
+<html>
+<head>
+ <title>
+ Check COEP report are send for CacheStorage requests in DedicatedWorker
+ </title>
+ <meta name="timeout" content="long">
+ <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="/service-workers/service-worker/resources/test-helpers.sub.js">
+ </script>
+ <script src="./resources/cache-storage-reporting.js"> </script>
+</head>
+<script>
+
+promise_test(async (t) => {
+ const worker_url = local(encode(worker_path + header_coep));
+ const worker = new Worker(worker_url);
+ const mc = new MessageChannel();
+ worker.postMessage({script: eval_script, port: mc.port2}, [mc.port2]);
+ const reports = (await new Promise(r => mc.port1.onmessage = r)).data;
+ assert_equals(reports.length, 1);
+ const report = reports[0];
+ assert_equals(report.body.blockedURL, image_url);
+ assert_equals(report.body.type, "corp");
+ assert_equals(report.body.disposition, "enforce");
+ assert_equals(report.body.destination, "");
+ assert_equals(report.type, "coep");
+ assert_equals(report.url, worker_url);
+}, "COEP support on DedicatedWorker.")
+
+promise_test(async (t) => {
+ const worker_url = local(encode(worker_path + header_coep_report_only));
+ const worker = new Worker(worker_url);
+ const mc = new MessageChannel();
+ worker.postMessage({script: eval_script, port: mc.port2}, [mc.port2]);
+ const reports = (await new Promise(r => mc.port1.onmessage = r)).data;
+ assert_equals(reports.length, 1);
+ const report = reports[0];
+ assert_equals(report.body.blockedURL, image_url);
+ assert_equals(report.body.type, "corp");
+ assert_equals(report.body.disposition, "reporting");
+ assert_equals(report.body.destination, "");
+ assert_equals(report.type, "coep");
+ assert_equals(report.url, worker_url);
+}, "COEP-Report-Only support on DedicatedWorker.")
+
+</script>
+</html>
diff --git a/testing/web-platform/tests/html/cross-origin-embedder-policy/cache-storage-reporting-document.https.html b/testing/web-platform/tests/html/cross-origin-embedder-policy/cache-storage-reporting-document.https.html
new file mode 100644
index 0000000000..b998ba7926
--- /dev/null
+++ b/testing/web-platform/tests/html/cross-origin-embedder-policy/cache-storage-reporting-document.https.html
@@ -0,0 +1,58 @@
+<!doctype html>
+<html>
+<head>
+ <title>
+ Check COEP report are send for CacheStorage requests in Document.
+ </title>
+ <meta name="timeout" content="long">
+ <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>
+ <script src="./resources/cache-storage-reporting.js"></script>
+</head>
+<script>
+
+async function waitReports(frame) {
+ return await new Promise((resolve) => {
+ const observer = new frame.contentWindow.ReportingObserver((reports) => {
+ observer.disconnect();
+ resolve(reports.map(r => r.toJSON()));
+ });
+ observer.observe();
+
+ frame.contentWindow.eval(eval_script);
+ });
+}
+
+promise_test(async (t) => {
+ const iframe_url = local(encode(iframe_path + header_coep));
+ const iframe = await makeIframe(t, iframe_url);
+ const reports = await waitReports(iframe);
+ assert_equals(reports.length, 1);
+ const report = reports[0];
+ assert_equals(report.body.blockedURL, image_url);
+ assert_equals(report.body.type, "corp");
+ assert_equals(report.body.disposition, "enforce");
+ assert_equals(report.body.destination, "");
+ assert_equals(report.type, "coep");
+ assert_equals(report.url, iframe_url);
+}, "COEP support on document.")
+
+promise_test(async (t) => {
+ const iframe_url = local(encode(iframe_path + header_coep_report_only));
+ const iframe = await makeIframe(t, iframe_url);
+ const reports = await waitReports(iframe);
+ assert_equals(reports.length, 1);
+ const report = reports[0];
+ assert_equals(report.body.blockedURL, image_url);
+ assert_equals(report.body.type, "corp");
+ assert_equals(report.body.disposition, "reporting");
+ assert_equals(report.body.destination, "");
+ assert_equals(report.type, "coep");
+ assert_equals(report.url, iframe_url);
+}, "COEP-Report-Only support on document.")
+
+</script>
+</html>
diff --git a/testing/web-platform/tests/html/cross-origin-embedder-policy/cache-storage-reporting-service-worker.https.html b/testing/web-platform/tests/html/cross-origin-embedder-policy/cache-storage-reporting-service-worker.https.html
new file mode 100644
index 0000000000..96a328b2cc
--- /dev/null
+++ b/testing/web-platform/tests/html/cross-origin-embedder-policy/cache-storage-reporting-service-worker.https.html
@@ -0,0 +1,64 @@
+<!doctype html>
+<html>
+<head>
+ <title>
+ Check COEP report are send for CacheStorage requests in ServiceWorker.
+ </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="/service-workers/service-worker/resources/test-helpers.sub.js">
+ </script>
+ <script src="./resources/cache-storage-reporting.js"></script>
+</head>
+<script>
+
+promise_test(async (t) => {
+ const worker_url = local(encode(worker_path + header_coep));
+ // As we don't want the service worker to control any page, generate a
+ // one-time scope.
+ const SCOPE = new URL(`resources/${token()}.html`, location).pathname;
+ const reg =
+ await service_worker_unregister_and_register(t, worker_url, SCOPE);
+ add_completion_callback(() => reg.unregister());
+ const worker = reg.installing || reg.waiting || reg.active;
+ const mc = new MessageChannel();
+ worker.postMessage({script: eval_script, port: mc.port2}, [mc.port2]);
+ const reports = (await new Promise(r => mc.port1.onmessage = r)).data;
+ assert_not_equals(reports, 'TIMEOUT');
+ assert_equals(reports.length, 1);
+ const report = reports[0];
+ assert_equals(report.body.blockedURL, image_url);
+ assert_equals(report.body.type, "corp");
+ assert_equals(report.body.disposition, "enforce");
+ assert_equals(report.body.destination, "");
+ assert_equals(report.type, "coep");
+ assert_equals(report.url, worker_url);
+}, "COEP support on ServiceWorker.");
+
+promise_test(async (t) => {
+ const worker_url = local(encode(worker_path + header_coep_report_only));
+ // As we don't want the service worker to control any page, generate a
+ // one-time scope.
+ const SCOPE = new URL(`resources/${token()}.html`, location).pathname;
+ const reg =
+ await service_worker_unregister_and_register(t, worker_url, SCOPE);
+ add_completion_callback(() => reg.unregister());
+ const worker = reg.installing || reg.waiting || reg.active;
+ const mc = new MessageChannel();
+ worker.postMessage({script: eval_script, port: mc.port2}, [mc.port2]);
+ const reports = (await new Promise(r => mc.port1.onmessage = r)).data;
+ assert_not_equals(reports, 'TIMEOUT');
+ assert_equals(reports.length, 1);
+ const report = reports[0];
+ assert_equals(report.body.blockedURL, image_url);
+ assert_equals(report.body.type, "corp");
+ assert_equals(report.body.disposition, "reporting");
+ assert_equals(report.body.destination, "");
+ assert_equals(report.type, "coep");
+ assert_equals(report.url, worker_url);
+}, "COEP-Report-Only support on ServiceWorker.");
+
+</script>
+</html>
diff --git a/testing/web-platform/tests/html/cross-origin-embedder-policy/cache-storage-reporting-shared-worker.https.html b/testing/web-platform/tests/html/cross-origin-embedder-policy/cache-storage-reporting-shared-worker.https.html
new file mode 100644
index 0000000000..34af988fc6
--- /dev/null
+++ b/testing/web-platform/tests/html/cross-origin-embedder-policy/cache-storage-reporting-shared-worker.https.html
@@ -0,0 +1,49 @@
+<!doctype html>
+<html>
+<head>
+ <title>
+ Check COEP report are send for CacheStorage requests in SharedWorker
+ </title>
+ <meta name="timeout" content="long">
+ <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/cache-storage-reporting.js"> </script>
+</head>
+<script>
+
+promise_test(async (t) => {
+ const worker_url = local(encode(worker_path + header_coep));
+ const worker = new SharedWorker(worker_url);
+ const mc = new MessageChannel();
+ worker.port.postMessage({script: eval_script, port: mc.port2}, [mc.port2]);
+ const reports = (await new Promise(r => mc.port1.onmessage = r)).data;
+ assert_equals(reports.length, 1);
+ const report = reports[0];
+ assert_equals(report.body.blockedURL, image_url);
+ assert_equals(report.body.type, "corp");
+ assert_equals(report.body.disposition, "enforce");
+ assert_equals(report.body.destination, "");
+ assert_equals(report.type, "coep");
+ assert_equals(report.url, worker_url);
+}, "COEP support on SharedWorker.")
+
+promise_test(async (t) => {
+ const worker_url = local(encode(worker_path + header_coep_report_only));
+ const worker = new SharedWorker(worker_url);
+ const mc = new MessageChannel();
+ worker.port.postMessage({script: eval_script, port: mc.port2}, [mc.port2]);
+ const reports = (await new Promise(r => mc.port1.onmessage = r)).data;
+ assert_equals(reports.length, 1);
+ const report = reports[0];
+ assert_equals(report.body.blockedURL, image_url);
+ assert_equals(report.body.type, "corp");
+ assert_equals(report.body.disposition, "reporting");
+ assert_equals(report.body.destination, "");
+ assert_equals(report.type, "coep");
+ assert_equals(report.url, worker_url);
+}, "COEP-Report-Only support on SharedWorker.")
+
+</script>
+</html>
diff --git a/testing/web-platform/tests/html/cross-origin-embedder-policy/coep-frame-javascript.https.html b/testing/web-platform/tests/html/cross-origin-embedder-policy/coep-frame-javascript.https.html
new file mode 100644
index 0000000000..089019dc2e
--- /dev/null
+++ b/testing/web-platform/tests/html/cross-origin-embedder-policy/coep-frame-javascript.https.html
@@ -0,0 +1,25 @@
+<!doctype html>
+<meta charset=utf-8>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+<script src="resources/script-factory.js"></script>
+<div id=log></div>
+<script>
+async_test(t => {
+ window.addEventListener("message", t.step_func_done(({ data }) => {
+ assert_equals(data.id, "");
+ assert_equals(data.origin, window.origin);
+ assert_true(data.sameOriginNoCORPSuccess);
+ assert_true(data.crossOriginNoCORPFailure, "Cross-origin without CORP did not fail");
+ }));
+ const frame = document.createElement("iframe");
+ t.add_cleanup(() => frame.remove());
+ frame.src = "resources/coep-frame.html";
+ frame.onload = t.step_func(() => {
+ frame.onload = null;
+ frame.src = `javascript:${encodeURIComponent(createScript(window.origin, get_host_info().HTTPS_NOTSAMESITE_ORIGIN))}`;
+ });
+ document.body.append(frame);
+}, "Cross-Origin-Embedder-Policy frame and javascript: URLs");
+</script>
diff --git a/testing/web-platform/tests/html/cross-origin-embedder-policy/coep-on-response-from-service-worker.https.html b/testing/web-platform/tests/html/cross-origin-embedder-policy/coep-on-response-from-service-worker.https.html
new file mode 100644
index 0000000000..b25b04ca4a
--- /dev/null
+++ b/testing/web-platform/tests/html/cross-origin-embedder-policy/coep-on-response-from-service-worker.https.html
@@ -0,0 +1,111 @@
+<!doctype html>
+<html>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script src="/common/get-host-info.sub.js"></script>
+<script src="/service-workers/service-worker/resources/test-helpers.sub.js"></script>
+<script>
+const FRAME_URL = 'resources/coep-frame.html'
+const SCOPE = new URL(FRAME_URL, location).pathname;
+const SCRIPT = 'resources/sw.js?';
+
+// This is similar to
+// none-sw-from-require-corp.https.html, but there is one difference:
+// In this file, the frame controlled by the service worker comes from
+// the service worker, but on none-sw-from-require-corp.https.html
+// the main document comes from the network directly. Hence the tests
+// here test whether COEP is set correctly for documents coming from
+// service workers.
+
+function remote(path) {
+ const REMOTE_ORIGIN = get_host_info().HTTPS_REMOTE_ORIGIN;
+ return new URL(path, REMOTE_ORIGIN + '/html/cross-origin-embedder-policy/');
+}
+
+let registration;
+let frame;
+
+promise_test(async (t) => {
+ registration = await service_worker_unregister_and_register(t, SCRIPT, SCOPE);
+ await wait_for_state(t, registration.installing, 'activated')
+ frame = await with_iframe(FRAME_URL);
+}, 'setup');
+
+
+promise_test(async (t) => {
+ const w = frame.contentWindow;
+ await w.fetch('resources/nothing-same-origin-corp.txt', {mode: 'no-cors'});
+}, 'making a same-origin request for CORP: same-origin');
+
+promise_test(async (t) => {
+ const w = frame.contentWindow;
+ await w.fetch('/common/blank.html', {mode: 'no-cors'});
+}, 'making a same-origin request for no CORP');
+
+promise_test(async (t) => {
+ const w = frame.contentWindow;
+ await w.fetch('resources/nothing-cross-origin-corp.js', {mode: 'no-cors'});
+}, 'making a same-origin request for CORP: cross-origin');
+
+promise_test(async (t) => {
+ const w = frame.contentWindow;
+ await promise_rejects_js(
+ t, w.TypeError,
+ w.fetch(remote('resources/nothing-same-origin-corp.txt'), {mode: 'no-cors'}));
+}, 'making a cross-origin request for CORP: same-origin');
+
+promise_test(async (t) => {
+ const w = frame.contentWindow;
+ await promise_rejects_js(
+ t, w.TypeError, w.fetch(remote('/common/blank.html'), {mode: 'no-cors'}));
+}, 'making a cross-origin request for no CORP');
+
+promise_test(async (t) => {
+ const w = frame.contentWindow;
+ await w.fetch(
+ remote('resources/nothing-cross-origin-corp.js'),
+ {mode: 'no-cors'});
+}, 'making a cross-origin request for CORP: cross-origin');
+
+promise_test(async (t) => {
+ const w = frame.contentWindow;
+ await promise_rejects_js(
+ t, w.TypeError,
+ w.fetch(remote('resources/nothing-same-origin-corp.txt?passthrough'),
+ {mode: 'no-cors'}));
+}, 'making a cross-origin request for CORP: same-origin [PASS THROUGH]');
+
+promise_test(async (t) => {
+ const w = frame.contentWindow;
+ await promise_rejects_js(
+ t, w.TypeError,
+ w.fetch(remote('/common/blank.html?passthrough'), {mode: 'no-cors'}));
+}, 'making a cross-origin request for no CORP [PASS THROUGH]');
+
+promise_test(async (t) => {
+ const w = frame.contentWindow;
+ await w.fetch(
+ remote('resources/nothing-cross-origin-corp.js?passthrough'),
+ {mode: 'no-cors'});
+}, 'making a cross-origin request for CORP: cross-origin [PASS THROUGH]');
+
+promise_test(async (t) => {
+ const w = frame.contentWindow;
+ await promise_rejects_js(
+ t, w.TypeError, w.fetch(remote('/common/blank.html'), {mode: 'cors'}));
+}, 'making a cross-origin request with CORS without ACAO');
+
+promise_test(async (t) => {
+ const w = frame.contentWindow;
+ const URL = remote(
+ '/common/blank.html?pipe=header(access-control-allow-origin,*');
+ await w.fetch(URL, {mode: 'cors'});
+}, 'making a cross-origin request with CORS');
+
+promise_test(async () => {
+ frame.remove();
+ await registration.unregister();
+}, 'teardown');
+
+</script>
+</html>
diff --git a/testing/web-platform/tests/html/cross-origin-embedder-policy/credentialless/META.yml b/testing/web-platform/tests/html/cross-origin-embedder-policy/credentialless/META.yml
new file mode 100644
index 0000000000..2bf6754a6b
--- /dev/null
+++ b/testing/web-platform/tests/html/cross-origin-embedder-policy/credentialless/META.yml
@@ -0,0 +1,7 @@
+spec: To be defined.
+suggested_reviewers:
+ - annevk
+ - arthursonzogni
+ - arturjanc
+ - camillelamy
+ - mikewest
diff --git a/testing/web-platform/tests/html/cross-origin-embedder-policy/credentialless/README.md b/testing/web-platform/tests/html/cross-origin-embedder-policy/credentialless/README.md
new file mode 100644
index 0000000000..86654525dd
--- /dev/null
+++ b/testing/web-platform/tests/html/cross-origin-embedder-policy/credentialless/README.md
@@ -0,0 +1,3 @@
+# Related documents:
+- https://github.com/mikewest/credentiallessness/
+- https://github.com/w3ctag/design-reviews/issues/582
diff --git a/testing/web-platform/tests/html/cross-origin-embedder-policy/credentialless/cache-storage.https.window.js b/testing/web-platform/tests/html/cross-origin-embedder-policy/credentialless/cache-storage.https.window.js
new file mode 100644
index 0000000000..573e6ac9cb
--- /dev/null
+++ b/testing/web-platform/tests/html/cross-origin-embedder-policy/credentialless/cache-storage.https.window.js
@@ -0,0 +1,150 @@
+// META: timeout=long
+// META: variant=?document
+// META: variant=?dedicated_worker
+// META: variant=?shared_worker
+// META: variant=?service_worker
+// META: script=/common/get-host-info.sub.js
+// META: script=/common/utils.js
+// META: script=/common/dispatcher/dispatcher.js
+// META: script=./resources/common.js
+
+// Fetch a resource and store it into CacheStorage from |storer| context. Then
+// check if it can be retrieved via CacheStorage.match from |retriever| context.
+const cacheStorageTest = (
+ description,
+ storer,
+ retriever,
+ resource_headers,
+ request_credential_mode,
+ expectation
+) => {
+ promise_test_parallel(async test => {
+ const cross_origin = get_host_info().HTTPS_REMOTE_ORIGIN;
+ const url = cross_origin + "/common/square.png?pipe=" + resource_headers +
+ `&${token()}`;
+ const this_token = token();
+
+ // Fetch a request from |stored|. Store the opaque response into
+ // CacheStorage.
+ send(storer, `
+ const cache = await caches.open("v1");
+ const fetch_request = new Request("${url}", {
+ mode: 'no-cors',
+ credentials: '${request_credential_mode}'
+ });
+ const fetch_response = await fetch(fetch_request);
+ await cache.put(fetch_request, fetch_response);
+ send("${this_token}", "stored");
+ `);
+ assert_equals(await receive(this_token), "stored");
+
+ // Retrieved it from |retriever|.
+ send(retriever, `
+ const cache = await caches.open("v1");
+ try {
+ const response = await cache.match("${url}");
+ send("${this_token}", "retrieved");
+ } catch (error) {
+ send("${this_token}", "error");
+ }
+ `);
+ assert_equals(await receive(this_token), expectation);
+ }, description);
+};
+
+// Execute the same set of tests for every type of execution contexts:
+// Documents, DedicatedWorkers, SharedWorkers, and ServiceWorkers. The results
+// should be independent of the context.
+const environment = location.search.substr(1);
+const constructor = environments[environment];
+
+const context_none = constructor(coep_none)[0];
+const context_credentialless = constructor(coep_credentialless)[0];
+const context_require_corp = constructor(coep_require_corp)[0];
+
+cacheStorageTest(`[${environment}] unsafe-none => unsafe-none`,
+ context_none,
+ context_none,
+ "",
+ "include",
+ "retrieved");
+cacheStorageTest(`[${environment}] unsafe-none => credentialless`,
+ context_none,
+ context_credentialless,
+ "",
+ "include",
+ "error");
+cacheStorageTest(`[${environment}] unsafe-none => credentialless (omit)`,
+ context_none,
+ context_credentialless,
+ "",
+ "omit",
+ "retrieved");
+cacheStorageTest(`[${environment}] unsafe-none => credentialless + CORP`,
+ context_none,
+ context_credentialless,
+ corp_cross_origin,
+ "include",
+ "retrieved");
+cacheStorageTest(`[${environment}] unsafe-none => require-corp`,
+ context_none,
+ context_require_corp,
+ "",
+ "include",
+ "error");
+cacheStorageTest(`[${environment}] unsafe-none => require-corp (omit)`,
+ context_none,
+ context_require_corp,
+ "",
+ "include",
+ "error");
+cacheStorageTest(`[${environment}] unsafe-none => require-corp + CORP`,
+ context_none,
+ context_require_corp,
+ corp_cross_origin,
+ "include",
+ "retrieved");
+
+cacheStorageTest(`[${environment}] credentialless => unsafe-none`,
+ context_credentialless,
+ context_none,
+ "",
+ "include",
+ "retrieved");
+cacheStorageTest(`[${environment}] credentialless => credentialless`,
+ context_credentialless,
+ context_credentialless,
+ "",
+ "include",
+ "retrieved");
+cacheStorageTest(`[${environment}] credentialless => require-corp`,
+ context_credentialless,
+ context_require_corp,
+ "",
+ "include",
+ "error");
+cacheStorageTest(`[${environment}] credentialless => require-corp + CORP`,
+ context_credentialless,
+ context_require_corp,
+ corp_cross_origin,
+ "include",
+ "retrieved");
+
+cacheStorageTest(`[${environment}] require_corp => unsafe-none`,
+ context_require_corp,
+ context_none,
+ corp_cross_origin,
+ "include",
+ "retrieved");
+cacheStorageTest(`[${environment}] require_corp => credentialless`,
+ context_require_corp,
+ context_credentialless,
+ corp_cross_origin,
+ "include",
+ "retrieved");
+cacheStorageTest(`[${environment}] require_corp => require-corp`,
+ context_require_corp,
+ context_require_corp,
+ corp_cross_origin,
+ "include",
+ "retrieved");
diff --git a/testing/web-platform/tests/html/cross-origin-embedder-policy/credentialless/cache.window.js b/testing/web-platform/tests/html/cross-origin-embedder-policy/credentialless/cache.window.js
new file mode 100644
index 0000000000..7d961804a0
--- /dev/null
+++ b/testing/web-platform/tests/html/cross-origin-embedder-policy/credentialless/cache.window.js
@@ -0,0 +1,84 @@
+// META: script=/common/get-host-info.sub.js
+// META: script=/common/utils.js
+// META: script=/common/dispatcher/dispatcher.js
+// META: script=./resources/common.js
+
+// With COEP:credentialless, requesting a resource without credentials MUST NOT
+// return a response requested with credentials. This would be a security
+// issue, since COEP:credentialless can be used to enable crossOriginIsolation.
+//
+// The test the behavior of the HTTP cache:
+// 1. b.com stores cookie.
+// 2. a.com(COEP:unsafe-none): request b.com's resource.
+// 3. a.com(COEP:credentialless): request b.com's resource.
+//
+// The first time, the resource is requested with credentials. The response is
+// served with Cache-Control: max-age=31536000. It enters the cache.
+// The second time, the resource is requested without credentials. The response
+// in the cache must not be returned.
+
+const cookie_key = "coep_cache_key";
+const cookie_value = "coep_cache_value";
+const same_origin = get_host_info().HTTPS_ORIGIN;
+const cross_origin = get_host_info().HTTPS_REMOTE_ORIGIN;
+
+const GetCookie = (response) => {
+ return parseCookies(JSON.parse(response))[cookie_key];
+}
+
+// "same_origin" document with COEP:unsafe-none.
+const w_coep_none_token = token();
+const w_coep_none_url = same_origin + executor_path + coep_none +
+ `&uuid=${w_coep_none_token}`
+const w_coep_none = window.open(w_coep_none_url);
+add_completion_callback(() => w_coep_none.close());
+
+// "same_origin" document with COEP:credentialles.
+const w_coep_credentialless_token = token();
+const w_coep_credentialless_url = same_origin + executor_path +
+ coep_credentialless + `&uuid=${w_coep_credentialless_token}`
+const w_coep_credentialless = window.open(w_coep_credentialless_url);
+add_completion_callback(() => w_coep_credentialless.close());
+
+const this_token = token();
+
+// A request toward a "cross-origin" cacheable response.
+const request_token = token();
+const request_url = cacheableShowRequestHeaders(cross_origin, request_token);
+
+promise_setup(async test => {
+ await setCookie(cross_origin, cookie_key, cookie_value + cookie_same_site_none);
+}, "Set cookie");
+
+// The "same-origin" COEP:unsafe-none document fetchs a "cross-origin"
+// resource. The request is sent with credentials.
+promise_setup(async test => {
+ send(w_coep_none_token, `
+ await fetch("${request_url}", {
+ mode : "no-cors",
+ credentials: "include",
+ });
+ send("${this_token}", "Resource fetched");
+ `);
+
+ assert_equals(await receive(this_token), "Resource fetched");
+ assert_equals(await receive(request_token).then(GetCookie), cookie_value);
+}, "Cache a response requested with credentials");
+
+// The "same-origin" COEP:credentialless document fetches the same resource
+// without credentials. The HTTP cache must not be used. Instead a second
+// request must be made without credentials.
+promise_test(async test => {
+ send(w_coep_credentialless_token, `
+ await fetch("${request_url}", {
+ mode : "no-cors",
+ credentials: "include",
+ });
+ send("${this_token}", "Resource fetched");
+ `);
+
+ assert_equals(await receive(this_token), "Resource fetched");
+
+ test.step_timeout(test.unreached_func("The HTTP cache has been used"), 1500);
+ assert_equals(await receive(request_token).then(GetCookie), undefined);
+}, "The HTTP cache must not be used");
diff --git a/testing/web-platform/tests/html/cross-origin-embedder-policy/credentialless/cross-origin-isolated.window.js b/testing/web-platform/tests/html/cross-origin-embedder-policy/credentialless/cross-origin-isolated.window.js
new file mode 100644
index 0000000000..361739f283
--- /dev/null
+++ b/testing/web-platform/tests/html/cross-origin-embedder-policy/credentialless/cross-origin-isolated.window.js
@@ -0,0 +1,49 @@
+// META: script=/common/get-host-info.sub.js
+// META: script=/common/utils.js
+// META: script=/common/dispatcher/dispatcher.js
+// META: script=./resources/common.js
+
+const http = get_host_info().HTTP_ORIGIN;
+const https = get_host_info().HTTPS_ORIGIN;
+
+let crossOriginIsolatedTest = (
+ description,
+ origin ,
+ headers,
+ expect_crossOriginIsolated) => {
+ promise_test_parallel(async test => {
+ const w_token = token();
+ const w_url = origin + executor_path + headers + `&uuid=${w_token}`;
+ const w = window.open(w_url)
+ add_completion_callback(() => w.close());
+
+ const this_token = token();
+ send(w_token, `
+ if (window.crossOriginIsolated)
+ send("${this_token}", "crossOriginIsolated");
+ else
+ send("${this_token}", "not isolated")
+ `);
+ assert_equals(await receive(this_token), expect_crossOriginIsolated);
+ }, description);
+}
+
+crossOriginIsolatedTest("Main crossOriginIsolated case:",
+ https, coop_same_origin +
+ coep_credentialless, "crossOriginIsolated");
+
+crossOriginIsolatedTest("Missing HTTPS:",
+ http, coop_same_origin +
+ coep_credentialless, "not isolated");
+
+crossOriginIsolatedTest("Missing COOP:same-origin:",
+ https, coep_credentialless, "not isolated");
+
+crossOriginIsolatedTest("Report-only:",
+ https, coop_same_origin +
+ coep_report_only_credentialless, "not isolated");
+
+crossOriginIsolatedTest("Report-only + enforced:",
+ https, coop_same_origin +
+ coep_credentialless +
+ coep_report_only_credentialless, "crossOriginIsolated");
diff --git a/testing/web-platform/tests/html/cross-origin-embedder-policy/credentialless/dedicated-worker.https.window.js b/testing/web-platform/tests/html/cross-origin-embedder-policy/credentialless/dedicated-worker.https.window.js
new file mode 100644
index 0000000000..b06ec56210
--- /dev/null
+++ b/testing/web-platform/tests/html/cross-origin-embedder-policy/credentialless/dedicated-worker.https.window.js
@@ -0,0 +1,137 @@
+// META: timeout=long
+// META: script=/common/get-host-info.sub.js
+// META: script=/common/utils.js
+// META: script=/common/dispatcher/dispatcher.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_dedicated_worker";
+const cookie_same_origin = "same_origin";
+const cookie_cross_origin = "cross_origin";
+
+promise_test(async test => {
+
+ await Promise.all([
+ setCookie(same_origin, cookie_key, cookie_same_origin +
+ cookie_same_site_none),
+ setCookie(cross_origin, cookie_key, cookie_cross_origin +
+ cookie_same_site_none),
+ ]);
+
+ // One window with COEP:none. (control)
+ const w_control_token = token();
+ const w_control_url = same_origin + executor_path +
+ coep_none + `&uuid=${w_control_token}`
+ const w_control = window.open(w_control_url);
+ add_completion_callback(() => w_control.close());
+
+ // One window with COEP:credentialless. (experiment)
+ const w_credentialless_token = token();
+ const w_credentialless_url = same_origin + executor_path +
+ coep_credentialless + `&uuid=${w_credentialless_token}`;
+ const w_credentialless = window.open(w_credentialless_url);
+ add_completion_callback(() => w_credentialless.close());
+
+ let GetCookie = (response) => {
+ const headers_credentialless = JSON.parse(response);
+ return parseCookies(headers_credentialless)[cookie_key];
+ }
+
+ const dedicatedWorkerTest = function(
+ description, origin, coep_for_worker,
+ expected_cookies_control,
+ expected_cookies_credentialless) {
+ promise_test_parallel(async t => {
+ // Create workers for both window.
+ const worker_token_1 = token();
+ const worker_token_2 = token();
+
+ // Used to check for errors creating the DedicatedWorker.
+ const worker_error_1 = token();
+ const worker_error_2 = token();
+
+ const w_worker_src_1 = same_origin + executor_worker_path +
+ coep_for_worker + `&uuid=${worker_token_1}`;
+ send(w_control_token, `
+ new Worker("${w_worker_src_1}", {});
+ worker.onerror = () => {
+ send("${worker_error_1}", "Worker blocked");
+ }
+ `);
+
+ const w_worker_src_2 = same_origin + executor_worker_path +
+ coep_for_worker + `&uuid=${worker_token_2}`;
+ send(w_credentialless_token, `
+ const worker = new Worker("${w_worker_src_2}", {});
+ worker.onerror = () => {
+ send("${worker_error_2}", "Worker blocked");
+ }
+ `);
+
+ // Fetch resources with the workers.
+ const request_token_1 = token();
+ const request_token_2 = token();
+ const request_url_1 = showRequestHeaders(origin, request_token_1);
+ const request_url_2 = showRequestHeaders(origin, request_token_2);
+
+ send(worker_token_1, `
+ fetch("${request_url_1}", {mode: 'no-cors', credentials: 'include'})
+ `);
+ send(worker_token_2, `
+ fetch("${request_url_2}", {mode: 'no-cors', credentials: 'include'});
+ `);
+
+ const response_control = await Promise.race([
+ receive(worker_error_1),
+ receive(request_token_1).then(GetCookie)
+ ]);
+ assert_equals(response_control,
+ expected_cookies_control,
+ "coep:none => ");
+
+ const response_credentialless = await Promise.race([
+ receive(worker_error_2),
+ receive(request_token_2).then(GetCookie)
+ ]);
+ assert_equals(response_credentialless,
+ expected_cookies_credentialless,
+ "coep:credentialless => ");
+ }, `fetch ${description}`)
+ };
+
+ dedicatedWorkerTest("same-origin + credentialless worker",
+ same_origin, coep_credentialless,
+ cookie_same_origin,
+ cookie_same_origin);
+
+ dedicatedWorkerTest("same-origin + require_corp worker",
+ same_origin, coep_require_corp,
+ cookie_same_origin,
+ cookie_same_origin);
+
+ dedicatedWorkerTest("same-origin",
+ same_origin, coep_none,
+ cookie_same_origin,
+ "Worker blocked");
+
+ dedicatedWorkerTest("cross-origin",
+ cross_origin, coep_none,
+ cookie_cross_origin,
+ "Worker blocked" // Owner's policy is credentialles, so we can't
+ // create a worker with coep_none.
+ );
+
+ dedicatedWorkerTest("cross-origin + credentialless worker",
+ cross_origin, coep_credentialless,
+ undefined, // Worker created successfully with credentialless, and fetch doesn't get credentials
+ undefined // Worker created successfully with credentialless, and fetch doesn't get credentials
+ );
+
+ dedicatedWorkerTest("cross-origin + require_corp worker",
+ cross_origin, coep_require_corp,
+ cookie_cross_origin,
+ cookie_cross_origin // The worker's policy is require_corp and doing a
+ // fetch within it has nothing to do with the Owner's policy.
+ );
+})
diff --git a/testing/web-platform/tests/html/cross-origin-embedder-policy/credentialless/fetch.https.window.js b/testing/web-platform/tests/html/cross-origin-embedder-policy/credentialless/fetch.https.window.js
new file mode 100644
index 0000000000..6ea94d0a19
--- /dev/null
+++ b/testing/web-platform/tests/html/cross-origin-embedder-policy/credentialless/fetch.https.window.js
@@ -0,0 +1,127 @@
+// META: script=/common/get-host-info.sub.js
+// META: script=/common/utils.js
+// META: script=/common/dispatcher/dispatcher.js
+// META: script=./resources/common.js
+
+promise_test(async test => {
+ const same_origin = get_host_info().HTTPS_ORIGIN;
+ const cross_origin = get_host_info().HTTPS_REMOTE_ORIGIN;
+ const cookie_key = "coep_credentialless_fetch";
+ const cookie_same_origin = "same_origin";
+ const cookie_cross_origin = "cross_origin";
+
+ await Promise.all([
+ setCookie(same_origin, cookie_key, cookie_same_origin +
+ cookie_same_site_none),
+ setCookie(cross_origin, cookie_key, cookie_cross_origin +
+ cookie_same_site_none),
+ ]);
+
+ // One window with COEP:none. (control)
+ const w_control_token = token();
+ const w_control_url = same_origin + executor_path +
+ coep_none + `&uuid=${w_control_token}`
+ const w_control = window.open(w_control_url);
+ add_completion_callback(() => w_control.close());
+
+ // One window with COEP:credentialless. (experiment)
+ const w_credentialless_token = token();
+ const w_credentialless_url = same_origin + executor_path +
+ coep_credentialless + `&uuid=${w_credentialless_token}`;
+ const w_credentialless = window.open(w_credentialless_url);
+ add_completion_callback(() => w_credentialless.close());
+
+ const fetchTest = function(
+ description, origin, mode, credentials,
+ expected_cookies_control,
+ expected_cookies_credentialless)
+ {
+ promise_test_parallel(async test => {
+ const token_1 = token();
+ const token_2 = token();
+
+ send(w_control_token, `
+ fetch("${showRequestHeaders(origin, token_1)}", {
+ mode:"${mode}",
+ credentials: "${credentials}",
+ });
+ `);
+ send(w_credentialless_token, `
+ fetch("${showRequestHeaders(origin, token_2)}", {
+ mode:"${mode}",
+ credentials: "${credentials}",
+ });
+ `);
+
+ const headers_control = JSON.parse(await receive(token_1));
+ const headers_credentialless = JSON.parse(await receive(token_2));
+
+ assert_equals(parseCookies(headers_control)[cookie_key],
+ expected_cookies_control,
+ "coep:none => ");
+ assert_equals(parseCookies(headers_credentialless)[cookie_key],
+ expected_cookies_credentialless,
+ "coep:credentialless => ");
+ }, `fetch ${description}`)
+ };
+
+ // Cookies are never sent with credentials='omit'
+ fetchTest("same-origin + no-cors + credentials:omit",
+ same_origin, 'no-cors', 'omit',
+ undefined,
+ undefined);
+ fetchTest("same-origin + cors + credentials:omit",
+ same_origin, 'cors', 'omit',
+ undefined,
+ undefined);
+ fetchTest("cross-origin + no-cors + credentials:omit",
+ cross_origin, 'no-cors', 'omit',
+ undefined,
+ undefined);
+ fetchTest("cross-origin + cors + credentials:omit",
+ cross_origin, 'cors', 'omit',
+ undefined,
+ undefined);
+
+ // Same-origin request contains Cookies.
+ fetchTest("same-origin + no-cors + credentials:include",
+ same_origin, 'no-cors', 'include',
+ cookie_same_origin,
+ cookie_same_origin);
+ fetchTest("same-origin + cors + credentials:include",
+ same_origin, 'cors', 'include',
+ cookie_same_origin,
+ cookie_same_origin);
+ fetchTest("same-origin + no-cors + credentials:same-origin",
+ same_origin, 'no-cors', 'same-origin',
+ cookie_same_origin,
+ cookie_same_origin);
+ fetchTest("same-origin + cors + credentials:same-origin",
+ same_origin, 'cors', 'same-origin',
+ cookie_same_origin,
+ cookie_same_origin);
+
+ // Cross-origin CORS requests contains Cookies, if credentials mode is set to
+ // 'include'. This does not depends on COEP.
+ fetchTest("cross-origin + cors + credentials:include",
+ cross_origin, 'cors', 'include',
+ cookie_cross_origin,
+ cookie_cross_origin);
+ fetchTest("cross-origin + cors + same-origin-credentials",
+ cross_origin, 'cors', 'same-origin',
+ undefined,
+ undefined);
+
+ // Cross-origin no-CORS requests includes Cookies when:
+ // 1. credentials mode is 'include'
+ // 2. COEP: is not credentialless.
+ fetchTest("cross-origin + no-cors + credentials:include",
+ cross_origin, 'no-cors', 'include',
+ cookie_cross_origin,
+ undefined);
+
+ fetchTest("cross-origin + no-cors + credentials:same-origin",
+ cross_origin, 'no-cors', 'same-origin',
+ undefined,
+ undefined);
+}, "");
diff --git a/testing/web-platform/tests/html/cross-origin-embedder-policy/credentialless/iframe-coep-credentialless.https.window.js b/testing/web-platform/tests/html/cross-origin-embedder-policy/credentialless/iframe-coep-credentialless.https.window.js
new file mode 100644
index 0000000000..f9d9fcb932
--- /dev/null
+++ b/testing/web-platform/tests/html/cross-origin-embedder-policy/credentialless/iframe-coep-credentialless.https.window.js
@@ -0,0 +1,37 @@
+// META: variant=?1-4
+// META: variant=?5-9
+// META: variant=?9-last
+// META: script=/common/get-host-info.sub.js
+// META: script=/common/utils.js
+// META: script=/common/dispatcher/dispatcher.js
+// META: script=./resources/common.js
+// META: script=./resources/iframeTest.js
+// META: script=/common/subset-tests.js
+
+const parent_coep_credentialless = newWindow(coep_credentialless);
+subsetTest(iframeTest, "COEP:credentialless embeds same-origin COEP:none",
+ parent_coep_credentialless, same_origin, coep_none, EXPECT_BLOCK);
+subsetTest(iframeTest, "COEP:credentialless embeds cross-origin COEP:none",
+ parent_coep_credentialless, cross_origin, coep_none, EXPECT_BLOCK);
+subsetTest(iframeTest, "COEP:credentialless embeds same-origin COEP:credentialless",
+ parent_coep_credentialless, same_origin, coep_credentialless, EXPECT_LOAD);
+subsetTest(iframeTest, "COEP:credentialless embeds cross-origin COEP:credentialless",
+ parent_coep_credentialless, cross_origin, coep_credentialless, EXPECT_BLOCK);
+subsetTest(iframeTest, "COEP:credentialless embeds same-origin COEP:require-corp",
+ parent_coep_credentialless, same_origin, coep_require_corp, EXPECT_LOAD);
+subsetTest(iframeTest, "COEP:credentialless embeds cross-origin COEP:require-corp",
+ parent_coep_credentialless, cross_origin, coep_require_corp, EXPECT_BLOCK);
+
+// Using CORP:cross-origin might unblock previously blocked iframes.
+subsetTest(iframeTestCORP, "COEP:credentialless embeds same-origin COEP:none",
+ parent_coep_credentialless, same_origin, coep_none, EXPECT_BLOCK);
+subsetTest(iframeTestCORP, "COEP:credentialless embeds cross-origin COEP:none",
+ parent_coep_credentialless, cross_origin, coep_none, EXPECT_BLOCK);
+subsetTest(iframeTestCORP, "COEP:credentialless embeds same-origin COEP:credentialless",
+ parent_coep_credentialless, same_origin, coep_credentialless, EXPECT_LOAD);
+subsetTest(iframeTestCORP, "COEP:credentialless embeds cross-origin COEP:credentialless",
+ parent_coep_credentialless, cross_origin, coep_credentialless, EXPECT_LOAD);
+subsetTest(iframeTestCORP, "COEP:credentialless embeds same-origin COEP:require-corp",
+ parent_coep_credentialless, same_origin, coep_require_corp, EXPECT_LOAD);
+subsetTest(iframeTestCORP, "COEP:credentialless embeds cross-origin COEP:require-corp",
+ parent_coep_credentialless, cross_origin, coep_require_corp, EXPECT_LOAD);
diff --git a/testing/web-platform/tests/html/cross-origin-embedder-policy/credentialless/iframe-coep-none.https.window.js b/testing/web-platform/tests/html/cross-origin-embedder-policy/credentialless/iframe-coep-none.https.window.js
new file mode 100644
index 0000000000..4f50b8d407
--- /dev/null
+++ b/testing/web-platform/tests/html/cross-origin-embedder-policy/credentialless/iframe-coep-none.https.window.js
@@ -0,0 +1,22 @@
+// META: variant=?1-4
+// META: variant=?5-last
+// META: script=/common/get-host-info.sub.js
+// META: script=/common/utils.js
+// META: script=/common/dispatcher/dispatcher.js
+// META: script=./resources/common.js
+// META: script=./resources/iframeTest.js
+// META: script=/common/subset-tests.js
+
+const parent_coep_none = newWindow(coep_none);
+subsetTest(iframeTest, "COEP:none embeds same-origin COEP:none",
+ parent_coep_none, same_origin, coep_none, EXPECT_LOAD);
+subsetTest(iframeTest, "COEP:none embeds cross-origin COEP:none",
+ parent_coep_none, cross_origin, coep_none, EXPECT_LOAD);
+subsetTest(iframeTest, "COEP:none embeds same-origin COEP:credentialless",
+ parent_coep_none, same_origin, coep_credentialless, EXPECT_LOAD);
+subsetTest(iframeTest, "COEP:none embeds cross-origin COEP:credentialless",
+ parent_coep_none, cross_origin, coep_credentialless, EXPECT_LOAD);
+subsetTest(iframeTest, "COEP:none embeds same-origin COEP:require-corp",
+ parent_coep_none, same_origin, coep_require_corp, EXPECT_LOAD);
+subsetTest(iframeTest, "COEP:none embeds cross-origin COEP:require-corp",
+ parent_coep_none, cross_origin, coep_require_corp, EXPECT_LOAD);
diff --git a/testing/web-platform/tests/html/cross-origin-embedder-policy/credentialless/iframe-coep-redirect.https.window.js b/testing/web-platform/tests/html/cross-origin-embedder-policy/credentialless/iframe-coep-redirect.https.window.js
new file mode 100644
index 0000000000..a9f6a1c88e
--- /dev/null
+++ b/testing/web-platform/tests/html/cross-origin-embedder-policy/credentialless/iframe-coep-redirect.https.window.js
@@ -0,0 +1,41 @@
+// META: variant=?1-4
+// META: variant=?5-last
+// META: script=/common/get-host-info.sub.js
+// META: script=/common/utils.js
+// META: script=/common/dispatcher/dispatcher.js
+// META: script=./resources/common.js
+// META: script=./resources/iframeTest.js
+promise_test(async test => {
+ const test_token = token();
+ const child_token = token();
+
+ const redirecting_child_url = cross_origin + "/common/dispatcher/executor.html?uuid=" + child_token;
+
+ const child_url =
+ same_origin +
+ "/html/cross-origin-embedder-policy/credentialless/resources/redirect_none_to_credentialless.py?redirectTo=" + redirecting_child_url;
+
+ await send(newWindow(coep_credentialless), `
+ let iframe = document.createElement("iframe");
+ iframe.src = "${child_url}";
+ document.body.appendChild(iframe);
+ `);
+
+ await send(child_token, `
+ send("${test_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(test_token, "block"), 6000);
+
+ assert_equals(await receive(test_token), EXPECT_LOAD);
+});
diff --git a/testing/web-platform/tests/html/cross-origin-embedder-policy/credentialless/iframe-coep-require-corp.https.window.js b/testing/web-platform/tests/html/cross-origin-embedder-policy/credentialless/iframe-coep-require-corp.https.window.js
new file mode 100644
index 0000000000..a70d4ff8fc
--- /dev/null
+++ b/testing/web-platform/tests/html/cross-origin-embedder-policy/credentialless/iframe-coep-require-corp.https.window.js
@@ -0,0 +1,38 @@
+// META: variant=?1-4
+// META: variant=?5-9
+// META: variant=?9-last
+// META: script=/common/get-host-info.sub.js
+// META: script=/common/utils.js
+// META: script=/common/dispatcher/dispatcher.js
+// META: script=./resources/common.js
+// META: script=./resources/iframeTest.js
+// META: script=/common/subset-tests.js
+
+const parent_coep_require_corp = newWindow(coep_require_corp);
+
+subsetTest(iframeTest, "COEP:require-corp embeds same-origin COEP:none",
+ parent_coep_require_corp, same_origin, coep_none, EXPECT_BLOCK);
+subsetTest(iframeTest, "COEP:require-corp embeds cross-origin COEP:none",
+ parent_coep_require_corp, cross_origin, coep_none, EXPECT_BLOCK);
+subsetTest(iframeTest, "COEP:require-corp embeds same-origin COEP:credentialless",
+ parent_coep_require_corp, same_origin, coep_credentialless, EXPECT_LOAD);
+subsetTest(iframeTest, "COEP:require-corp embeds cross-origin COEP:credentialless",
+ parent_coep_require_corp, cross_origin, coep_credentialless, EXPECT_BLOCK);
+subsetTest(iframeTest, "COEP:require-corp embeds same-origin COEP:require-corp",
+ parent_coep_require_corp, same_origin, coep_require_corp, EXPECT_LOAD);
+subsetTest(iframeTest, "COEP:require-corp embeds cross-origin COEP:require-corp",
+ parent_coep_require_corp, cross_origin, coep_require_corp, EXPECT_BLOCK);
+
+// Using CORP:cross-origin might unblock previously blocked iframes.
+subsetTest(iframeTestCORP, "COEP:require-corp embeds same-origin COEP:none",
+ parent_coep_require_corp, same_origin, coep_none, EXPECT_BLOCK);
+subsetTest(iframeTestCORP, "COEP:require-corp embeds cross-origin COEP:none",
+ parent_coep_require_corp, cross_origin, coep_none, EXPECT_BLOCK);
+subsetTest(iframeTestCORP, "COEP:require-corp embeds same-origin COEP:credentialless",
+ parent_coep_require_corp, same_origin, coep_credentialless, EXPECT_LOAD);
+subsetTest(iframeTestCORP, "COEP:require-corp embeds cross-origin COEP:credentialless",
+ parent_coep_require_corp, cross_origin, coep_credentialless, EXPECT_LOAD);
+subsetTest(iframeTestCORP, "COEP:require-corp embeds same-origin COEP:require-corp",
+ parent_coep_require_corp, same_origin, coep_require_corp, EXPECT_LOAD);
+subsetTest(iframeTestCORP, "COEP:require-corp embeds cross-origin COEP:require-corp",
+ parent_coep_require_corp, cross_origin, coep_require_corp, EXPECT_LOAD);
diff --git a/testing/web-platform/tests/html/cross-origin-embedder-policy/credentialless/iframe.window.js b/testing/web-platform/tests/html/cross-origin-embedder-policy/credentialless/iframe.window.js
new file mode 100644
index 0000000000..d7a9c1e170
--- /dev/null
+++ b/testing/web-platform/tests/html/cross-origin-embedder-policy/credentialless/iframe.window.js
@@ -0,0 +1,47 @@
+// META: timeout=long
+// META: script=/common/get-host-info.sub.js
+// META: script=/common/utils.js
+// META: script=/common/dispatcher/dispatcher.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 = "coep_redirect";
+const cookie_same_origin = "same_origin";
+const cookie_cross_origin = "cross_origin";
+
+// Operate on a window with COEP:credentialless.
+const w_token = token();
+const w_url = same_origin + executor_path + coep_credentialless +
+ `&uuid=${w_token}`
+const w = window.open(w_url);
+add_completion_callback(() => w.close());
+
+// Check whether COEP:credentialless applies to navigation request. It
+// shouldn't.
+const iframeTest = function(name, origin, expected_cookies) {
+ promise_test_parallel(async test => {
+ const token_request = token();
+ const url = showRequestHeaders(origin, token_request);
+
+ send(w_token, `
+ const iframe = document.createElement("iframe");
+ iframe.src = "${url}";
+ document.body.appendChild(iframe);
+ `);
+
+ const headers = JSON.parse(await receive(token_request));
+ assert_equals(parseCookies(headers)[cookie_key], expected_cookies);
+ }, name)
+};
+
+promise_test_parallel(async test => {
+ await Promise.all([
+ setCookie(same_origin, cookie_key, cookie_same_origin +
+ cookie_same_site_none),
+ setCookie(cross_origin, cookie_key, cookie_cross_origin +
+ cookie_same_site_none),
+ ]);
+
+ iframeTest("same-origin", same_origin, cookie_same_origin);
+ iframeTest("cross-origin", cross_origin, cookie_cross_origin);
+}, "Setup");
diff --git a/testing/web-platform/tests/html/cross-origin-embedder-policy/credentialless/image.https.window.js b/testing/web-platform/tests/html/cross-origin-embedder-policy/credentialless/image.https.window.js
new file mode 100644
index 0000000000..2e9166d1bb
--- /dev/null
+++ b/testing/web-platform/tests/html/cross-origin-embedder-policy/credentialless/image.https.window.js
@@ -0,0 +1,97 @@
+// META: script=/common/get-host-info.sub.js
+// META: script=/common/utils.js
+// META: script=/common/dispatcher/dispatcher.js
+// META: script=./resources/common.js
+
+promise_test_parallel(async test => {
+ const same_origin = get_host_info().HTTPS_ORIGIN;
+ const cross_origin = get_host_info().HTTPS_REMOTE_ORIGIN;
+ const cookie_key = "coep_credentialless_image";
+ const cookie_same_origin = "same_origin";
+ const cookie_cross_origin = "cross_origin";
+
+ await Promise.all([
+ setCookie(same_origin, cookie_key, cookie_same_origin +
+ cookie_same_site_none),
+ setCookie(cross_origin, cookie_key, cookie_cross_origin +
+ cookie_same_site_none),
+ ]);
+
+ // One window with COEP:none. (control)
+ const w_control_token = token();
+ const w_control_url = same_origin + executor_path +
+ coep_none + `&uuid=${w_control_token}`
+ const w_control = window.open(w_control_url);
+ add_completion_callback(() => w_control.close());
+
+ // One window with COEP:credentialless. (experiment)
+ const w_credentialless_token = token();
+ const w_credentialless_url = same_origin + executor_path +
+ coep_credentialless + `&uuid=${w_credentialless_token}`;
+ const w_credentialless = window.open(w_credentialless_url);
+ add_completion_callback(() => w_credentialless.close());
+
+ let imgTest = function(
+ description, origin, mode,
+ expected_cookies_control,
+ expected_cookies_credentialless)
+ {
+ promise_test_parallel(async test => {
+ const token_1 = token();
+ const token_2 = token();
+
+ send(w_control_token, `
+ let img = document.createElement("img");
+ img.src = "${showRequestHeaders(origin, token_1)}";
+ ${mode};
+ document.body.appendChild(img);
+ `);
+ send(w_credentialless_token, `
+ let img = document.createElement("img");
+ img.src = "${showRequestHeaders(origin, token_2)}";
+ ${mode};
+ document.body.appendChild(img);
+ `);
+
+ const headers_control = JSON.parse(await receive(token_1));
+ const headers_credentialless = JSON.parse(await receive(token_2));
+
+ assert_equals(parseCookies(headers_control)[cookie_key],
+ expected_cookies_control,
+ "coep:none => ");
+ assert_equals(parseCookies(headers_credentialless)[cookie_key],
+ expected_cookies_credentialless,
+ "coep:credentialless => ");
+ }, `image ${description}`)
+ };
+
+ // Same-origin request always contains Cookies:
+ imgTest("same-origin + undefined",
+ same_origin, '',
+ cookie_same_origin,
+ cookie_same_origin);
+ imgTest("same-origin + anonymous",
+ same_origin, 'img.crossOrigin="anonymous"',
+ cookie_same_origin,
+ cookie_same_origin);
+ imgTest("same-origin + use-credentials",
+ same_origin, 'img.crossOrigin="use-credentials"',
+ cookie_same_origin,
+ cookie_same_origin);
+
+ // Cross-origin request contains cookies in the following cases:
+ // - COEP:credentialless is not set.
+ // - img.crossOrigin is `use-credentials`.
+ imgTest("cross-origin + undefined",
+ cross_origin, '',
+ cookie_cross_origin,
+ undefined);
+ imgTest("cross-origin + anonymous",
+ cross_origin, 'img.crossOrigin="anonymous"',
+ undefined,
+ undefined);
+ imgTest("cross-origin + use-credentials",
+ cross_origin, 'img.crossOrigin="use-credentials"',
+ cookie_cross_origin,
+ cookie_cross_origin);
+}, "Main");
diff --git a/testing/web-platform/tests/html/cross-origin-embedder-policy/credentialless/link.https.window.js b/testing/web-platform/tests/html/cross-origin-embedder-policy/credentialless/link.https.window.js
new file mode 100644
index 0000000000..0a0f8eef66
--- /dev/null
+++ b/testing/web-platform/tests/html/cross-origin-embedder-policy/credentialless/link.https.window.js
@@ -0,0 +1,99 @@
+// META: script=/common/get-host-info.sub.js
+// META: script=/common/utils.js
+// META: script=/common/dispatcher/dispatcher.js
+// META: script=./resources/common.js
+
+promise_test_parallel(async test => {
+ const same_origin = get_host_info().HTTPS_ORIGIN;
+ const cross_origin = get_host_info().HTTPS_REMOTE_ORIGIN;
+ const cookie_key = "coep_credentialless_link";
+ const cookie_same_origin = "same_origin";
+ const cookie_cross_origin = "cross_origin";
+
+ await Promise.all([
+ setCookie(same_origin, cookie_key, cookie_same_origin +
+ cookie_same_site_none),
+ setCookie(cross_origin, cookie_key, cookie_cross_origin +
+ cookie_same_site_none),
+ ]);
+
+ // One window with COEP:none. (control)
+ const w_control_token = token();
+ const w_control_url = same_origin + executor_path +
+ coep_none + `&uuid=${w_control_token}`
+ const w_control = window.open(w_control_url);
+ add_completion_callback(() => w_control.close());
+
+ // One window with COEP:credentialless. (experiment)
+ const w_credentialless_token = token();
+ const w_credentialless_url = same_origin + executor_path +
+ coep_credentialless + `&uuid=${w_credentialless_token}`;
+ const w_credentialless = window.open(w_credentialless_url);
+ add_completion_callback(() => w_credentialless.close());
+
+ let linkTest = function(
+ description, origin, mode,
+ expected_cookies_control,
+ expected_cookies_credentialless)
+ {
+ promise_test_parallel(async test => {
+ const token_1 = token();
+ const token_2 = token();
+
+ send(w_control_token, `
+ let link = document.createElement("link");
+ link.href = "${showRequestHeaders(origin, token_1)}";
+ link.rel = "stylesheet";
+ ${mode}
+ document.head.appendChild(link);
+ `);
+ send(w_credentialless_token, `
+ let link = document.createElement("link");
+ link.href = "${showRequestHeaders(origin, token_2)}";
+ link.rel = "stylesheet";
+ ${mode}
+ document.head.appendChild(link);
+ `);
+
+ const headers_control = JSON.parse(await receive(token_1));
+ const headers_credentialless = JSON.parse(await receive(token_2));
+
+ assert_equals(parseCookies(headers_control)[cookie_key],
+ expected_cookies_control,
+ "coep:none => ");
+ assert_equals(parseCookies(headers_credentialless)[cookie_key],
+ expected_cookies_credentialless,
+ "coep:credentialless => ");
+ }, `link ${description}`)
+ };
+
+ // Same-origin request always contains Cookies:
+ linkTest("same-origin + undefined",
+ same_origin, '',
+ cookie_same_origin,
+ cookie_same_origin);
+ linkTest("same-origin + anonymous",
+ same_origin, 'link.crossOrigin="anonymous"',
+ cookie_same_origin,
+ cookie_same_origin);
+ linkTest("same-origin + use-credentials",
+ same_origin, 'link.crossOrigin="use-credentials"',
+ cookie_same_origin,
+ cookie_same_origin);
+
+ // Cross-origin request contains cookies in the following cases:
+ // - COEP:credentialless is not set.
+ // - link.crossOrigin is `use-credentials`.
+ linkTest("cross-origin + undefined",
+ cross_origin, '',
+ cookie_cross_origin,
+ undefined);
+ linkTest("cross-origin + anonymous",
+ cross_origin, 'link.crossOrigin="anonymous"',
+ undefined,
+ undefined);
+ linkTest("cross-origin + use-credentials",
+ cross_origin, 'link.crossOrigin="use-credentials"',
+ cookie_cross_origin,
+ cookie_cross_origin);
+}, "Main");
diff --git a/testing/web-platform/tests/html/cross-origin-embedder-policy/credentialless/redirect.window.js b/testing/web-platform/tests/html/cross-origin-embedder-policy/credentialless/redirect.window.js
new file mode 100644
index 0000000000..db8ca08d36
--- /dev/null
+++ b/testing/web-platform/tests/html/cross-origin-embedder-policy/credentialless/redirect.window.js
@@ -0,0 +1,55 @@
+// META: timeout=long
+// META: script=/common/get-host-info.sub.js
+// META: script=/common/utils.js
+// META: script=/common/dispatcher/dispatcher.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 = "coep_redirect";
+const cookie_same_origin = "same_origin";
+const cookie_cross_origin = "cross_origin";
+
+// Operate on a window with COEP:credentialless.:
+const w_token = token();
+const w_url = same_origin + executor_path + coep_credentialless +
+ `&uuid=${w_token}`
+const w = window.open(w_url);
+add_completion_callback(() => w.close());
+
+let redirectTest = function(name,
+ redirect_origin,
+ final_origin,
+ expected_cookies) {
+ promise_test_parallel(async test => {
+ const token_request = token();
+ const url = redirect_origin + "/common/redirect.py?location=" +
+ encodeURIComponent(showRequestHeaders(final_origin, token_request));
+
+ send(w_token, `
+ const img = document.createElement("img");
+ img.src = "${url}";
+ document.body.appendChild(img);
+ `);
+
+ const headers = JSON.parse(await receive(token_request));
+ assert_equals(parseCookies(headers)[cookie_key], expected_cookies);
+ }, name)
+};
+
+promise_test_parallel(async test => {
+ await Promise.all([
+ setCookie(same_origin, cookie_key, cookie_same_origin +
+ cookie_same_site_none),
+ setCookie(cross_origin, cookie_key, cookie_cross_origin +
+ cookie_same_site_none),
+ ]);
+
+ redirectTest("same-origin -> same-origin",
+ same_origin, same_origin, cookie_same_origin);
+ redirectTest("same-origin -> cross-origin",
+ same_origin, cross_origin, undefined)
+ redirectTest("cross-origin -> same-origin",
+ cross_origin, same_origin, undefined);
+ redirectTest("cross-origin -> cross-origin",
+ cross_origin, cross_origin, undefined);
+}, "Setup");
diff --git a/testing/web-platform/tests/html/cross-origin-embedder-policy/credentialless/reporting-navigation.https.window.js b/testing/web-platform/tests/html/cross-origin-embedder-policy/credentialless/reporting-navigation.https.window.js
new file mode 100644
index 0000000000..1d62996e38
--- /dev/null
+++ b/testing/web-platform/tests/html/cross-origin-embedder-policy/credentialless/reporting-navigation.https.window.js
@@ -0,0 +1,139 @@
+// META: timeout=long
+// META: script=/common/get-host-info.sub.js
+// META: script=./resources/common.js
+const {ORIGIN, REMOTE_ORIGIN} = get_host_info();
+const COEP = '|header(cross-origin-embedder-policy,credentialless)';
+const COEP_RO =
+ '|header(cross-origin-embedder-policy-report-only,credentialless)';
+const CORP_CROSS_ORIGIN =
+ '|header(cross-origin-resource-policy,cross-origin)';
+const FRAME_URL = `${ORIGIN}/common/blank.html?pipe=`;
+const REMOTE_FRAME_URL = `${REMOTE_ORIGIN}/common/blank.html?pipe=`;
+
+function checkCorpReport(report, contextUrl, blockedUrl, disposition) {
+ assert_equals(report.type, 'coep');
+ assert_equals(report.url, contextUrl);
+ assert_equals(report.body.type, 'corp');
+ assert_equals(report.body.blockedURL, blockedUrl);
+ assert_equals(report.body.disposition, disposition);
+ assert_equals(report.body.destination, 'iframe');
+}
+
+function checkCoepMismatchReport(report, contextUrl, blockedUrl, disposition) {
+ assert_equals(report.type, 'coep');
+ assert_equals(report.url, contextUrl);
+ assert_equals(report.body.type, 'navigation');
+ assert_equals(report.body.blockedURL, blockedUrl);
+ assert_equals(report.body.disposition, disposition);
+}
+
+function loadFrame(document, url) {
+ return new Promise((resolve, reject) => {
+ const frame = document.createElement('iframe');
+ frame.src = url;
+ frame.onload = () => resolve(frame);
+ frame.onerror = reject;
+ document.body.appendChild(frame);
+ });
+}
+
+// |parentSuffix| is a suffix for the parent frame URL.
+// |targetUrl| is a URL for the target frame.
+async function loadFrames(test, parentSuffix, targetUrl) {
+ const frame = await loadFrame(document, FRAME_URL + parentSuffix);
+ test.add_cleanup(() => frame.remove());
+ // Here we don't need "await". This loading may or may not succeed, and
+ // we're not interested in the result.
+ loadFrame(frame.contentDocument, targetUrl);
+
+ return frame;
+}
+
+async function observeReports(global, expected_count) {
+ const reports = [];
+ const receivedEveryReports = new Promise(resolve => {
+ if (expected_count == 0)
+ resolve();
+
+ const observer = new global.ReportingObserver((rs) => {
+ for (const r of rs) {
+ reports.push(r.toJSON());
+ }
+ if (expected_count <= reports.length)
+ resolve();
+ });
+ observer.observe();
+
+ });
+
+ // Wait 500ms more to catch additionnal unexpected reports.
+ await receivedEveryReports;
+ await new Promise(r => step_timeout(r, 500));
+ return reports;
+}
+
+function desc(headers) {
+ return headers === '' ? '(none)' : headers;
+}
+
+// CASES is a list of test case. Each test case consists of:
+// parent_headers: the suffix of the URL of the parent frame.
+// target_headers: the suffix of the URL of the target frame.
+// expected_reports: one of:
+// 'CORP': CORP violation
+// 'CORP-RO': CORP violation (report only)
+// 'NAV': COEP mismatch between the frames.
+// 'NAV-RO': COEP mismatch between the frames (report only).
+const reportingTest = function(
+ parent_headers, target_headers, expected_reports) {
+ // These tests are very slow, so they must be run in parallel using
+ // async_test.
+ promise_test_parallel(async t => {
+ const targetUrl = REMOTE_FRAME_URL + target_headers;
+ const parent = await loadFrames(t, parent_headers, targetUrl);
+ const contextUrl = parent.src ? parent.src : 'about:blank';
+ const reports = await observeReports(
+ parent.contentWindow,
+ expected_reports.length
+ );
+ assert_equals(reports.length, expected_reports.length);
+ for (let i = 0; i < reports.length; i += 1) {
+ const report = reports[i];
+ switch (expected_reports[i]) {
+ case 'CORP':
+ checkCorpReport(report, contextUrl, targetUrl, 'enforce');
+ break;
+ case 'CORP-RO':
+ checkCorpReport(report, contextUrl, targetUrl, 'reporting');
+ break;
+ case 'NAV':
+ checkCoepMismatchReport(report, contextUrl, targetUrl, 'enforce');
+ break;
+ case 'NAV-RO':
+ checkCoepMismatchReport(report, contextUrl, targetUrl, 'reporting');
+ break;
+ default:
+ assert_unreached(
+ 'Unexpected report exception: ' + expected_reports[i]);
+ }
+ }
+ }, `parent: ${desc(parent_headers)}, target: ${desc(target_headers)}, `);
+}
+
+reportingTest('', '', []);
+reportingTest('', COEP, []);
+reportingTest(COEP, COEP, ['CORP']);
+reportingTest(COEP, '', ['CORP']);
+
+reportingTest('', CORP_CROSS_ORIGIN, []);
+reportingTest(COEP, CORP_CROSS_ORIGIN, ['NAV']);
+
+reportingTest('', COEP + CORP_CROSS_ORIGIN, []);
+reportingTest(COEP, COEP + CORP_CROSS_ORIGIN, []);
+
+reportingTest(COEP_RO, COEP, ['CORP-RO']);
+reportingTest(COEP_RO, '', ['CORP-RO', 'NAV-RO']);
+reportingTest(COEP_RO, CORP_CROSS_ORIGIN, ['NAV-RO']);
+reportingTest(COEP_RO, COEP + CORP_CROSS_ORIGIN, []);
+
+reportingTest(COEP, COEP_RO + CORP_CROSS_ORIGIN, ['NAV']);
diff --git a/testing/web-platform/tests/html/cross-origin-embedder-policy/credentialless/reporting-subresource-corp.https.window.js b/testing/web-platform/tests/html/cross-origin-embedder-policy/credentialless/reporting-subresource-corp.https.window.js
new file mode 100644
index 0000000000..ab583fd49e
--- /dev/null
+++ b/testing/web-platform/tests/html/cross-origin-embedder-policy/credentialless/reporting-subresource-corp.https.window.js
@@ -0,0 +1,74 @@
+// META: timeout=long
+// META: script=/common/utils.js
+// META: script=/common/get-host-info.sub.js
+// META: script=/service-workers/service-worker/resources/test-helpers.sub.js
+const {ORIGIN, REMOTE_ORIGIN} = get_host_info();
+const BASE = "/html/cross-origin-embedder-policy/resources";
+const REPORTING_FRAME_URL = `${ORIGIN}${BASE}/reporting-empty-frame.html` +
+ '?pipe=header(cross-origin-embedder-policy,credentialless)' +
+ '&token=${token()}';
+
+async function observeReports(global, expected_count) {
+ const reports = [];
+ const receivedEveryReports = new Promise(resolve => {
+ if (expected_count == 0)
+ resolve();
+
+ const observer = new global.ReportingObserver((rs) => {
+ for (const r of rs) {
+ reports.push(r.toJSON());
+ }
+ if (expected_count <= reports.length)
+ resolve();
+ });
+ observer.observe();
+
+ });
+
+ await receivedEveryReports;
+ // Wait 1000ms more to catch additionnal unexpected reports.
+ await new Promise(r => step_timeout(r, 1000));
+ return reports;
+}
+
+async function fetchInFrame(t, frameUrl, url, expected_count) {
+ const frame = await with_iframe(frameUrl);
+ t.add_cleanup(() => frame.remove());
+
+ const init = { mode: 'no-cors', cache: 'no-store' };
+ let future_reports = observeReports(frame.contentWindow, expected_count);
+ await frame.contentWindow.fetch(url, init).catch(() => {});
+
+ return await future_reports;
+}
+
+function checkReport(report, contextUrl, blockedUrl, disposition, destination) {
+ assert_equals(report.type, 'coep');
+ assert_equals(report.url, contextUrl);
+ assert_equals(report.body.type, 'corp');
+ assert_equals(report.body.blockedURL, blockedUrl);
+ assert_equals(report.body.disposition, disposition);
+ assert_equals(report.body.destination, destination);
+}
+
+// A redirection is used, so that the initial request is same-origin and is
+// proxyied through the service worker. The ServiceWorker is COEP:unsafe-none,
+// so it will make the cross-origin request with credentials. The fetch will
+// succeed, but the response will be blocked by CORP when entering the
+// COEP:credentialless document.
+// https://github.com/w3c/ServiceWorker/issues/1592
+promise_test(async (t) => {
+ const url = `${ORIGIN}/common/redirect.py?location=` +
+ encodeURIComponent(`${REMOTE_ORIGIN}/common/text-plain.txt`);
+ const WORKER_URL = `${ORIGIN}${BASE}/sw.js`;
+ const reg = await service_worker_unregister_and_register(
+ t, WORKER_URL, REPORTING_FRAME_URL);
+ t.add_cleanup(() => reg.unregister());
+ const worker = reg.installing || reg.waiting || reg.active;
+ worker.addEventListener('error', t.unreached_func('Worker.onerror'));
+ await wait_for_state(t, worker, 'activated');
+
+ const reports = await fetchInFrame(t, REPORTING_FRAME_URL, url, 1);
+ assert_equals(reports.length, 1);
+ checkReport(reports[0], REPORTING_FRAME_URL, url, 'enforce', '');
+});
diff --git a/testing/web-platform/tests/html/cross-origin-embedder-policy/credentialless/resources/common.js b/testing/web-platform/tests/html/cross-origin-embedder-policy/credentialless/resources/common.js
new file mode 100644
index 0000000000..ce21c766f6
--- /dev/null
+++ b/testing/web-platform/tests/html/cross-origin-embedder-policy/credentialless/resources/common.js
@@ -0,0 +1,134 @@
+const executor_path = '/common/dispatcher/executor.html?pipe=';
+const executor_worker_path = '/common/dispatcher/executor-worker.js?pipe=';
+const executor_service_worker_path = '/common/dispatcher/executor-service-worker.js?pipe=';
+
+// COEP
+const coep_none =
+ '|header(Cross-Origin-Embedder-Policy,none)';
+const coep_credentialless =
+ '|header(Cross-Origin-Embedder-Policy,credentialless)';
+const coep_require_corp =
+ '|header(Cross-Origin-Embedder-Policy,require-corp)';
+
+// COEP-Report-Only
+const coep_report_only_credentialless =
+ '|header(Cross-Origin-Embedder-Policy-Report-Only,credentialless)';
+
+// COOP
+const coop_same_origin =
+ '|header(Cross-Origin-Opener-Policy,same-origin)';
+
+// CORP
+const corp_cross_origin =
+ '|header(Cross-Origin-Resource-Policy,cross-origin)';
+
+const cookie_same_site_none = ';SameSite=None;Secure';
+
+// Test using the modern async/await primitives are easier to read/write.
+// However they run sequentially, contrary to async_test. This is the parallel
+// version, to avoid timing out.
+let promise_test_parallel = (promise, description) => {
+ async_test(test => {
+ promise(test)
+ .then(() => test.done())
+ .catch(test.step_func(error => { throw error; }));
+ }, description);
+};
+
+// Add a cookie |cookie_key|=|cookie_value| on an |origin|.
+// Note: cookies visibility depends on the path of the document. Those are set
+// from a document from: /html/cross-origin-embedder-policy/credentialless/. So
+// the cookie is visible to every path underneath.
+const setCookie = async (origin, cookie_key, cookie_value) => {
+ const popup_token = token();
+ const popup_url = origin + executor_path + `&uuid=${popup_token}`;
+ const popup = window.open(popup_url);
+
+ const reply_token = token();
+ send(popup_token, `
+ document.cookie = "${cookie_key}=${cookie_value}";
+ send("${reply_token}", "done");
+ `);
+ assert_equals(await receive(reply_token), "done");
+ popup.close();
+}
+
+let parseCookies = function(headers_json) {
+ if (!headers_json["cookie"])
+ return {};
+
+ return headers_json["cookie"]
+ .split(';')
+ .map(v => v.split('='))
+ .reduce((acc, v) => {
+ acc[v[0].trim()] = v[1].trim();
+ return acc;
+ }, {});
+}
+
+// Open a new window with a given |origin|, loaded with COEP:credentialless. The
+// new document will execute any scripts sent toward the token it returns.
+const newCredentiallessWindow = (origin) => {
+ const main_document_token = token();
+ const url = origin + executor_path + coep_credentialless +
+ `&uuid=${main_document_token}`;
+ const context = window.open(url);
+ add_completion_callback(() => w.close());
+ return main_document_token;
+};
+
+// Create a new iframe, loaded with COEP:credentialless.
+// The new document will execute any scripts sent toward the token it returns.
+const newCredentiallessIframe = (parent_token, child_origin) => {
+ const sub_document_token = token();
+ const iframe_url = child_origin + executor_path + coep_credentialless +
+ `&uuid=${sub_document_token}`;
+ send(parent_token, `
+ let iframe = document.createElement("iframe");
+ iframe.src = "${iframe_url}";
+ document.body.appendChild(iframe);
+ `)
+ return sub_document_token;
+};
+
+// A common interface for building the 4 type of execution contexts:
+// It outputs: [
+// - The token to communicate with the environment.
+// - A promise resolved when the environment encounters an error.
+// ]
+const environments = {
+ document: headers => {
+ const tok = token();
+ const url = window.origin + executor_path + headers + `&uuid=${tok}`;
+ const context = window.open(url);
+ add_completion_callback(() => context.close());
+ return [tok, new Promise(resolve => {})];
+ },
+
+ dedicated_worker: headers => {
+ const tok = token();
+ const url = window.origin + executor_worker_path + headers + `&uuid=${tok}`;
+ const context = new Worker(url);
+ return [tok, new Promise(resolve => context.onerror = resolve)];
+ },
+
+ shared_worker: headers => {
+ const tok = token();
+ const url = window.origin + executor_worker_path + headers + `&uuid=${tok}`;
+ const context = new SharedWorker(url);
+ return [tok, new Promise(resolve => context.onerror = resolve)];
+ },
+
+ service_worker: headers => {
+ const tok = token();
+ const url = window.origin + executor_worker_path + headers + `&uuid=${tok}`;
+ const scope = url; // Generate a one-time scope for service worker.
+ const error = new Promise(resolve => {
+ navigator.serviceWorker.register(url, {scope: scope})
+ .then(registration => {
+ add_completion_callback(() => registration.unregister());
+ }, /* catch */ resolve);
+ });
+ return [tok, error];
+ },
+};
diff --git a/testing/web-platform/tests/html/cross-origin-embedder-policy/credentialless/resources/iframeTest.js b/testing/web-platform/tests/html/cross-origin-embedder-policy/credentialless/resources/iframeTest.js
new file mode 100644
index 0000000000..501a864d46
--- /dev/null
+++ b/testing/web-platform/tests/html/cross-origin-embedder-policy/credentialless/resources/iframeTest.js
@@ -0,0 +1,85 @@
+// One document embeds another in an iframe. Both are loaded from the network.
+// Depending on the response headers:
+// - Cross-Origin-Embedder-Policy (COEP)
+// - Cross-Origin-Resource-Policy (CORP)
+// The child must load or must be blocked.
+//
+// What to do for:
+// - COEP:credentialless
+// - COEP:credentialless-on-children
+// is currently an active open question. This test will be updated/completed
+// later.
+
+// 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 same_origin = get_host_info().HTTPS_ORIGIN;
+const cross_origin = get_host_info().HTTPS_REMOTE_ORIGIN;
+
+// Open a new window loaded with the given |headers|. The new document will
+// execute any script sent toward the token it returns.
+const newWindow = (headers) => {
+ const executor_token = token();
+ const url = same_origin + executor_path + headers + `&uuid=${executor_token}`;
+ const w = window.open(url);
+ add_completion_callback(() => w.close());
+ return executor_token;
+};
+
+const EXPECT_LOAD = "load";
+const EXPECT_BLOCK = "block";
+
+// Load in iframe. Control both the parent and the child headers. Check whether
+// it loads or not.
+const iframeTest = function(
+ description,
+ parent_token,
+ child_origin,
+ child_headers,
+ expectation
+) {
+ promise_test_parallel(async test => {
+ const test_token = token();
+
+ const child_token = token();
+ const child_url = child_origin + executor_path + child_headers +
+ `&uuid=${child_token}`;
+
+ await send(parent_token, `
+ let iframe = document.createElement("iframe");
+ iframe.src = "${child_url}";
+ document.body.appendChild(iframe);
+ `);
+
+ await send(child_token, `
+ send("${test_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(test_token, "block"), expectation == EXPECT_BLOCK
+ ? 2000
+ : 6000
+ );
+
+ assert_equals(await receive(test_token), expectation);
+ }, description);
+}
+
+// A decorated version of iframeTest, adding CORP:cross-origin to the child.
+const iframeTestCORP = function() {
+ arguments[0] += ", CORP:cross-origin"; // description
+ arguments[3] += corp_cross_origin; // child_headers
+ iframeTest(...arguments);
+}
diff --git a/testing/web-platform/tests/html/cross-origin-embedder-policy/credentialless/resources/redirect_none_to_credentialless.py b/testing/web-platform/tests/html/cross-origin-embedder-policy/credentialless/resources/redirect_none_to_credentialless.py
new file mode 100644
index 0000000000..7f0c35e93e
--- /dev/null
+++ b/testing/web-platform/tests/html/cross-origin-embedder-policy/credentialless/resources/redirect_none_to_credentialless.py
@@ -0,0 +1,14 @@
+COEP_HEADER = b"|header(cross-origin-embedder-policy, credentialless)"
+CORP_HEADER = b"|header(cross-origin-resource-policy, cross-origin)"
+
+def main(request, response):
+ """
+ Causes a redirection that the initial response doesn't have credentialless and the
+ redirected response has credentialless.
+ """
+ response.status = 302
+
+ location = \
+ request.GET[b'redirectTo'] + b"&pipe=" + COEP_HEADER + CORP_HEADER
+
+ response.headers.set(b"Location", location)
diff --git a/testing/web-platform/tests/html/cross-origin-embedder-policy/credentialless/script.https.window.js b/testing/web-platform/tests/html/cross-origin-embedder-policy/credentialless/script.https.window.js
new file mode 100644
index 0000000000..96bf7b08db
--- /dev/null
+++ b/testing/web-platform/tests/html/cross-origin-embedder-policy/credentialless/script.https.window.js
@@ -0,0 +1,99 @@
+// META: script=/common/get-host-info.sub.js
+// META: script=/common/utils.js
+// META: script=/common/dispatcher/dispatcher.js
+// META: script=./resources/common.js
+
+window.onload = function() {
+ promise_test_parallel(async test => {
+ const same_origin = get_host_info().HTTPS_ORIGIN;
+ const cross_origin = get_host_info().HTTPS_REMOTE_ORIGIN;
+ const cookie_key = "coep_credentialless_script";
+ const cookie_same_origin = "same_origin";
+ const cookie_cross_origin = "cross_origin";
+
+ await Promise.all([
+ setCookie(same_origin, cookie_key, cookie_same_origin +
+ cookie_same_site_none),
+ setCookie(cross_origin, cookie_key, cookie_cross_origin +
+ cookie_same_site_none),
+ ]);
+
+ // One window with COEP:none. (control)
+ const w_control_token = token();
+ const w_control_url = same_origin + executor_path +
+ coep_none + `&uuid=${w_control_token}`
+ const w_control = window.open(w_control_url);
+ add_completion_callback(() => w_control.close());
+
+ // One window with COEP:credentialless. (experiment)
+ const w_credentialless_token = token();
+ const w_credentialless_url = same_origin + executor_path +
+ coep_credentialless + `&uuid=${w_credentialless_token}`;
+ const w_credentialless = window.open(w_credentialless_url);
+ add_completion_callback(() => w_credentialless.close());
+
+ let scriptTest = function(
+ description, origin, mode,
+ expected_cookies_control,
+ expected_cookies_credentialless)
+ {
+ promise_test_parallel(async test => {
+ const token_1 = token();
+ const token_2 = token();
+
+ send(w_control_token, `
+ let script = document.createElement("script");
+ script.src = "${showRequestHeaders(origin, token_1)}";
+ ${mode};
+ document.body.appendChild(script);
+ `);
+ send(w_credentialless_token, `
+ let script = document.createElement("script");
+ script.src = "${showRequestHeaders(origin, token_2)}";
+ ${mode};
+ document.body.appendChild(script);
+ `);
+
+ const headers_control = JSON.parse(await receive(token_1));
+ const headers_credentialless = JSON.parse(await receive(token_2));
+
+ assert_equals(parseCookies(headers_control)[cookie_key],
+ expected_cookies_control,
+ "coep:none => ");
+ assert_equals(parseCookies(headers_credentialless)[cookie_key],
+ expected_cookies_credentialless,
+ "coep:credentialless => ");
+ }, `script ${description}`)
+ };
+
+ // Same-origin request always contains Cookies:
+ scriptTest("same-origin + undefined",
+ same_origin, '',
+ cookie_same_origin,
+ cookie_same_origin);
+ scriptTest("same-origin + anonymous",
+ same_origin, 'script.crossOrigin="anonymous"',
+ cookie_same_origin,
+ cookie_same_origin);
+ scriptTest("same-origin + use-credentials",
+ same_origin, 'script.crossOrigin="use-credentials"',
+ cookie_same_origin,
+ cookie_same_origin);
+
+ // Cross-origin request contains cookies in the following cases:
+ // - COEP:credentialless is not set.
+ // - script.crossOrigin is `use-credentials`.
+ scriptTest("cross-origin + undefined",
+ cross_origin, '',
+ cookie_cross_origin,
+ undefined);
+ scriptTest("cross-origin + anonymous",
+ cross_origin, 'script.crossOrigin="anonymous"',
+ undefined,
+ undefined);
+ scriptTest("cross-origin + use-credentials",
+ cross_origin, 'script.crossOrigin="use-credentials"',
+ cookie_cross_origin,
+ cookie_cross_origin);
+ }, "Main");
+}
diff --git a/testing/web-platform/tests/html/cross-origin-embedder-policy/credentialless/service-worker-coep-credentialless-proxy.https.window.js b/testing/web-platform/tests/html/cross-origin-embedder-policy/credentialless/service-worker-coep-credentialless-proxy.https.window.js
new file mode 100644
index 0000000000..8ffea04325
--- /dev/null
+++ b/testing/web-platform/tests/html/cross-origin-embedder-policy/credentialless/service-worker-coep-credentialless-proxy.https.window.js
@@ -0,0 +1,85 @@
+// META: script=/common/get-host-info.sub.js
+// META: script=/common/utils.js
+// META: script=/common/dispatcher/dispatcher.js
+// META: script=./resources/common.js
+// META: script=/service-workers/service-worker/resources/test-helpers.sub.js
+
+const same_origin = get_host_info().HTTPS_ORIGIN;
+const cross_origin = get_host_info().HTTPS_REMOTE_ORIGIN;
+
+promise_test(async test => {
+ const this_token_1 = token();
+ const this_token_2 = token();
+
+ // Register a COEP:credentialless ServiceWorker.
+ const sw_token = token();
+ const sw_url =
+ executor_service_worker_path + coep_credentialless + `&uuid=${sw_token}`;
+ // Executors should be controlled by the service worker.
+ const scope = executor_path;
+ const sw_registration =
+ await service_worker_unregister_and_register(test, sw_url, scope);
+ test.add_cleanup(() => sw_registration.unregister());
+ await wait_for_state(test, sw_registration.installing, 'activated');
+
+ // Configure the ServiceWorker to proxy the fetch requests. Wait for the
+ // worker to be installed and activated.
+ send(sw_token, `
+ fetchHandler = event => {
+ if (!event.request.url.includes("/proxied"))
+ return;
+
+ send("${this_token_1}", "ServiceWorker: Proxying");
+
+ // Response with a cross-origin no-cors resource.
+ const url = "${cross_origin}" + "/common/blank.html";
+
+ event.respondWith(new Promise(async resolve => {
+ try {
+ let response = await fetch(url, {
+ mode: "no-cors",
+ credentials: "include"
+ });
+ send("${this_token_1}", "ServiceWorker: Fetch success");
+ resolve(response);
+ } catch (error) {
+ send("${this_token_1}", "ServiceWorker: Fetch failure");
+ resolve(new Response("", {status: 400}));
+ }
+ }));
+ }
+
+ await clients.claim();
+
+ send("${this_token_1}", serviceWorker.state);
+ `)
+ assert_equals(await receive(this_token_1), "activated");
+
+ // Create a COEP:credentialless document.
+ const document_token = environments["document"](coep_credentialless)[0];
+
+ // The document fetches a same-origin no-cors resource. The requests needs to
+ // be same-origin to be handled by the ServiceWorker.
+ send(document_token, `
+ try {
+ const response = await fetch("/proxied", { mode: "no-cors", });
+
+ send("${this_token_2}", "Document: Fetch success");
+ } catch (error) {
+ send("${this_token_2}", "Document: Fetch error");
+ }
+ `);
+
+ // The COEP:credentialless ServiceWorker is able to handle the cross-origin
+ // no-cors request, requested with credentials.
+ assert_equals(await receive(this_token_1), "ServiceWorker: Proxying");
+ assert_equals(await receive(this_token_1), "ServiceWorker: Fetch success");
+
+ // The COEP:credentialless Document is allowed by CORP to get it.
+ assert_equals(await receive(this_token_2), "Document: Fetch success");
+
+ // test.add_cleanup doesn't allow waiting for a promise. Unregistering a
+ // ServiceWorker is an asynchronous operation. It might not be completed on
+ // time for the next test. Do it here for extra flakiness safety.
+ await sw_registration.unregister()
+}, "COEP:credentialless ServiceWorker");
diff --git a/testing/web-platform/tests/html/cross-origin-embedder-policy/credentialless/service-worker-coep-none-proxy.https.window.js b/testing/web-platform/tests/html/cross-origin-embedder-policy/credentialless/service-worker-coep-none-proxy.https.window.js
new file mode 100644
index 0000000000..c2a6f227e4
--- /dev/null
+++ b/testing/web-platform/tests/html/cross-origin-embedder-policy/credentialless/service-worker-coep-none-proxy.https.window.js
@@ -0,0 +1,87 @@
+// META: script=/common/get-host-info.sub.js
+// META: script=/common/utils.js
+// META: script=/common/dispatcher/dispatcher.js
+// META: script=./resources/common.js
+// META: script=/service-workers/service-worker/resources/test-helpers.sub.js
+
+const same_origin = get_host_info().HTTPS_ORIGIN;
+const cross_origin = get_host_info().HTTPS_REMOTE_ORIGIN;
+
+promise_test(async test => {
+ const this_token_1 = token();
+ const this_token_2 = token();
+
+ // Register a COEP:none ServiceWorker.
+ const sw_token = token();
+ const sw_url = executor_service_worker_path + coep_none + `&uuid=${sw_token}`;
+ // Executors should be controlled by the service worker.
+ const scope = executor_path;
+ const sw_registration =
+ await service_worker_unregister_and_register(test, sw_url, scope);
+ test.add_cleanup(() => sw_registration.unregister());
+ await wait_for_state(test, sw_registration.installing, 'activated');
+
+ // Configure the ServiceWorker to proxy the fetch requests. Wait for the
+ // worker to be installed and activated.
+ send(sw_token, `
+ fetchHandler = event => {
+ if (!event.request.url.includes("/proxied"))
+ return;
+
+ send("${this_token_1}", "ServiceWorker: Proxying");
+
+ // Response with a cross-origin no-cors resource.
+ const url = "${cross_origin}" + "/common/blank.html";
+
+ event.respondWith(new Promise(async resolve => {
+ try {
+ let response = await fetch(url, {
+ mode: "no-cors",
+ credentials: "include"
+ });
+ send("${this_token_1}", "ServiceWorker: Fetch success");
+ resolve(response);
+ } catch (error) {
+ send("${this_token_1}", "ServiceWorker: Fetch failure");
+ resolve(new Response("", {status: 400}));
+ }
+ }));
+ }
+
+ await clients.claim();
+
+ send("${this_token_1}", serviceWorker.state);
+ `)
+ assert_equals(await receive(this_token_1), "activated");
+
+ // Create a COEP:credentialless document.
+ const document_token = environments["document"](coep_credentialless)[0];
+
+ // The document fetches a same-origin no-cors resource. The requests needs to
+ // be same-origin to be handled by the ServiceWorker.
+ send(document_token, `
+ try {
+ const response = await fetch("/proxied", {
+ mode: "no-cors",
+ credentials: "include"
+ });
+
+ send("${this_token_2}", "Document: Fetch success");
+ } catch (error) {
+ send("${this_token_2}", "Document: Fetch error");
+ }
+ `);
+
+ // The COEP:unsafe-none ServiceWorker is able to handle the cross-origin
+ // no-cors request, requested with credentials.
+ assert_equals(await receive(this_token_1), "ServiceWorker: Proxying");
+ assert_equals(await receive(this_token_1), "ServiceWorker: Fetch success");
+
+ // However, the COEP:credentialless Document is disallowed by CORP to get it.
+ assert_equals(await receive(this_token_2), "Document: Fetch error");
+
+ // test.add_cleanup doesn't allow waiting for a promise. Unregistering a
+ // ServiceWorker is an asynchronous operation. It might not be completed on
+ // time for the next test. Do it here for extra flakiness safety.
+ await sw_registration.unregister()
+}, "COEP:unsafe-none ServiceWorker");
diff --git a/testing/web-platform/tests/html/cross-origin-embedder-policy/credentialless/service-worker.https.window.js b/testing/web-platform/tests/html/cross-origin-embedder-policy/credentialless/service-worker.https.window.js
new file mode 100644
index 0000000000..4fc0061c57
--- /dev/null
+++ b/testing/web-platform/tests/html/cross-origin-embedder-policy/credentialless/service-worker.https.window.js
@@ -0,0 +1,113 @@
+// META: timeout=long
+// META: script=/common/get-host-info.sub.js
+// META: script=/common/utils.js
+// META: script=/common/dispatcher/dispatcher.js
+// META: script=/service-workers/service-worker/resources/test-helpers.sub.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_service_worker";
+const cookie_same_origin = "same_origin";
+const cookie_cross_origin = "cross_origin";
+
+promise_test(async t => {
+ await Promise.all([
+ setCookie(same_origin, cookie_key, cookie_same_origin +
+ cookie_same_site_none),
+ setCookie(cross_origin, cookie_key, cookie_cross_origin +
+ cookie_same_site_none),
+ ]);
+
+ // One iframe with COEP:none. (control)
+ const w_control_token = token();
+ const w_control_url = same_origin + executor_path +
+ coep_none + `&uuid=${w_control_token}`
+ const w_control = document.createElement("iframe");
+ w_control.src = w_control_url;
+ document.body.appendChild(w_control);
+
+ // One iframe with COEP:credentialless. (experiment)
+ const w_credentialless_token = token();
+ const w_credentialless_url = same_origin + executor_path +
+ coep_credentialless + `&uuid=${w_credentialless_token}`;
+ const w_credentialless = document.createElement("iframe");
+ w_credentialless.src = w_credentialless_url;
+ document.body.appendChild(w_credentialless);
+
+ const serviceWorkerTest = function(
+ description, origin, coep_for_worker,
+ expected_cookies_control,
+ expected_cookies_credentialless)
+ {
+ promise_test(async test => {
+ // Create workers for both window.
+ const control_worker_token = token();
+ const credentialless_worker_token = token();
+
+ const w_control_worker_src = same_origin + executor_worker_path +
+ coep_for_worker + `&uuid=${control_worker_token}`;
+ const w_control_worker_reg =
+ await service_worker_unregister_and_register(
+ test, w_control_worker_src, w_control_url);
+
+ const w_credentialless_worker_src = same_origin + executor_worker_path +
+ coep_for_worker + `&uuid=${credentialless_worker_token}`;
+ const w_credentialless_worker_reg =
+ await service_worker_unregister_and_register(
+ test, w_credentialless_worker_src, w_credentialless_url);
+
+ // Fetch resources from the workers.
+ const control_request_token = token();
+ const credentialless_request_token = token();
+ const control_request_url = showRequestHeaders(origin, control_request_token);
+ const credentialless_request_url = showRequestHeaders(origin, credentialless_request_token);
+ send(control_worker_token, `
+ fetch("${control_request_url}", {
+ mode: 'no-cors',
+ credentials: 'include'
+ })
+ `);
+ send(credentialless_worker_token, `
+ fetch("${credentialless_request_url}", {
+ mode: 'no-cors',
+ credentials: 'include'
+ })
+ `);
+
+ // Retrieve the resource request headers.
+ const headers_control = JSON.parse(await receive(control_request_token));
+ const headers_credentialless = JSON.parse(await receive(credentialless_request_token));
+
+ assert_equals(parseCookies(headers_control)[cookie_key],
+ expected_cookies_control,
+ "coep:none => ");
+ assert_equals(parseCookies(headers_credentialless)[cookie_key],
+ expected_cookies_credentialless,
+ "coep:credentialless => ");
+
+ w_control_worker_reg.unregister();
+ w_credentialless_worker_reg.unregister();
+ }, `fetch ${description}`)
+ };
+
+ serviceWorkerTest("same-origin",
+ same_origin, coep_none,
+ cookie_same_origin,
+ cookie_same_origin);
+
+ serviceWorkerTest("same-origin + credentialless worker",
+ same_origin, coep_credentialless,
+ cookie_same_origin,
+ cookie_same_origin);
+
+ serviceWorkerTest("cross-origin",
+ cross_origin, coep_none,
+ cookie_cross_origin,
+ cookie_cross_origin);
+
+ serviceWorkerTest("cross-origin + credentialless worker",
+ cross_origin, coep_credentialless,
+ undefined,
+ undefined);
+})
diff --git a/testing/web-platform/tests/html/cross-origin-embedder-policy/credentialless/shared-worker.https.window.js b/testing/web-platform/tests/html/cross-origin-embedder-policy/credentialless/shared-worker.https.window.js
new file mode 100644
index 0000000000..5e259a5acc
--- /dev/null
+++ b/testing/web-platform/tests/html/cross-origin-embedder-policy/credentialless/shared-worker.https.window.js
@@ -0,0 +1,93 @@
+// META: variant=?request_origin=same_origin&worker_coep=none&window_coep=none
+// META: variant=?request_origin=same_origin&worker_coep=none&window_coep=credentialless
+// META: variant=?request_origin=same_origin&worker_coep=credentialless&window_coep=none
+// META: variant=?request_origin=same_origin&worker_coep=credentialless&window_coep=credentialless
+// META: variant=?request_origin=cross_origin&worker_coep=none&window_coep=none
+// META: variant=?request_origin=cross_origin&worker_coep=none&window_coep=credentialless
+// META: variant=?request_origin=cross_origin&worker_coep=credentialless&window_coep=none
+// META: variant=?request_origin=cross_origin&worker_coep=credentialless&window_coep=credentialless
+// META: timeout=long
+// META: script=/common/get-host-info.sub.js
+// META: script=/common/utils.js
+// META: script=/common/dispatcher/dispatcher.js
+// META: script=./resources/common.js
+
+// Test description:
+// Request a resource from a SharedWorker. Check the request's cookies.
+//
+// Variant:
+// - The Window COEP policy: none or credentialless.
+// - The SharedWorker COEP policy: none or credentialless.
+// - The SharedWorker's request URL origin: same-origin or cross-origin.
+
+const same_origin = get_host_info().HTTPS_ORIGIN;
+const cross_origin = get_host_info().HTTPS_REMOTE_ORIGIN;
+const cookie_key = token();
+const cookie_same_origin = "same_origin";
+const cookie_cross_origin = "cross_origin";
+
+const variants = new URLSearchParams(window.location.search);
+const window_coep = variants.get('window_coep') == 'none'
+ ? coep_none
+ : coep_credentialless;
+const worker_coep = variants.get('worker_coep') == 'none'
+ ? coep_none
+ : coep_credentialless;
+const request_origin = variants.get('request_origin') == 'same-origin'
+ ? same_origin
+ : cross_origin;
+
+// When using COEP:credentialless: cross-origin no-cors request do not include
+// credentials. Note: This must not depend on the window's COEP policy.
+const worker_expected_cookie =
+ request_origin == same_origin
+ ? cookie_same_origin
+ : (worker_coep == coep_credentialless
+ ? undefined
+ : cookie_cross_origin);
+
+// From a JSON representing the `response` HTTP headers key-values, return the
+// cookie corresponding to the `cookie_key`.
+const get_cookie = (response) => {
+ const headers_credentialless = JSON.parse(response);
+ return parseCookies(headers_credentialless)[cookie_key];
+}
+
+promise_test(async test => {
+ // 0. Populate cookies for the two origins.
+ await Promise.all([
+ setCookie(same_origin, cookie_key, cookie_same_origin +
+ cookie_same_site_none),
+ setCookie(cross_origin, cookie_key, cookie_cross_origin +
+ cookie_same_site_none),
+ ]);
+
+ // 1. Create the popup with the `window_coep` COEP policy:
+ const popup = environments.document(window_coep)[0];
+
+ // 2. Create the worker with the `worker_coep` COEP policy:
+ const worker_token = token();
+ const worker_error = token();
+ const worker_src = same_origin + executor_worker_path + worker_coep +
+ `&uuid=${worker_token}`;
+ send(popup, `
+ let worker = new SharedWorker("${worker_src}", {});
+ worker.onerror = () => {
+ send("${worker_error}", "Worker blocked");
+ }
+ `);
+
+ // 3. Request the resource from the worker, with the `request_origin` origin.
+ const request_token = token();
+ const request_url = showRequestHeaders(request_origin, request_token);
+ send(worker_token, `fetch("${request_url}", {
+ mode: 'no-cors',
+ credentials: 'include',
+ })`);
+ const request_cookie = await Promise.race([
+ receive(worker_error),
+ receive(request_token).then(get_cookie)
+ ]);
+
+ assert_equals(request_cookie, worker_expected_cookie);
+})
diff --git a/testing/web-platform/tests/html/cross-origin-embedder-policy/credentialless/video.https.window.js b/testing/web-platform/tests/html/cross-origin-embedder-policy/credentialless/video.https.window.js
new file mode 100644
index 0000000000..0410b48564
--- /dev/null
+++ b/testing/web-platform/tests/html/cross-origin-embedder-policy/credentialless/video.https.window.js
@@ -0,0 +1,53 @@
+// META: script=/common/get-host-info.sub.js
+// META: script=/common/utils.js
+// META: script=/common/dispatcher/dispatcher.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 = "coep_credentialless_image";
+const cookie_same_origin = "same_origin";
+const cookie_cross_origin = "cross_origin";
+
+promise_setup(async test => {
+ await Promise.all([
+ setCookie(same_origin, cookie_key, cookie_same_origin +
+ cookie_same_site_none),
+ setCookie(cross_origin, cookie_key, cookie_cross_origin +
+ cookie_same_site_none),
+ ]);
+}, "Setup cookies");
+
+const videoTest = function(description, origin, mode, expected_cookie) {
+ promise_test(async test => {
+ const video_token = token();
+
+ let video = document.createElement("video");
+ video.src = showRequestHeaders(origin, video_token);
+ video.autoplay = true;
+ if (mode)
+ video.crossOrigin = mode;
+ document.body.appendChild(video);
+
+ const headers = JSON.parse(await receive(video_token));
+
+ assert_equals(parseCookies(headers)[cookie_key], expected_cookie);
+ }, `video ${description}`)
+};
+
+// Same-origin request always contains Cookies:
+videoTest("same-origin + undefined",
+ same_origin, undefined, cookie_same_origin);
+videoTest("same-origin + anonymous",
+ same_origin, 'anonymous', cookie_same_origin);
+videoTest("same-origin + use-credentials",
+ same_origin, 'use-credentials', cookie_same_origin);
+
+// Cross-origin request contains cookies, only when sent in CORS mode, using
+// crossOrigin = "use-credentials".
+videoTest("cross-origin + undefined",
+ cross_origin, '', undefined);
+videoTest("cross-origin + anonymous",
+ cross_origin, 'anonymous', undefined);
+videoTest("cross-origin + use-credentials",
+ cross_origin, 'use-credentials', cookie_cross_origin);
diff --git a/testing/web-platform/tests/html/cross-origin-embedder-policy/credentialless/video.https.window.js.headers b/testing/web-platform/tests/html/cross-origin-embedder-policy/credentialless/video.https.window.js.headers
new file mode 100644
index 0000000000..68fde79c91
--- /dev/null
+++ b/testing/web-platform/tests/html/cross-origin-embedder-policy/credentialless/video.https.window.js.headers
@@ -0,0 +1 @@
+Cross-Origin-Embedder-Policy:credentialless
diff --git a/testing/web-platform/tests/html/cross-origin-embedder-policy/credentialless/websocket.https.window.js b/testing/web-platform/tests/html/cross-origin-embedder-policy/credentialless/websocket.https.window.js
new file mode 100644
index 0000000000..2fe8d02879
--- /dev/null
+++ b/testing/web-platform/tests/html/cross-origin-embedder-policy/credentialless/websocket.https.window.js
@@ -0,0 +1,77 @@
+// META: timeout=long
+// META: script=/common/get-host-info.sub.js
+// META: script=/common/utils.js
+// META: script=/common/dispatcher/dispatcher.js
+// META: script=./resources/common.js
+
+promise_test_parallel(async test => {
+ const same_origin = get_host_info().HTTPS_ORIGIN;
+ const cross_origin = get_host_info().HTTPS_REMOTE_ORIGIN;
+ const cookie_key = "coep_credentialless_websocket";
+ const cookie_same_origin = "same_origin";
+ const cookie_cross_origin = "cross_origin";
+
+ await Promise.all([
+ setCookie(same_origin, cookie_key, cookie_same_origin +
+ cookie_same_site_none),
+ setCookie(cross_origin, cookie_key, cookie_cross_origin +
+ cookie_same_site_none),
+ ]);
+
+ // One window with COEP:none. (control)
+ const w_control_token = token();
+ const w_control_url = same_origin + executor_path +
+ coep_none + `&uuid=${w_control_token}`
+ const w_control = window.open(w_control_url);
+ add_completion_callback(() => w_control.close());
+
+ // One window with COEP:credentialless. (experiment)
+ const w_credentialless_token = token();
+ const w_credentialless_url = same_origin + executor_path +
+ coep_credentialless + `&uuid=${w_credentialless_token}`;
+ const w_credentialless = window.open(w_credentialless_url);
+ add_completion_callback(() => w_credentialless.close());
+
+ let WebSocketTest = function(
+ description, origin,
+ expected_cookies_control,
+ expected_cookies_credentialless)
+ {
+ promise_test_parallel(async test => {
+ const token_1 = token();
+ const token_2 = token();
+
+ const origin_for_websocket = origin.replace("https", "wss");
+
+ send(w_control_token, `
+ var ws = new WebSocket("${showRequestHeaders(origin_for_websocket, token_1)}");
+ `);
+
+ send(w_credentialless_token, `
+ var ws = new WebSocket("${showRequestHeaders(origin_for_websocket, token_2)}");
+ `);
+
+ const headers_control = JSON.parse(await receive(token_1));
+ const headers_credentialless = JSON.parse(await receive(token_2));
+
+ assert_equals(parseCookies(headers_control)[cookie_key],
+ expected_cookies_control,
+ "coep:none => ");
+ assert_equals(parseCookies(headers_credentialless)[cookie_key],
+ expected_cookies_credentialless,
+ "coep:credentialless => ");
+ }, `WebSocket ${description}`)
+ };
+
+ // Same-origin request always contains Cookies:
+ WebSocketTest("same-origin",
+ same_origin,
+ cookie_same_origin,
+ cookie_same_origin);
+
+ // Cross-origin request also always contains Cookies:
+ WebSocketTest("cross-origin",
+ cross_origin,
+ cookie_cross_origin,
+ cookie_cross_origin);
+}, "Main");
diff --git a/testing/web-platform/tests/html/cross-origin-embedder-policy/cross-origin-isolated-permission-iframe.https.window.js b/testing/web-platform/tests/html/cross-origin-embedder-policy/cross-origin-isolated-permission-iframe.https.window.js
new file mode 100644
index 0000000000..9190303206
--- /dev/null
+++ b/testing/web-platform/tests/html/cross-origin-embedder-policy/cross-origin-isolated-permission-iframe.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=./credentialless/resources/common.js
+// META: script=./resources/common.js
+
+const cors_coep_headers = coep_require_corp + corp_cross_origin;
+const same_origin = get_host_info().HTTPS_ORIGIN;
+const cross_origin = get_host_info().HTTPS_REMOTE_ORIGIN;
+
+const newIframe = async (
+ test,
+ parent_origin,
+ parent_headers,
+ child_origin,
+ child_headers
+) => {
+ const [future_child, future_error] =
+ await createIsolatedFrame(parent_origin, parent_headers);
+ future_error.then(test.unreached_func('cannot create isolated iframe.'));
+
+ const child = await future_child;
+ add_completion_callback(() => child.remove());
+
+ const grand_child_token = token();
+ const grand_child = child.contentDocument.createElement('iframe');
+ grand_child.src = child_origin + executor_path + child_headers +
+ `&uuid=${grand_child_token}`;
+ child.contentDocument.body.appendChild(grand_child);
+ add_completion_callback(() => grand_child.remove());
+
+ return grand_child_token;
+};
+
+const childFrameIsCrossOriginIsolated = async (
+ test,
+ child_origin,
+ parent_permission_coi
+) => {
+ let parent_headers = cors_coep_headers;
+ const child_headers = cors_coep_headers;
+ if (parent_permission_coi !== undefined) {
+ // Escape right parenthesis in WPT pipe:
+ parent_permission_coi = parent_permission_coi.replace(')', '\\)');
+ parent_headers += `|header(permissions-policy,` +
+ `cross-origin-isolated=${parent_permission_coi})`;
+ }
+ const parent_origin = same_origin;
+ const iframe = await newIframe(
+ test,
+ parent_origin,
+ parent_headers,
+ child_origin,
+ child_headers);
+ return IsCrossOriginIsolated(iframe);
+}
+
+const generate_iframe_test = async (origin, isolation, expect_coi) => {
+ promise_test_parallel(async (test) => {
+ const isCrossOriginIsolated =
+ await childFrameIsCrossOriginIsolated(test, origin, isolation);
+ assert_equals(isCrossOriginIsolated, expect_coi)
+ }, `iframe (origin: ${origin}) cross origin isolated (${isolation}) ` +
+ `permission test`);
+}
+
+generate_iframe_test(same_origin, undefined, true);
+generate_iframe_test(same_origin, '*', true);
+generate_iframe_test(same_origin, 'self', true);
+generate_iframe_test(same_origin, '()', false);
+generate_iframe_test(cross_origin, undefined, false);
+generate_iframe_test(cross_origin, '*', false);
+generate_iframe_test(cross_origin, 'self', false);
+generate_iframe_test(cross_origin, '()', false); \ No newline at end of file
diff --git a/testing/web-platform/tests/html/cross-origin-embedder-policy/cross-origin-isolated-permission-iframe.https.window.js.headers b/testing/web-platform/tests/html/cross-origin-embedder-policy/cross-origin-isolated-permission-iframe.https.window.js.headers
new file mode 100644
index 0000000000..3b7825def9
--- /dev/null
+++ b/testing/web-platform/tests/html/cross-origin-embedder-policy/cross-origin-isolated-permission-iframe.https.window.js.headers
@@ -0,0 +1,2 @@
+Cross-Origin-Embedder-Policy: require-corp
+Cross-Origin-Opener-Policy: same-origin \ No newline at end of file
diff --git a/testing/web-platform/tests/html/cross-origin-embedder-policy/cross-origin-isolated-permission-worker.https.window.js b/testing/web-platform/tests/html/cross-origin-embedder-policy/cross-origin-isolated-permission-worker.https.window.js
new file mode 100644
index 0000000000..d9431cdb50
--- /dev/null
+++ b/testing/web-platform/tests/html/cross-origin-embedder-policy/cross-origin-isolated-permission-worker.https.window.js
@@ -0,0 +1,170 @@
+// META: script=/common/get-host-info.sub.js
+// META: script=/common/utils.js
+// META: script=/common/dispatcher/dispatcher.js
+// META: script=./credentialless/resources/common.js
+// META: script=./resources/common.js
+
+const cors_coep_headers = coep_require_corp + corp_cross_origin;
+const same_origin = get_host_info().HTTPS_ORIGIN;
+const cross_origin = get_host_info().HTTPS_REMOTE_ORIGIN;
+const dedicatedWorkerPostMessage = `
+ self.addEventListener('message', (e) => {
+ e.data.port.postMessage(self.crossOriginIsolated);
+ });
+`;
+
+const postMessageIsWorkerCrossOriginIsolated = async (
+ test,
+ frame,
+ worker_url
+) => {
+ const worker = new frame.contentWindow.Worker(worker_url);
+ const mc = new MessageChannel();
+ worker.postMessage({port: mc.port2}, [mc.port2]);
+ worker.onerror = test.unreached_func('cannot create dedicated worker');
+ return (await new Promise(r => mc.port1.onmessage = r)).data;
+}
+
+const isDataDedicatedWorkerCrossOriginIsolated = async (
+ test,
+ parent_headers
+) => {
+ const [future_child, future_error] =
+ await createIsolatedFrame('', parent_headers);
+ future_error.then(test.unreached_func('cannot create isolated iframe'));
+
+ const child = await future_child;
+ add_completion_callback(() => child.remove());
+
+ const worker_url =
+ `data:application/javascript;base64,${btoa(dedicatedWorkerPostMessage)}`;
+ return postMessageIsWorkerCrossOriginIsolated(test, child, worker_url);
+}
+
+const isBlobURLDedicatedWorkerCrossOriginIsolated = async(
+ test,
+ parent_headers
+) => {
+ const [future_child, future_error] =
+ await createIsolatedFrame("", parent_headers);
+ future_error.then(test.unreached_func('cannot create isolated iframe'));
+
+ const child = await future_child;
+ add_completion_callback(() => child.remove());
+
+ const blob =
+ new Blob([dedicatedWorkerPostMessage], {type: 'text/plaintext'});
+ const workerURL = URL.createObjectURL(blob);
+ return postMessageIsWorkerCrossOriginIsolated(test, child, workerURL)
+}
+
+const isHTTPSDedicatedWorkerCrossOriginIsolated = async(
+ test,
+ parent_headers
+) => {
+ const [future_child, future_error] =
+ await createIsolatedFrame("", parent_headers);
+ future_error.then(test.unreached_func('cannot create isolated iframe'));
+
+ const child = await future_child;
+ add_completion_callback(() => child.remove());
+
+ const worker_token = token();
+ const workerURL =
+ `${executor_worker_path}${cors_coep_headers}&uuid=${worker_token}`;
+ const worker = new child.contentWindow.Worker(workerURL);
+ return IsCrossOriginIsolated(worker_token);
+}
+
+const sharedWorkerIsCrossOriginIsolated = async(
+ test,
+ withCoopCoep
+) => {
+ const [worker, future_error] =
+ environments.shared_worker(withCoopCoep ? cors_coep_headers : "");
+ future_error.then(test.unreached_func('cannot create shared worker.'));
+ return IsCrossOriginIsolated(worker);
+}
+
+const serviceWorkerIsCrossOriginIsolated = async(
+ test,
+ withCoopCoep
+) => {
+ const [worker, future_error] =
+ environments.service_worker(withCoopCoep ? cors_coep_headers : "");
+ future_error.then(test.unreached_func('cannot create service worker.'));
+ return IsCrossOriginIsolated(worker);
+}
+
+const dedicatedWorkerIsCrossOriginIsolated = async (
+ test,
+ scheme,
+ parent_permission_coi
+) => {
+ let parent_headers = cors_coep_headers;
+ if (parent_permission_coi !== undefined) {
+ // Escape right parenthesis in WPT cors_coep_headers:
+ parent_permission_coi = parent_permission_coi.replace(')', '\\)');
+ parent_headers += `|header(permissions-policy,` +
+ `cross-origin-isolated=${parent_permission_coi})`;
+ }
+ switch (scheme) {
+ case 'https':
+ return isHTTPSDedicatedWorkerCrossOriginIsolated(test, parent_headers);
+ case 'data':
+ return isDataDedicatedWorkerCrossOriginIsolated(test, parent_headers);
+ case 'blob':
+ return isBlobURLDedicatedWorkerCrossOriginIsolated(test, parent_headers);
+ default:
+ assert_unreached("wrong scheme for dedicated worker test.");
+ }
+}
+
+const generate_shared_worker_test = async (withCoopCoep, expected) => {
+ promise_test_parallel(async (test) => {
+ const isCrossOriginIsolated =
+ await sharedWorkerIsCrossOriginIsolated(test, withCoopCoep);
+ assert_equals(isCrossOriginIsolated, expected)
+ }, `shared_worker (withCoopCoep: ${withCoopCoep}) ` +
+ `cross origin isolated permission test`);
+}
+
+const generate_dedicated_worker_test = async (
+ scheme,
+ parent_permission_coi,
+ expected
+) => {
+ promise_test_parallel(async (test) => {
+ const isCrossOriginIsolated =
+ await dedicatedWorkerIsCrossOriginIsolated(test, scheme, parent_permission_coi);
+ assert_equals(isCrossOriginIsolated, expected)
+ }, `dedicated_worker (scheme: ${scheme}) cross origin ` +
+ `isolated (${parent_permission_coi}) permission test`);
+}
+
+const generate_service_worker_test = async (withCoopCoep, expected) => {
+ promise_test_parallel(async (test) => {
+ const isCrossOriginIsolated =
+ await serviceWorkerIsCrossOriginIsolated(test, withCoopCoep);
+ assert_equals(isCrossOriginIsolated, expected)
+ }, `service_worker (withCoopCoep: ${withCoopCoep}) ` +
+ `cross origin isolated permission test`);
+}
+
+generate_shared_worker_test(false, false);
+generate_shared_worker_test(true, true);
+
+generate_dedicated_worker_test('https', undefined, true);
+generate_dedicated_worker_test('https', '*', true);
+generate_dedicated_worker_test('https', 'self', true);
+generate_dedicated_worker_test('https', '()', false);
+generate_dedicated_worker_test('data', undefined, false);
+generate_dedicated_worker_test('data', '*', false);
+generate_dedicated_worker_test('data', 'self', false);
+generate_dedicated_worker_test('blob', undefined, true);
+generate_dedicated_worker_test('blob', '*', true);
+generate_dedicated_worker_test('blob', 'self', true);
+generate_dedicated_worker_test('blob', '()', false);
+
+generate_service_worker_test(false, false);
+generate_service_worker_test(true, true); \ No newline at end of file
diff --git a/testing/web-platform/tests/html/cross-origin-embedder-policy/cross-origin-isolated-permission-worker.https.window.js.headers b/testing/web-platform/tests/html/cross-origin-embedder-policy/cross-origin-isolated-permission-worker.https.window.js.headers
new file mode 100644
index 0000000000..3b7825def9
--- /dev/null
+++ b/testing/web-platform/tests/html/cross-origin-embedder-policy/cross-origin-isolated-permission-worker.https.window.js.headers
@@ -0,0 +1,2 @@
+Cross-Origin-Embedder-Policy: require-corp
+Cross-Origin-Opener-Policy: same-origin \ No newline at end of file
diff --git a/testing/web-platform/tests/html/cross-origin-embedder-policy/data.https.html b/testing/web-platform/tests/html/cross-origin-embedder-policy/data.https.html
new file mode 100644
index 0000000000..f2878dfc54
--- /dev/null
+++ b/testing/web-platform/tests/html/cross-origin-embedder-policy/data.https.html
@@ -0,0 +1,20 @@
+<!doctype html>
+<meta charset=utf-8>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/script-factory.js"></script>
+<div id=log></div>
+<script>
+async_test(t => {
+ window.addEventListener("message", t.step_func_done(({ data }) => {
+ assert_equals(data.id, "");
+ assert_equals(data.origin, "null");
+ assert_false(data.sameOriginNoCORPSuccess); // This is effectively a no-op for this test
+ assert_true(data.crossOriginNoCORPFailure, "Cross-origin without CORP did not fail");
+ }));
+ const frame = document.createElement("iframe");
+ t.add_cleanup(() => frame.remove());
+ frame.src = `data:text/html,<script>${encodeURIComponent(createScript("null", window.origin))}<\/script>`;
+ document.body.append(frame);
+}, "Cross-Origin-Embedder-Policy and data: URLs");
+</script>
diff --git a/testing/web-platform/tests/html/cross-origin-embedder-policy/data.https.html.headers b/testing/web-platform/tests/html/cross-origin-embedder-policy/data.https.html.headers
new file mode 100644
index 0000000000..6604450991
--- /dev/null
+++ b/testing/web-platform/tests/html/cross-origin-embedder-policy/data.https.html.headers
@@ -0,0 +1 @@
+Cross-Origin-Embedder-Policy: require-corp
diff --git a/testing/web-platform/tests/html/cross-origin-embedder-policy/dedicated-worker-cache-storage.https.html b/testing/web-platform/tests/html/cross-origin-embedder-policy/dedicated-worker-cache-storage.https.html
new file mode 100644
index 0000000000..2c97e6f875
--- /dev/null
+++ b/testing/web-platform/tests/html/cross-origin-embedder-policy/dedicated-worker-cache-storage.https.html
@@ -0,0 +1,128 @@
+<!doctype html>
+<html>
+<title> Check enforcement of COEP in a DedicatedWorker using CacheStorage. </title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+<script>
+// See also: ./shared-worker-cache-storage.https.html
+
+function remote(path) {
+ const REMOTE_ORIGIN = get_host_info().HTTPS_REMOTE_ORIGIN;
+ return new URL(path, REMOTE_ORIGIN);
+}
+
+const iframe_path = "./resources/iframe.html?pipe=";
+const dedicated_worker_path = "./universal-worker.js?pipe=";
+const ressource_path = "/images/blue.png?pipe=";
+
+const coep_header= {
+ "coep-none" : "",
+ "coep-require-corp" : "|header(Cross-Origin-Embedder-Policy,require-corp)",
+}
+
+const corp_header = {
+ "corp-undefined": "",
+ "corp-cross-origin": "|header(Cross-Origin-Resource-Policy,cross-origin)",
+}
+
+// Check enforcement of COEP in a DedicatedWorker using CacheStorage.
+//
+// 1) Fetch a response from a document with COEP:none. Store it in the
+// CacheStorage. The response is cross-origin without any CORS header.
+// 2) From an iframe, start a DedicatedWorker and try to retrieve the response
+// from the CacheStorage.
+//
+// Test parameters:
+// - |iframe_coep| the COEP header of the iframe's document response
+// - |worker_coep| the COEP header of the DedicatedWorker's script response.
+// - |response_corp| the CORP header of the response.
+//
+// Test expectations:
+// |result|
+// - "success" when the worker is able to fetch the response from the
+// CacheStorage,
+// - "failure" when the worker is not able to fetch the response from the
+// CacheStorage, and
+// - "error" when it is unable to create a worker.
+// https://mikewest.github.io/corpp/#initialize-embedder-policy-for-global
+function check(
+ // Test parameters:
+ iframe_coep,
+ worker_coep,
+ response_corp,
+
+ // Test expectations:
+ result) {
+
+ promise_test(async (t) => {
+ // 1) Fetch a response from a document with COEP:none. Store it in the
+ // CacheStorage. The response is cross-origin without any CORS header.
+ const resource_path = ressource_path + corp_header[response_corp];
+ const resource_url = remote(resource_path);
+ const fetch_request = new Request(resource_url, {mode: 'no-cors'});
+ const cache = await caches.open('v1');
+ const fetch_response = await fetch(fetch_request);
+ await cache.put(fetch_request, fetch_response);
+
+ // 2) From an iframe, start a DedicatedWorker and try to retrieve the
+ // response from the CacheStorage.
+ const worker_url = dedicated_worker_path + coep_header[worker_coep];
+ const worker_eval = `
+ (async function() {
+ const cache = await caches.open('v1');
+ const request = new Request('${resource_url}', {
+ mode: 'no-cors'
+ });
+ try {
+ const response = await cache.match(request);
+ postMessage('success');
+ } catch(error) {
+ postMessage('failure');
+ }
+ })()
+ `;
+
+ const iframe_url = iframe_path + coep_header[iframe_coep];
+ const iframe_eval = `
+ (async function() {
+ const w = new Worker('${worker_url}');
+ const worker_response = new Promise(resolve => w.onmessage = resolve);
+ w.onerror = () => parent.postMessage('error');
+ w.postMessage(\`${worker_eval}\`);
+ const response = await worker_response;
+ parent.postMessage(response.data);
+ })();
+ `;
+
+ const iframe = document.createElement("iframe");
+ t.add_cleanup(() => iframe.remove());
+ iframe.src = iframe_url;
+ const iframe_loaded = new Promise(resolve => iframe.onload = resolve);
+ document.body.appendChild(iframe);
+ await iframe_loaded;
+
+ const iframe_response = new Promise(resolve => {
+ window.addEventListener("message", resolve);
+ })
+ iframe.contentWindow.postMessage(iframe_eval);
+
+ const {data} = await iframe_response;
+ assert_equals(data, result);
+ }, `${iframe_coep} ${worker_coep} ${response_corp}`)
+}
+
+// -----------------------------------------------------------------------------
+// iframe_coep , worker_coep , response_corp , loaded
+// -----------------------------------------------------------------------------
+check("coep-none" , "coep-none" , "corp-cross-origin" , "success");
+check("coep-none" , "coep-none" , "corp-undefined" , "success");
+check("coep-none" , "coep-require-corp" , "corp-cross-origin" , "success");
+check("coep-none" , "coep-require-corp" , "corp-undefined" , "failure");
+check("coep-require-corp" , "coep-none" , "corp-cross-origin" , "error");
+check("coep-require-corp" , "coep-none" , "corp-undefined" , "error");
+check("coep-require-corp" , "coep-require-corp" , "corp-cross-origin" , "success");
+check("coep-require-corp" , "coep-require-corp" , "corp-undefined" , "failure");
+
+</script>
+</html>
diff --git a/testing/web-platform/tests/html/cross-origin-embedder-policy/dedicated-worker.https.html b/testing/web-platform/tests/html/cross-origin-embedder-policy/dedicated-worker.https.html
new file mode 100644
index 0000000000..1ba624181c
--- /dev/null
+++ b/testing/web-platform/tests/html/cross-origin-embedder-policy/dedicated-worker.https.html
@@ -0,0 +1,214 @@
+<!doctype html>
+<title>COEP and dedicated worker</title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script src="/common/get-host-info.sub.js"></script>
+<script src="resources/worker-support.js"></script>
+<body>
+<script>
+
+const targetUrl = resolveUrl("/common/blank.html", {
+ host: get_host_info().REMOTE_HOST,
+}).href;
+
+function workerUrl(options) {
+ return resolveUrl("resources/dedicated-worker.js", options);
+}
+
+async function createWorker(t, url, options) {
+ const { ownerCoep, workerOptions } = options || {};
+
+ const frameUrl = resolveUrl("/common/blank.html", {
+ coep: ownerCoep,
+ });
+ const frame = await withIframe(t, frameUrl);
+
+ return new frame.contentWindow.Worker(url, workerOptions);
+}
+
+promise_test(async (t) => {
+ const worker = await createWorker(t, workerUrl());
+ worker.onerror = t.unreached_func('Worker.onerror should not be called');
+
+ worker.postMessage(targetUrl);
+
+ const result = await waitForMessage(worker);
+ assert_equals(result.data, 'LOADED');
+}, 'COEP: none worker in COEP: none frame');
+
+promise_test(async (t) => {
+ const worker = await createWorker(t, workerUrl(), {
+ ownerCoep: "require-corp",
+ });
+ await new Promise(resolve => {
+ worker.onerror = resolve;
+ });
+}, 'COEP: none worker in COEP: require-corp frame');
+
+promise_test(async (t) => {
+ const worker = await createWorker(t, workerUrl({ coep: "require-corp" }));
+ worker.onerror = t.unreached_func('Worker.onerror should not be called');
+
+ worker.postMessage(targetUrl);
+
+ const result = await waitForMessage(worker);
+ assert_equals(result.data, 'FAILED');
+}, 'COEP: require-corp worker in COEP: none frame');
+
+promise_test(async (t) => {
+ const worker = await createWorker(t, workerUrl({ coep: "require-corp" }), {
+ ownerCoep: "require-corp",
+ });
+ worker.onerror = t.unreached_func('Worker.onerror should not be called');
+
+ worker.postMessage(targetUrl);
+
+ const result = await waitForMessage(worker);
+ assert_equals(result.data, 'FAILED');
+}, 'COEP: require-corp worker in COEP: require-corp frame');
+
+promise_test(async (t) => {
+ const worker = await createWorker(t, workerUrl(), {
+ workerOptions: { type: 'module' },
+ });
+ worker.onerror = t.unreached_func('Worker.onerror should not be called');
+
+ worker.postMessage(targetUrl);
+
+ const result = await waitForMessage(worker);
+ assert_equals(result.data, 'LOADED');
+}, 'COEP: none module worker in COEP: none frame');
+
+promise_test(async (t) => {
+ const worker = await createWorker(t, workerUrl(), {
+ ownerCoep: "require-corp",
+ workerOptions: { type: 'module' },
+ });
+ await new Promise(resolve => {
+ worker.onerror = resolve;
+ });
+}, 'COEP: none module worker in COEP: require-corp frame');
+
+promise_test(async (t) => {
+ const worker = await createWorker(t, workerUrl({ coep: "require-corp" }), {
+ workerOptions: { type: 'module' },
+ });
+ worker.onerror = t.unreached_func('Worker.onerror should not be called');
+
+ worker.postMessage(targetUrl);
+
+ const result = await waitForMessage(worker);
+ assert_equals(result.data, 'FAILED');
+}, 'COEP: require-corp module worker in COEP: none frame');
+
+promise_test(async (t) => {
+ const worker = await createWorker(t, workerUrl({ coep: "require-corp" }), {
+ ownerCoep: "require-corp",
+ workerOptions: { type: 'module' },
+ });
+ worker.onerror = t.unreached_func('Worker.onerror should not be called');
+
+ worker.postMessage(targetUrl);
+
+ const result = await waitForMessage(worker);
+ assert_equals(result.data, 'FAILED');
+}, 'COEP: require-corp module worker in COEP: require-corp frame');
+
+promise_test(async (t) => {
+ const url = await createLocalUrl(t, {
+ url: workerUrl(),
+ creatorCoep: "require-corp",
+ scheme: "blob",
+ });
+
+ const worker = await createWorker(t, url, { ownerCoep: "require-corp" });
+ worker.onerror = t.unreached_func('Worker.onerror should not be called');
+
+ worker.postMessage(targetUrl);
+
+ const result = await waitForMessage(worker);
+ assert_equals(result.data, 'FAILED');
+}, "COEP: worker inherits COEP for blob URL.");
+
+promise_test(async (t) => {
+ const url = await createLocalUrl(t, {
+ url: workerUrl(),
+ creatorCoep: "require-corp",
+ scheme: "blob",
+ });
+
+ const worker = await createWorker(t, url);
+ worker.onerror = t.unreached_func('Worker.onerror should not be called');
+
+ worker.postMessage(targetUrl);
+
+ const result = await waitForMessage(worker);
+ assert_equals(result.data, 'FAILED');
+}, "COEP: worker inherits COEP from blob URL creator, not owner.");
+
+promise_test(async (t) => {
+ const url = await createLocalUrl(t, {
+ url: workerUrl(),
+ creatorCoep: "require-corp",
+ scheme: "data",
+ });
+
+ const worker = await createWorker(t, url, { ownerCoep: "require-corp" });
+ worker.onerror = t.unreached_func('Worker.onerror should not be called');
+
+ worker.postMessage(targetUrl);
+
+ const result = await waitForMessage(worker);
+ assert_equals(result.data, 'FAILED');
+}, "COEP: worker inherits COEP for data URL.");
+
+promise_test(async (t) => {
+ const url = await createLocalUrl(t, {
+ url: workerUrl(),
+ creatorCoep: "require-corp",
+ scheme: "data",
+ });
+
+ const worker = await createWorker(t, url);
+ worker.onerror = t.unreached_func('Worker.onerror should not be called');
+
+ worker.postMessage(targetUrl);
+
+ const result = await waitForMessage(worker);
+ assert_equals(result.data, 'LOADED');
+}, "COEP: worker inherits COEP from owner, not data URL creator.");
+
+promise_test(async (t) => {
+ const url = await createLocalUrl(t, {
+ url: workerUrl(),
+ creatorCoep: "require-corp",
+ scheme: "filesystem",
+ });
+
+ const worker = await createWorker(t, url, { ownerCoep: "require-corp" });
+ worker.onerror = t.unreached_func('Worker.onerror should not be called');
+
+ worker.postMessage(targetUrl);
+
+ const result = await waitForMessage(worker);
+ assert_equals(result.data, 'FAILED');
+}, "COEP: worker inherits COEP for filesystem URL.");
+
+promise_test(async (t) => {
+ const url = await createLocalUrl(t, {
+ url: workerUrl(),
+ creatorCoep: "require-corp",
+ scheme: "filesystem",
+ });
+
+ const worker = await createWorker(t, url);
+ worker.onerror = t.unreached_func('Worker.onerror should not be called');
+
+ worker.postMessage(targetUrl);
+
+ const result = await waitForMessage(worker);
+ assert_equals(result.data, 'FAILED');
+}, "COEP: worker inherits COEP from filesystem URL creator, not owner.");
+
+</script>
+</body>
diff --git a/testing/web-platform/tests/html/cross-origin-embedder-policy/header-parsing.https.html b/testing/web-platform/tests/html/cross-origin-embedder-policy/header-parsing.https.html
new file mode 100644
index 0000000000..7a25eed51f
--- /dev/null
+++ b/testing/web-platform/tests/html/cross-origin-embedder-policy/header-parsing.https.html
@@ -0,0 +1,85 @@
+<!DOCTYPE html>
+<html>
+ <meta charset="utf-8">
+ <meta name="timeout" content="long">
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+<body>
+<script>
+'use strict';
+function createIframe(t, values) {
+ const parent = document.createElement('iframe');
+ const child = document.createElement('iframe');
+ const params = values.map((value) => {
+ const percentEncodedValue = typeof value === "object" ? value.percentEncoded : encodeURIComponent(value);
+ return `value=${percentEncodedValue}`;
+ });
+ parent.setAttribute('src', `resources/empty-coep.py?${params.join("&")}`);
+ document.body.appendChild(parent);
+ t.add_cleanup(() => parent.remove());
+
+ return new Promise((resolve, reject) => {
+ parent.onload = resolve;
+ parent.onerror = () =>
+ reject(new Error(`failed to load from ${parent.src}`));
+ })
+ .then(() => {
+ child.setAttribute('src', '/common/blank.html');
+ parent.contentDocument.body.appendChild(child);
+ return new Promise((resolve) => {
+ child.onload = resolve;
+ child.onerror = () =>
+ reject(new Error(`failed to load from ${child.src}`));
+ });
+ })
+ .then(() => child);
+}
+
+[
+ [],
+ [''],
+ ['jibberish'],
+ [{ percentEncoded: 'require%FFcorp' }], // non-ASCII byte
+ ['require-corp;'],
+ ['\u000brequire-corp\u000b'], // vertical tab
+ ['\u000crequire-corp\u000c'], // form feed
+ ['\u000drequire-corp\u000d'], // carriage return
+ ['Require-corp'],
+ ['"require-corp"'], // HTTP structured header "string" item
+ [':cmVxdWlyZS1jb3Jw:'], // HTTP structured header "byte sequence" item
+ ['require-corp;\tfoo=bar'],
+ ['require-corp require-corp'],
+ ['require-corp,require-corp'],
+ ['require-corp', 'require-corp'],
+ ['', 'require-corp'],
+ ['require-corp', ''],
+].forEach((values) => {
+ promise_test((t) => {
+ return createIframe(t, values)
+ .then((child) => {
+ assert_not_equals(child.contentDocument, null);
+ });
+ }, 'navigation allowed for ' + JSON.stringify(values));
+});
+
+[
+ ['require-corp'],
+ [' require-corp '],
+ ['\trequire-corp\t'], // leading and trailing OWS is not part of the field-value per HTTP
+ [' \trequire-corp'],
+ ['require-corp\t '],
+ ['require-corp; foo=bar'],
+ ['require-corp;require-corp'],
+ ['require-corp; report-to="data:', '"'], // `require-corp; report-to="data:, "`
+
+].forEach((values) => {
+ promise_test((t) => {
+ return createIframe(t, values)
+ .then((child) => {
+ assert_equals(child.contentDocument, null);
+ });
+ }, 'navigation blocked for ' + JSON.stringify(values));
+});
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/cross-origin-embedder-policy/iframe-history-none-require-corp.https.html b/testing/web-platform/tests/html/cross-origin-embedder-policy/iframe-history-none-require-corp.https.html
new file mode 100644
index 0000000000..0e7ef8108b
--- /dev/null
+++ b/testing/web-platform/tests/html/cross-origin-embedder-policy/iframe-history-none-require-corp.https.html
@@ -0,0 +1,54 @@
+<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/get-host-info.sub.js"></script>
+<script>
+
+promise_test(async test => {
+ // TODO(arthursonzogni): Consider switching toward another message passing
+ // API like:
+ // /common/dispatcher/dispatcher.js
+ const bc = new BroadcastChannel(token());
+ const futureMessage = () => {
+ return new Promise(resolve => {
+ bc.onmessage = event => resolve(event.data);
+ });
+ };
+
+ const prefix = document.URL.substr(0, document.URL.lastIndexOf('/'))
+ const attribute = `?channelName=${bc.name}`;
+ const url_coep_none =
+ prefix + "/resources/navigate-none.sub.html" + attribute;
+ const url_coep_require_corp =
+ prefix + "/resources/navigate-require-corp.sub.html" + attribute;
+
+ const w = window.open();
+ test.add_cleanup(() => w.close());
+
+ // Navigate to COEP:unsafe-none.
+ w.location.href = url_coep_none;
+ assert_equals(await futureMessage(), "loaded");
+ assert_equals(w.location.href, url_coep_none);
+
+ // For unknown reasons so far. Waiting in between the different navigations
+ // avoids flakes.
+ await new Promise(resolve => test.step_timeout(resolve, 1000));
+
+ // Navigate to COEP:require-corp.
+ w.location.href = url_coep_require_corp;
+ assert_equals(await futureMessage(), "loaded");
+ assert_equals(w.location.href, url_coep_require_corp);
+
+ // For unknown reasons so far. Waiting in between the different navigations
+ // avoids flakes.
+ await new Promise(resolve => test.step_timeout(resolve, 1000));
+
+ // Navigate back to COEP:unsafe-none, using the history API.
+ // Note: `url_coep_none` already take the BFCache into account.
+ w.history.back();
+ assert_equals(await futureMessage(), "loaded");
+ assert_equals(w.location.href, url_coep_none);
+}, `"none" top-level: navigating a frame back from "require-corp" should succeed`);
+
+</script>
diff --git a/testing/web-platform/tests/html/cross-origin-embedder-policy/javascript.https.html b/testing/web-platform/tests/html/cross-origin-embedder-policy/javascript.https.html
new file mode 100644
index 0000000000..60edf00312
--- /dev/null
+++ b/testing/web-platform/tests/html/cross-origin-embedder-policy/javascript.https.html
@@ -0,0 +1,21 @@
+<!doctype html>
+<meta charset=utf-8>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+<script src="resources/script-factory.js"></script>
+<div id=log></div>
+<script>
+async_test(t => {
+ window.addEventListener("message", t.step_func_done(({ data }) => {
+ assert_equals(data.id, "");
+ assert_equals(data.origin, window.origin);
+ assert_true(data.sameOriginNoCORPSuccess);
+ assert_true(data.crossOriginNoCORPFailure, "Cross-origin without CORP did not fail");
+ }));
+ const frame = document.createElement("iframe");
+ t.add_cleanup(() => frame.remove());
+ frame.src = `javascript:${encodeURIComponent(createScript(window.origin, get_host_info().HTTPS_NOTSAMESITE_ORIGIN))}`;
+ document.body.append(frame);
+}, "Cross-Origin-Embedder-Policy and javascript: URLs");
+</script>
diff --git a/testing/web-platform/tests/html/cross-origin-embedder-policy/javascript.https.html.headers b/testing/web-platform/tests/html/cross-origin-embedder-policy/javascript.https.html.headers
new file mode 100644
index 0000000000..6604450991
--- /dev/null
+++ b/testing/web-platform/tests/html/cross-origin-embedder-policy/javascript.https.html.headers
@@ -0,0 +1 @@
+Cross-Origin-Embedder-Policy: require-corp
diff --git a/testing/web-platform/tests/html/cross-origin-embedder-policy/meta-http-equiv.https.html b/testing/web-platform/tests/html/cross-origin-embedder-policy/meta-http-equiv.https.html
new file mode 100644
index 0000000000..d35df3135a
--- /dev/null
+++ b/testing/web-platform/tests/html/cross-origin-embedder-policy/meta-http-equiv.https.html
@@ -0,0 +1,20 @@
+<!doctype html>
+<meta http-equiv="Cross-Origin-Embedder-Policy" content="require-corp"><!-- should not be supported -->
+<title>Cross-Origin-Embedder-Policy in &lt;meta http-equiv></title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<div id=log></div>
+<script>
+async_test(t => {
+ const frame = document.createElement("iframe");
+ t.add_cleanup(() => frame.remove());
+ frame.src = "/common/blank.html";
+ document.body.append(frame);
+ assert_equals(frame.contentDocument.URL, "about:blank");
+ assert_equals(frame.contentDocument.body.localName, "body");
+ frame.onload = t.step_func_done(() => {
+ assert_equals(frame.contentDocument.URL, `${location.protocol}//${location.host}/common/blank.html`);
+ assert_equals(frame.contentDocument.body.localName, "body");
+ });
+}, `<meta http-equiv="Cross-Origin-Embedder-Policy" content="require-corp"> top-level: navigating a frame to "none" should not fail`);
+</script>
diff --git a/testing/web-platform/tests/html/cross-origin-embedder-policy/multi-globals/current/current.html b/testing/web-platform/tests/html/cross-origin-embedder-policy/multi-globals/current/current.html
new file mode 100644
index 0000000000..e6261f8388
--- /dev/null
+++ b/testing/web-platform/tests/html/cross-origin-embedder-policy/multi-globals/current/current.html
@@ -0,0 +1,3 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>Current page</title>
diff --git a/testing/web-platform/tests/html/cross-origin-embedder-policy/multi-globals/current/current.html.headers b/testing/web-platform/tests/html/cross-origin-embedder-policy/multi-globals/current/current.html.headers
new file mode 100644
index 0000000000..6604450991
--- /dev/null
+++ b/testing/web-platform/tests/html/cross-origin-embedder-policy/multi-globals/current/current.html.headers
@@ -0,0 +1 @@
+Cross-Origin-Embedder-Policy: require-corp
diff --git a/testing/web-platform/tests/html/cross-origin-embedder-policy/multi-globals/current/worker.js b/testing/web-platform/tests/html/cross-origin-embedder-policy/multi-globals/current/worker.js
new file mode 100644
index 0000000000..44103842a4
--- /dev/null
+++ b/testing/web-platform/tests/html/cross-origin-embedder-policy/multi-globals/current/worker.js
@@ -0,0 +1 @@
+postMessage('current');
diff --git a/testing/web-platform/tests/html/cross-origin-embedder-policy/multi-globals/incumbent/incumbent.html b/testing/web-platform/tests/html/cross-origin-embedder-policy/multi-globals/incumbent/incumbent.html
new file mode 100644
index 0000000000..d8bd1ae2c0
--- /dev/null
+++ b/testing/web-platform/tests/html/cross-origin-embedder-policy/multi-globals/incumbent/incumbent.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Incumbent page</title>
+
+<iframe src="../current/current.html" id="c"></iframe>
+
+<script>
+ const current = document.querySelector("#c").contentWindow;
+
+ window.hello = () => {
+ const worker = new current.Worker('worker.js');
+ worker.onmessage = e => { parent.postMessage(e.data, '*'); }
+ };
+</script>
diff --git a/testing/web-platform/tests/html/cross-origin-embedder-policy/multi-globals/incumbent/incumbent.html.headers b/testing/web-platform/tests/html/cross-origin-embedder-policy/multi-globals/incumbent/incumbent.html.headers
new file mode 100644
index 0000000000..6604450991
--- /dev/null
+++ b/testing/web-platform/tests/html/cross-origin-embedder-policy/multi-globals/incumbent/incumbent.html.headers
@@ -0,0 +1 @@
+Cross-Origin-Embedder-Policy: require-corp
diff --git a/testing/web-platform/tests/html/cross-origin-embedder-policy/multi-globals/incumbent/worker.js b/testing/web-platform/tests/html/cross-origin-embedder-policy/multi-globals/incumbent/worker.js
new file mode 100644
index 0000000000..03f02a8690
--- /dev/null
+++ b/testing/web-platform/tests/html/cross-origin-embedder-policy/multi-globals/incumbent/worker.js
@@ -0,0 +1 @@
+postMessage('incumbent');
diff --git a/testing/web-platform/tests/html/cross-origin-embedder-policy/multi-globals/worker.js b/testing/web-platform/tests/html/cross-origin-embedder-policy/multi-globals/worker.js
new file mode 100644
index 0000000000..fcc521e313
--- /dev/null
+++ b/testing/web-platform/tests/html/cross-origin-embedder-policy/multi-globals/worker.js
@@ -0,0 +1 @@
+postMessage('entry');
diff --git a/testing/web-platform/tests/html/cross-origin-embedder-policy/multi-globals/workers-coep-report.https.html b/testing/web-platform/tests/html/cross-origin-embedder-policy/multi-globals/workers-coep-report.https.html
new file mode 100644
index 0000000000..e1f16a61e1
--- /dev/null
+++ b/testing/web-platform/tests/html/cross-origin-embedder-policy/multi-globals/workers-coep-report.https.html
@@ -0,0 +1,49 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<title>Multiple globals for Worker constructor: COEP reports</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<!-- This is the entry global -->
+
+<iframe src="incumbent/incumbent.html"></iframe>
+<button onclick="" id="button">Hello</button>
+
+<script>
+async function observeReports(global) {
+ const reports = [];
+ const observer = new global.ReportingObserver((rs) => {
+ for (const r of rs) {
+ reports.push(r.toJSON());
+ }
+ });
+ observer.observe();
+
+ // Wait 5000ms for reports to settle.
+ await new Promise(r => step_timeout(r, 5000));
+ return reports;
+}
+
+async_test((t) => {
+ onload = t.step_func(() => {
+ Promise.all([
+ observeReports(window),
+ observeReports(frames[0]),
+ observeReports(frames[0].frames[0])
+ ]).then(t.step_func_done(([entry, incumbent, current]) => {
+ assert_equals(entry.length, 0);
+ assert_equals(incumbent.length, 0);
+ assert_equals(current.length, 1);
+ const report = current[0];
+ assert_equals(report.type, 'coep');
+ assert_equals(report.url, new URL('current/current.html', location.href).href);
+ assert_equals(report.body.type, 'worker initialization');
+ assert_equals(report.body.blockedURL, new URL('current/worker.js', location.href).href);
+ assert_equals(report.body.disposition, 'enforce');
+ }));
+
+ frames[0].hello();
+ });
+ onmessage = t.unreached_func('worker should have been blocked by COEP');
+});
+</script>
diff --git a/testing/web-platform/tests/html/cross-origin-embedder-policy/multi-globals/workers-coep-report.https.html.headers b/testing/web-platform/tests/html/cross-origin-embedder-policy/multi-globals/workers-coep-report.https.html.headers
new file mode 100644
index 0000000000..6604450991
--- /dev/null
+++ b/testing/web-platform/tests/html/cross-origin-embedder-policy/multi-globals/workers-coep-report.https.html.headers
@@ -0,0 +1 @@
+Cross-Origin-Embedder-Policy: require-corp
diff --git a/testing/web-platform/tests/html/cross-origin-embedder-policy/no-secure-context.html b/testing/web-platform/tests/html/cross-origin-embedder-policy/no-secure-context.html
new file mode 100644
index 0000000000..6e1573cd64
--- /dev/null
+++ b/testing/web-platform/tests/html/cross-origin-embedder-policy/no-secure-context.html
@@ -0,0 +1,19 @@
+<!doctype html>
+<meta charset=utf-8>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+<div id=log></div>
+<script>
+async_test(t => {
+ const frame = document.body.appendChild(document.createElement("iframe"));
+ t.add_cleanup(() => frame.remove());
+ frame.src = get_host_info().HTTP_NOTSAMESITE_ORIGIN + new URL("resources/iframe.html", location).pathname;
+ window.onmessage = t.step_func_done(({ data }) => {
+ assert_equals(data, "success");
+ });
+ frame.onload = t.step_func(() => {
+ frame.contentWindow.postMessage("parent.postMessage('success', '*');", "*");
+ });
+}, "COEP requires a secure context");
+</script>
diff --git a/testing/web-platform/tests/html/cross-origin-embedder-policy/no-secure-context.html.headers b/testing/web-platform/tests/html/cross-origin-embedder-policy/no-secure-context.html.headers
new file mode 100644
index 0000000000..6604450991
--- /dev/null
+++ b/testing/web-platform/tests/html/cross-origin-embedder-policy/no-secure-context.html.headers
@@ -0,0 +1 @@
+Cross-Origin-Embedder-Policy: require-corp
diff --git a/testing/web-platform/tests/html/cross-origin-embedder-policy/non-initial-about-blank.https.html b/testing/web-platform/tests/html/cross-origin-embedder-policy/non-initial-about-blank.https.html
new file mode 100644
index 0000000000..7fed1fe581
--- /dev/null
+++ b/testing/web-platform/tests/html/cross-origin-embedder-policy/non-initial-about-blank.https.html
@@ -0,0 +1,21 @@
+<!doctype html>
+<meta charset=utf-8>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id=log></div>
+<script>
+async_test(t => {
+ const frame = document.createElement("iframe");
+ t.add_cleanup(() => frame.remove());
+ let i = 0;
+ frame.onload = t.step_func(() => {
+ i++;
+ assert_equals(frame.contentDocument.URL, "about:blank");
+ frame.src = "about:blank";
+ if (i == 2) {
+ t.done();
+ }
+ });
+ document.body.append(frame);
+}, "Cross-Origin-Embedder-Policy and about:blank");
+</script>
diff --git a/testing/web-platform/tests/html/cross-origin-embedder-policy/non-initial-about-blank.https.html.headers b/testing/web-platform/tests/html/cross-origin-embedder-policy/non-initial-about-blank.https.html.headers
new file mode 100644
index 0000000000..6604450991
--- /dev/null
+++ b/testing/web-platform/tests/html/cross-origin-embedder-policy/non-initial-about-blank.https.html.headers
@@ -0,0 +1 @@
+Cross-Origin-Embedder-Policy: require-corp
diff --git a/testing/web-platform/tests/html/cross-origin-embedder-policy/none-load-from-cache-storage.https.html b/testing/web-platform/tests/html/cross-origin-embedder-policy/none-load-from-cache-storage.https.html
new file mode 100644
index 0000000000..177ae8d11b
--- /dev/null
+++ b/testing/web-platform/tests/html/cross-origin-embedder-policy/none-load-from-cache-storage.https.html
@@ -0,0 +1,173 @@
+<!doctype html>
+<html>
+<title> Retrieve resources from CacheStorage with Cross-Origin-Embedder-Policy: require-corp</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+<script src="/service-workers/service-worker/resources/test-helpers.sub.js"></script>
+<script>
+
+/*
+ This document does NOT define the Cross-Origin-Embedder-Policy header.
+ Cross-Origin Embedder Policy Editor's draft: https://mikewest.github.io/corpp/
+
+ This test is retrieving same-origin and cross-origin resources from the
+ CacheStorage. The resources are generated from the ServiceWorker or from the
+ network with the header Cross-Origin-Resource-Policy being one of:
+ - 'same-origin'
+ - 'cross-origin'
+ - <undefined>
+*/
+
+promise_test(async (t) => {
+ const SCOPE = new URL(location.href).pathname;
+ const SCRIPT =
+ 'resources/sw-store-to-cache-storage.js?' +
+ `pipe=header(service-worker-allowed,${SCOPE})`;
+
+ const reg = await service_worker_unregister_and_register(t, SCRIPT, SCOPE);
+ add_completion_callback(() => reg.unregister());
+ await new Promise(resolve => {
+ navigator.serviceWorker.addEventListener('controllerchange', resolve);
+ });
+}, 'setting up');
+
+function remote(path) {
+ const REMOTE_ORIGIN = get_host_info().HTTPS_REMOTE_ORIGIN;
+ return new URL(path, REMOTE_ORIGIN);
+}
+
+function local(path) {
+ return new URL(path, location.origin);
+}
+
+// Send a message to the currently active ServiceWorker and wait for its
+// response.
+function executeCommandInServiceWorker(command) {
+ return new Promise(resolve => {
+ navigator.serviceWorker.addEventListener('message', e => resolve(e.data));
+ navigator.serviceWorker.controller.postMessage(command);
+ });
+}
+
+// Try loading an image from a |response|. Return a Promise resolving or
+// rejecting depending on the image loading result.
+const loadFailure = {name: "Image.onerror"};
+function readImageFromResponse(response) {
+ return new Promise((resolve, reject) => {
+ const img = document.createElement("img");
+ img.onload = resolve.bind(this, "");
+ img.onerror = reject.bind(this, loadFailure);
+ response.blob().then(blob => {
+ img.src = URL.createObjectURL(blob);
+ document.body.appendChild(img);
+ })
+ })
+}
+
+const image_path = "/images/blue.png?pipe=";
+
+const corp_header = {
+ "":"",
+ "corp-undefined": "",
+ "corp-same-origin": "|header(Cross-Origin-Resource-Policy,same-origin)",
+ "corp-cross-origin": "|header(Cross-Origin-Resource-Policy,cross-origin)",
+}
+
+const cors_header = {
+ "":"",
+ "cors-disabled": "",
+ "cors-enabled": "|header(Access-Control-Allow-Origin,*)",
+}
+
+function test(
+ // Test parameters:
+ request_source, request_origin, request_mode, response_cors, response_corp,
+ // Test expectations:
+ response_stored, response_type) {
+ promise_test(async (t) => {
+ // 0. Start from an empty CacheStorage.
+ await caches.delete("v1");
+
+ // 1. Make the ServiceWorker to request the ressource and store it into the
+ // CacheStorage.
+ const path = image_path +
+ corp_header[response_corp] +
+ cors_header[response_cors];
+ const url = (request_origin === "same-origin" ? local : remote)(path);
+ const command = {
+ url: url.href,
+ mode: request_mode,
+ source: request_source,
+ };
+
+ assert_equals(await executeCommandInServiceWorker(command), response_stored);
+ if (response_stored === "not-stored") {
+ return;
+ }
+
+ // 2. Make this document to retrieve it from the CacheStorage.
+ const cache = await caches.open('v1');
+ const response = await cache.match(url);
+
+ assert_equals(response.type, response_type);
+
+ if (request_source === "service-worker") {
+ assert_equals("foo", await response.text());
+ return;
+ }
+
+ // Opaque response are not readable.
+ if (response_type === "opaque") {
+ await promise_rejects_exactly(t, loadFailure, readImageFromResponse(response));
+ return;
+ }
+
+ await readImageFromResponse(response);
+ }, `Fetch ${request_origin} ${request_mode} ${response_cors} ${response_corp} from ${request_source} and CacheStorage.`)
+}
+
+// Responses generated from the ServiceWorker.
+{
+ test("service-worker", "cross-origin", "cors", "", "", "stored", "default");
+ test("service-worker", "cross-origin", "no-cors", "", "", "stored", "default");
+ test("service-worker", "same-origin", "cors", "", "", "stored", "default");
+ test("service-worker", "same-origin", "no-cors", "", "", "stored", "default");
+}
+
+// Responses generated from a same-origin server.
+{
+ const t = test.bind(this, "network", "same-origin");
+ t("cors", "cors-disabled", "corp-cross-origin", "stored", "basic");
+ t("cors", "cors-disabled", "corp-same-origin", "stored", "basic");
+ t("cors", "cors-disabled", "corp-undefined", "stored", "basic");
+ t("cors", "cors-enabled", "corp-cross-origin", "stored", "basic");
+ t("cors", "cors-enabled", "corp-same-origin", "stored", "basic");
+ t("cors", "cors-enabled", "corp-undefined", "stored", "basic");
+ t("no-cors", "cors-disabled", "corp-cross-origin", "stored", "basic");
+ t("no-cors", "cors-disabled", "corp-same-origin", "stored", "basic");
+ t("no-cors", "cors-disabled", "corp-undefined", "stored", "basic");
+ t("no-cors", "cors-enabled", "corp-cross-origin", "stored", "basic");
+ t("no-cors", "cors-enabled", "corp-same-origin", "stored", "basic");
+ t("no-cors", "cors-enabled", "corp-undefined", "stored", "basic");
+}
+
+// Responses generated from a cross-origin server.
+{
+ const t = test.bind(this, "network", "cross-origin");
+ t("cors", "cors-disabled", "corp-cross-origin", "not-stored");
+ t("cors", "cors-disabled", "corp-same-origin", "not-stored");
+ t("cors", "cors-disabled", "corp-undefined", "not-stored");
+ t("cors", "cors-enabled", "corp-cross-origin", "stored", "cors");
+ t("cors", "cors-enabled", "corp-same-origin", "stored", "cors");
+ t("cors", "cors-enabled", "corp-undefined", "stored", "cors");
+ t("no-cors", "cors-disabled", "corp-cross-origin", "stored", "opaque");
+ t("no-cors", "cors-disabled", "corp-same-origin", "not-stored");
+ t("no-cors", "cors-disabled", "corp-undefined", "stored", "opaque");
+ t("no-cors", "cors-enabled", "corp-cross-origin", "stored", "opaque");
+ t("no-cors", "cors-enabled", "corp-same-origin", "not-stored");
+ t("no-cors", "cors-enabled", "corp-undefined", "stored", "opaque");
+}
+
+</script>
+</html>
diff --git a/testing/web-platform/tests/html/cross-origin-embedder-policy/none-sw-from-none.https.html b/testing/web-platform/tests/html/cross-origin-embedder-policy/none-sw-from-none.https.html
new file mode 100644
index 0000000000..6d2cf0f64f
--- /dev/null
+++ b/testing/web-platform/tests/html/cross-origin-embedder-policy/none-sw-from-none.https.html
@@ -0,0 +1,89 @@
+<!doctype html>
+<html>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script src="/common/get-host-info.sub.js"></script>
+<script src="/service-workers/service-worker/resources/test-helpers.sub.js"></script>
+<script>
+const SCOPE = new URL(location.href).pathname;
+const SCRIPT =
+ 'resources/sw.js?' +
+ `pipe=header(service-worker-allowed,${SCOPE})`;
+
+function remote(path) {
+ const REMOTE_ORIGIN = get_host_info().HTTPS_REMOTE_ORIGIN;
+ return new URL(path, REMOTE_ORIGIN + '/html/cross-origin-embedder-policy/');
+}
+
+promise_test(async (t) => {
+ const reg = await service_worker_unregister_and_register(t, SCRIPT, SCOPE);
+ add_completion_callback(() => {
+ reg.unregister();
+ });
+ await new Promise(resolve => {
+ navigator.serviceWorker.addEventListener('controllerchange', resolve);
+ });
+}, 'setting up');
+
+promise_test(async (t) => {
+ await fetch('resources/nothing-same-origin-corp.txt', {mode: 'no-cors'});
+}, 'making a same-origin request for CORP: same-origin');
+
+promise_test(async (t) => {
+ await fetch('/common/blank.html', {mode: 'no-cors'});
+}, 'making a same-origin request for no CORP');
+
+promise_test(async (t) => {
+ await fetch('resources/nothing-cross-origin-corp.js', {mode: 'no-cors'});
+}, 'making a same-origin request for CORP: cross-origin');
+
+promise_test(async (t) => {
+ await promise_rejects_js(
+ t, TypeError,
+ fetch(remote('resources/nothing-same-origin-corp.txt'), {mode: 'no-cors'}));
+}, 'making a cross-origin request for CORP: same-origin');
+
+promise_test(async (t) => {
+ await fetch(remote('/common/blank.html'), {mode: 'no-cors'});
+}, 'making a cross-origin request for no CORP');
+
+promise_test(async (t) => {
+ await fetch(
+ remote('resources/nothing-cross-origin-corp.js'),
+ {mode: 'no-cors'});
+}, 'making a cross-origin request for CORP: cross-origin');
+
+promise_test(async (t) => {
+ await promise_rejects_js(
+ t, TypeError,
+ fetch(remote('resources/nothing-same-origin-corp.txt?passthrough'),
+ {mode: 'no-cors'}));
+}, 'making a cross-origin request for CORP: same-origin [PASS THROUGH]');
+
+promise_test(async (t) => {
+ await fetch(remote('/common/blank.html?passthrough'), {mode: 'no-cors'});
+}, 'making a cross-origin request for no CORP [PASS THROUGH]');
+
+promise_test(async (t) => {
+ await fetch(
+ remote('resources/nothing-cross-origin-corp.js?passthrough'),
+ {mode: 'no-cors'});
+}, 'making a cross-origin request for CORP: cross-origin [PASS THROUGH]');
+
+promise_test(async (t) => {
+ await promise_rejects_js(
+ t, TypeError, fetch(remote('/common/blank.html'), {mode: 'cors'}));
+}, 'making a cross-origin request with CORS without ACAO');
+
+promise_test(async (t) => {
+ const URL = remote(
+ '/common/blank.html?pipe=header(access-control-allow-origin,*)');
+ await fetch(URL, {mode: 'cors'});
+}, 'making a cross-origin request with CORS');
+
+promise_test(async (t) => {
+ const URL = remote('/fetch/api/resources/preflight.py?allow_headers=hoge');
+ await fetch(URL, {mode: 'cors', headers: {'hoge': 'fuga'}});
+}, 'making a cross-origin request with CORS-preflight');
+</script>
+</html>
diff --git a/testing/web-platform/tests/html/cross-origin-embedder-policy/none-sw-from-require-corp.https.html b/testing/web-platform/tests/html/cross-origin-embedder-policy/none-sw-from-require-corp.https.html
new file mode 100644
index 0000000000..dc1209c40d
--- /dev/null
+++ b/testing/web-platform/tests/html/cross-origin-embedder-policy/none-sw-from-require-corp.https.html
@@ -0,0 +1,93 @@
+<!doctype html>
+<html>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script src="/common/get-host-info.sub.js"></script>
+<script src="/service-workers/service-worker/resources/test-helpers.sub.js"></script>
+<script>
+const SCOPE = new URL(location.href).pathname;
+const SCRIPT =
+ 'resources/sw.js?' +
+ `pipe=header(service-worker-allowed,${SCOPE})`;
+
+function remote(path) {
+ const REMOTE_ORIGIN = get_host_info().HTTPS_REMOTE_ORIGIN;
+ return new URL(path, REMOTE_ORIGIN + '/html/cross-origin-embedder-policy/');
+}
+
+promise_test(async (t) => {
+ const reg = await service_worker_unregister_and_register(t, SCRIPT, SCOPE);
+ add_completion_callback(() => {
+ reg.unregister();
+ });
+ await new Promise(resolve => {
+ navigator.serviceWorker.addEventListener('controllerchange', resolve);
+ });
+}, 'setting up');
+
+promise_test(async (t) => {
+ await fetch('resources/nothing-same-origin-corp.txt', {mode: 'no-cors'});
+}, 'making a same-origin request for CORP: same-origin');
+
+promise_test(async (t) => {
+ await fetch('/common/blank.html', {mode: 'no-cors'});
+}, 'making a same-origin request for no CORP');
+
+promise_test(async (t) => {
+ await fetch('resources/nothing-cross-origin-corp.js', {mode: 'no-cors'});
+}, 'making a same-origin request for CORP: cross-origin');
+
+promise_test(async (t) => {
+ await promise_rejects_js(
+ t, TypeError,
+ fetch(remote('resources/nothing-same-origin-corp.txt'), {mode: 'no-cors'}));
+}, 'making a cross-origin request for CORP: same-origin');
+
+promise_test(async (t) => {
+ await promise_rejects_js(
+ t, TypeError, fetch(remote('/common/blank.html'), {mode: 'no-cors'}));
+}, 'making a cross-origin request for no CORP');
+
+promise_test(async (t) => {
+ await fetch(
+ remote('resources/nothing-cross-origin-corp.js'),
+ {mode: 'no-cors'});
+}, 'making a cross-origin request for CORP: cross-origin');
+
+promise_test(async (t) => {
+ await promise_rejects_js(
+ t, TypeError,
+ fetch(remote('resources/nothing-same-origin-corp.txt?passthrough'),
+ {mode: 'no-cors'}));
+}, 'making a cross-origin request for CORP: same-origin [PASS THROUGH]');
+
+promise_test(async (t) => {
+ await promise_rejects_js(
+ t, TypeError,
+ fetch(remote('/common/blank.html?passthrough'), {mode: 'no-cors'}));
+}, 'making a cross-origin request for no CORP [PASS THROUGH]');
+
+promise_test(async (t) => {
+ await fetch(
+ remote('resources/nothing-cross-origin-corp.js?passthrough'),
+ {mode: 'no-cors'});
+}, 'making a cross-origin request for CORP: cross-origin [PASS THROUGH]');
+
+promise_test(async (t) => {
+ await promise_rejects_js(
+ t, TypeError, fetch(remote('/common/blank.html'), {mode: 'cors'}));
+}, 'making a cross-origin request with CORS without ACAO');
+
+promise_test(async (t) => {
+ const URL = remote(
+ '/common/blank.html?pipe=header(access-control-allow-origin,*)');
+ await fetch(URL, {mode: 'cors'});
+}, 'making a cross-origin request with CORS');
+
+promise_test(async (t) => {
+ const URL = remote('/fetch/api/resources/preflight.py?allow_headers=hoge');
+ await fetch(URL, {mode: 'cors', headers: {'hoge': 'fuga'}});
+}, 'making a cross-origin request with CORS-preflight');
+
+</script>
+</html>
diff --git a/testing/web-platform/tests/html/cross-origin-embedder-policy/none-sw-from-require-corp.https.html.headers b/testing/web-platform/tests/html/cross-origin-embedder-policy/none-sw-from-require-corp.https.html.headers
new file mode 100644
index 0000000000..8df98474b5
--- /dev/null
+++ b/testing/web-platform/tests/html/cross-origin-embedder-policy/none-sw-from-require-corp.https.html.headers
@@ -0,0 +1 @@
+cross-origin-embedder-policy: require-corp
diff --git a/testing/web-platform/tests/html/cross-origin-embedder-policy/none.https.html b/testing/web-platform/tests/html/cross-origin-embedder-policy/none.https.html
new file mode 100644
index 0000000000..cf9b34b4ca
--- /dev/null
+++ b/testing/web-platform/tests/html/cross-origin-embedder-policy/none.https.html
@@ -0,0 +1,91 @@
+<meta name="timeout" content="long">
+<title>Cross-Origin-Embedder-Policy header and nested navigable resource without such header</title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script src=/common/utils.js></script> <!-- Use token() to allow running tests in parallel -->
+<script src="/common/get-host-info.sub.js"></script>
+<div id=log></div>
+<script>
+
+const HOST = get_host_info();
+const BASE = new URL("resources", location).pathname;
+
+async_test(t => {
+ const frame = document.createElement("iframe");
+ t.add_cleanup(() => frame.remove());
+ frame.onload = t.step_func_done(() => {
+ assert_not_equals(frame.contentDocument, null);
+ });
+ frame.src = "/common/blank.html";
+ document.body.append(frame);
+ assert_equals(frame.contentDocument.body.localName, "body");
+}, `"none" top-level: navigating a frame to "none" should succeed`);
+
+async_test(t => {
+ const frame = document.createElement("iframe");
+ t.add_cleanup(() => frame.remove());
+ const blank = "/common/blank.html";
+ let firstNavOk = false;
+ frame.onload = t.step_func(() => {
+ if (!firstNavOk) {
+ assert_not_equals(frame.contentDocument, null);
+ firstNavOk = true;
+ } else {
+ assert_not_equals(frame.contentDocument, null);
+ assert_equals(frame.contentWindow.location.pathname, blank);
+ t.done();
+ }
+ });
+ frame.src = `resources/navigate-require-corp.sub.html?to=${blank}`;
+ document.body.append(frame);
+ assert_equals(frame.contentDocument.body.localName, "body");
+}, `"none" top-level: navigating a frame from "require-corp" to "none" should succeed`);
+
+async_test(t => {
+ let pageLoaded = false;
+ // TODO(arthursonzogni): Consider switching toward another message passing
+ // API like:
+ // /common/dispatcher/dispatcher.js
+ const bc = new BroadcastChannel(token());
+ let finished = false;
+ let doneCheck = _ => {
+ if (finished && pageLoaded) {
+ t.done();
+ }
+ }
+ bc.onmessage = t.step_func((event) => {
+ pageLoaded = true;
+ let payload = event.data;
+ assert_equals(payload, "loaded");
+
+ doneCheck();
+ });
+
+ const bc2 = new BroadcastChannel(token());
+ bc2.onmessage = t.step_func((event) => {
+ finished = true;
+ let payload = event.data;
+ assert_equals(payload, "loaded");
+
+ doneCheck();
+ });
+
+ const win = window.open(`resources/navigate-require-corp.sub.html?channelName=${bc.name}&to=navigate-none.sub.html?channelName=${bc2.name}`, "_blank", "noopener");
+ assert_equals(win, null);
+}, `"require-corp" top-level noopener popup: navigating to "none" should succeed`);
+
+async_test(t => {
+ const frame = document.createElement("iframe");
+ const id = token();
+ t.add_cleanup(() => frame.remove());
+ window.addEventListener('message', t.step_func((e) => {
+ if (e.data === id) {
+ // Loaded!
+ t.done();
+ }
+ }));
+ frame.src = `${HOST.HTTPS_NOTSAMESITE_ORIGIN}${BASE}/navigate-require-corp-same-site.sub.html?token=${id}`;
+ document.body.append(frame);
+}, 'CORP: same-site is not checked.');
+
+</script>
diff --git a/testing/web-platform/tests/html/cross-origin-embedder-policy/none.https.html.headers b/testing/web-platform/tests/html/cross-origin-embedder-policy/none.https.html.headers
new file mode 100644
index 0000000000..43c44cffd6
--- /dev/null
+++ b/testing/web-platform/tests/html/cross-origin-embedder-policy/none.https.html.headers
@@ -0,0 +1 @@
+Cross-Origin-Embedder-Policy: unknown-should-be-parsed-as-null
diff --git a/testing/web-platform/tests/html/cross-origin-embedder-policy/reflection-credentialless.tentative.https.any.js b/testing/web-platform/tests/html/cross-origin-embedder-policy/reflection-credentialless.tentative.https.any.js
new file mode 100644
index 0000000000..f4d59955af
--- /dev/null
+++ b/testing/web-platform/tests/html/cross-origin-embedder-policy/reflection-credentialless.tentative.https.any.js
@@ -0,0 +1,2 @@
+// META: global=window,worker,sharedworker-module,serviceworker-module
+test(t => assert_equals(crossOriginEmbedderPolicy, "credentialless"));
diff --git a/testing/web-platform/tests/html/cross-origin-embedder-policy/reflection-credentialless.tentative.https.any.js.headers b/testing/web-platform/tests/html/cross-origin-embedder-policy/reflection-credentialless.tentative.https.any.js.headers
new file mode 100644
index 0000000000..32523a6978
--- /dev/null
+++ b/testing/web-platform/tests/html/cross-origin-embedder-policy/reflection-credentialless.tentative.https.any.js.headers
@@ -0,0 +1 @@
+Cross-Origin-Embedder-Policy: credentialless
diff --git a/testing/web-platform/tests/html/cross-origin-embedder-policy/reflection-require-corp.tentative.https.any.js b/testing/web-platform/tests/html/cross-origin-embedder-policy/reflection-require-corp.tentative.https.any.js
new file mode 100644
index 0000000000..f6019c2457
--- /dev/null
+++ b/testing/web-platform/tests/html/cross-origin-embedder-policy/reflection-require-corp.tentative.https.any.js
@@ -0,0 +1,2 @@
+// META: global=window,worker,sharedworker-module,serviceworker-module
+test(t => assert_equals(crossOriginEmbedderPolicy, "require-corp"));
diff --git a/testing/web-platform/tests/html/cross-origin-embedder-policy/reflection-require-corp.tentative.https.any.js.headers b/testing/web-platform/tests/html/cross-origin-embedder-policy/reflection-require-corp.tentative.https.any.js.headers
new file mode 100644
index 0000000000..6604450991
--- /dev/null
+++ b/testing/web-platform/tests/html/cross-origin-embedder-policy/reflection-require-corp.tentative.https.any.js.headers
@@ -0,0 +1 @@
+Cross-Origin-Embedder-Policy: require-corp
diff --git a/testing/web-platform/tests/html/cross-origin-embedder-policy/reflection-unsafe-none.tentative.https.any.js b/testing/web-platform/tests/html/cross-origin-embedder-policy/reflection-unsafe-none.tentative.https.any.js
new file mode 100644
index 0000000000..d2890901eb
--- /dev/null
+++ b/testing/web-platform/tests/html/cross-origin-embedder-policy/reflection-unsafe-none.tentative.https.any.js
@@ -0,0 +1,2 @@
+// META: global=window,worker,sharedworker-module,serviceworker-module
+test(t => assert_equals(crossOriginEmbedderPolicy, "unsafe-none"));
diff --git a/testing/web-platform/tests/html/cross-origin-embedder-policy/report-only-require-corp.https.html b/testing/web-platform/tests/html/cross-origin-embedder-policy/report-only-require-corp.https.html
new file mode 100644
index 0000000000..ff9e5b64a0
--- /dev/null
+++ b/testing/web-platform/tests/html/cross-origin-embedder-policy/report-only-require-corp.https.html
@@ -0,0 +1,86 @@
+<!doctype html>
+<meta name="timeout" content="long">
+<title>Cross-Origin-Embedder-Policy-Report-Only header does not affect the actual behavior</title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script src=/common/utils.js></script> <!-- Use token() to allow running tests in parallel -->
+<script src="/common/get-host-info.sub.js"></script>
+<div id=log></div>
+<script>
+const HOST = get_host_info();
+const BASE = new URL("resources", location).pathname;
+
+async_test(t => {
+ const frame = document.createElement("iframe");
+ t.add_cleanup(() => frame.remove());
+ frame.onload = t.step_func_done(() => {
+ assert_not_equals(frame.contentDocument, null);
+ });
+ frame.src = "/common/blank.html";
+ document.body.append(frame);
+ assert_equals(frame.contentDocument.body.localName, "body");
+}, `"none" top-level: navigating a frame to "none" should succeed`);
+
+async_test(t => {
+ const frame = document.createElement("iframe");
+ t.add_cleanup(() => frame.remove());
+ const blank = "/common/blank.html";
+ let firstNavOk = false;
+ frame.onload = t.step_func(() => {
+ if (!firstNavOk) {
+ assert_not_equals(frame.contentDocument, null);
+ firstNavOk = true;
+ } else {
+ assert_not_equals(frame.contentDocument, null);
+ assert_equals(frame.contentWindow.location.pathname, blank);
+ t.done();
+ }
+ });
+ frame.src = `resources/navigate-require-corp.sub.html?to=${blank}`;
+ document.body.append(frame);
+ assert_equals(frame.contentDocument.body.localName, "body");
+}, `"none" top-level: navigating a frame from "require-corp" to "none" should succeed`);
+
+async_test(t => {
+ const w = window.open(`resources/navigate-none.sub.html?to=navigate-require-corp.sub.html`, "window_name");
+ t.add_cleanup(() => w.close());
+
+ w.onload = t.step_func(() => {
+ w.history.back();
+ t.step_timeout(() => {
+ assert_not_equals(w.document, null);
+ t.done();
+ }, 1500);
+ });
+}, `"none" top-level: navigating a frame back from "require-corp" should succeed`);
+
+async_test(t => {
+ let pageLoaded = false;
+ const bc = new BroadcastChannel(token());
+ let finished = false;
+ let doneCheck = _ => {
+ if (finished && pageLoaded) {
+ t.done();
+ }
+ }
+ bc.onmessage = t.step_func((event) => {
+ pageLoaded = true;
+ let payload = event.data;
+ assert_equals(payload, "loaded");
+
+ doneCheck();
+ });
+
+ const bc2 = new BroadcastChannel(token());
+ bc2.onmessage = t.step_func((event) => {
+ finished = true;
+ let payload = event.data;
+ assert_equals(payload, "loaded");
+
+ doneCheck();
+ });
+
+ const win = window.open(`resources/navigate-require-corp.sub.html?channelName=${bc.name}&to=navigate-none.sub.html?channelName=${bc2.name}`, "_blank", "noopener");
+ assert_equals(win, null);
+}, `"require-corp" top-level noopener popup: navigating to "none" should succeed`);
+</script>
diff --git a/testing/web-platform/tests/html/cross-origin-embedder-policy/report-only-require-corp.https.html.headers b/testing/web-platform/tests/html/cross-origin-embedder-policy/report-only-require-corp.https.html.headers
new file mode 100644
index 0000000000..289659a41f
--- /dev/null
+++ b/testing/web-platform/tests/html/cross-origin-embedder-policy/report-only-require-corp.https.html.headers
@@ -0,0 +1 @@
+cross-origin-embedder-policy-report-only: require-corp
diff --git a/testing/web-platform/tests/html/cross-origin-embedder-policy/reporting-navigation.https.html b/testing/web-platform/tests/html/cross-origin-embedder-policy/reporting-navigation.https.html
new file mode 100644
index 0000000000..dea8947818
--- /dev/null
+++ b/testing/web-platform/tests/html/cross-origin-embedder-policy/reporting-navigation.https.html
@@ -0,0 +1,170 @@
+<!doctype html>
+<html>
+<meta name="timeout" content="long">
+<body>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+<script src="./credentialless/resources/common.js"></script>
+<script>
+const {ORIGIN, REMOTE_ORIGIN} = get_host_info();
+const COEP = '|header(cross-origin-embedder-policy,require-corp)';
+const COEP_RO =
+ '|header(cross-origin-embedder-policy-report-only,require-corp)';
+const CORP_CROSS_ORIGIN =
+ '|header(cross-origin-resource-policy,cross-origin)';
+const CSP_FRAME_ANCESTORS_NONE =
+ '|header(content-security-policy,frame-ancestors \'none\')';
+const XFRAMEOPTIONS_DENY =
+ '|header(x-frame-options,deny)';
+const FRAME_URL = `${ORIGIN}/common/blank.html?pipe=`;
+const REMOTE_FRAME_URL = `${REMOTE_ORIGIN}/common/blank.html?pipe=`;
+
+function checkCorpReport(report, contextUrl, blockedUrl, disposition) {
+ assert_equals(report.type, 'coep');
+ assert_equals(report.url, contextUrl);
+ assert_equals(report.body.type, 'corp');
+ assert_equals(report.body.blockedURL, blockedUrl);
+ assert_equals(report.body.disposition, disposition);
+ assert_equals(report.body.destination, 'iframe');
+}
+
+function checkCoepMismatchReport(report, contextUrl, blockedUrl, disposition) {
+ assert_equals(report.type, 'coep');
+ assert_equals(report.url, contextUrl);
+ assert_equals(report.body.type, 'navigation');
+ assert_equals(report.body.blockedURL, blockedUrl);
+ assert_equals(report.body.disposition, disposition);
+}
+
+function loadFrame(document, url) {
+ return new Promise((resolve, reject) => {
+ const frame = document.createElement('iframe');
+ frame.src = url;
+ frame.onload = () => resolve(frame);
+ frame.onerror = reject;
+ document.body.appendChild(frame);
+ });
+}
+
+// |parentSuffix| is a suffix for the parent frame URL.
+// When |withEmptyFrame| is true, this function creates an empty frame
+// between the parent and target frames.
+// |targetUrl| is a URL for the target frame.
+async function loadFrames(test, parentSuffix, withEmptyFrame, targetUrl) {
+ const frame = await loadFrame(document, FRAME_URL + parentSuffix);
+ test.add_cleanup(() => frame.remove());
+ let parent;
+ if (withEmptyFrame) {
+ parent = frame.contentDocument.createElement('iframe');
+ frame.contentDocument.body.appendChild(parent);
+ } else {
+ parent = frame;
+ }
+ // Here we don't need "await". This loading may or may not succeed, and
+ // we're not interested in the result.
+ loadFrame(parent.contentDocument, targetUrl);
+
+ return parent;
+}
+
+async function observeReports(global, expected_count) {
+ const reports = [];
+ const receivedEveryReports = new Promise(resolve => {
+ if (expected_count == 0)
+ resolve();
+
+ const observer = new global.ReportingObserver((rs) => {
+ for (const r of rs) {
+ reports.push(r.toJSON());
+ }
+ if (expected_count <= reports.length)
+ resolve();
+ });
+ observer.observe();
+
+ });
+
+ // Wait 5000 ms more to catch additionnal unexpected reports.
+ await receivedEveryReports;
+ await new Promise(r => step_timeout(r, 5000));
+ return reports;
+}
+
+// CASES is a list of test case. Each test case consists of:
+// parent: the suffix of the URL of the parent frame.
+// target: the suffix of the URL of the target frame.
+// reports: the expectation of reports to be made. Each report is one of:
+// 'CORP': CORP violation
+// 'CORP-RO' CORP violation (report only)
+// 'NAV': COEP mismatch between the frames.
+// 'NAV-RO': COEP mismatch between the frames (report only).
+const CASES = [
+ { parent: '', target: '', reports: [] },
+ { parent: '', target: COEP, reports: [] },
+ { parent: COEP, target: COEP, reports: ['CORP'] },
+ { parent: COEP, target: '', reports: ['CORP'] },
+
+ { parent: '', target: CORP_CROSS_ORIGIN, reports: [] },
+ { parent: COEP, target: CORP_CROSS_ORIGIN, reports: ['NAV'] },
+
+ { parent: '', target: COEP + CORP_CROSS_ORIGIN, reports: [] },
+ { parent: COEP, target: COEP + CORP_CROSS_ORIGIN, reports: [] },
+
+ { parent: COEP_RO, target: COEP, reports: ['CORP-RO'] },
+ { parent: COEP_RO, target: '', reports: ['CORP-RO', 'NAV-RO'] },
+ { parent: COEP_RO, target: CORP_CROSS_ORIGIN, reports: ['NAV-RO'] },
+ { parent: COEP_RO, target: COEP + CORP_CROSS_ORIGIN, reports: [] },
+
+ { parent: COEP, target: COEP_RO + CORP_CROSS_ORIGIN, reports: ['NAV'] },
+
+ // Test ordering of CSP frame-ancestors, COEP, and X-Frame-Options
+ { parent: COEP, target: CORP_CROSS_ORIGIN + CSP_FRAME_ANCESTORS_NONE, reports: [] },
+ { parent: COEP, target: CORP_CROSS_ORIGIN + XFRAMEOPTIONS_DENY, reports: ['NAV'] },
+];
+
+for (const testcase of CASES) {
+ for (const withEmptyFrame of [false, true]) {
+ function desc(s) {
+ return s === '' ? '(none)' : s;
+ }
+ // These tests are very slow, so they must be run in parallel using
+ // async_test.
+ async_test(t => {
+ const targetUrl = REMOTE_FRAME_URL + testcase.target;
+ loadFrames(t, testcase.parent, withEmptyFrame, targetUrl)
+ .then(t.step_func(parent => {
+ const contextUrl = parent.src ? parent.src : 'about:blank';
+ observeReports(parent.contentWindow, testcase.reports.length)
+ .then(t.step_func(reports => {
+ assert_equals(reports.length, testcase.reports.length);
+ for (let i = 0; i < reports.length; i += 1) {
+ const report = reports[i];
+ switch (testcase.reports[i]) {
+ case 'CORP':
+ checkCorpReport(report, contextUrl, targetUrl, 'enforce');
+ break;
+ case 'CORP-RO':
+ checkCorpReport(report, contextUrl, targetUrl, 'reporting');
+ break;
+ case 'NAV':
+ checkCoepMismatchReport(report, contextUrl, targetUrl, 'enforce');
+ break;
+ case 'NAV-RO':
+ checkCoepMismatchReport(report, contextUrl, targetUrl, 'reporting');
+ break;
+ default:
+ assert_unreached(
+ 'Unexpected report expeaction: ' + testcase.reports[i]);
+ }
+ }
+ t.done();
+ })).catch(t.step_func(e => { throw e; }));
+ })).catch(t.step_func(e => { throw e; }));
+ }, `parent: ${desc(testcase.parent)}, target: ${desc(testcase.target)}, ` +
+ `with empty frame: ${withEmptyFrame}`);
+ }
+}
+
+</script>
+</body></html>
diff --git a/testing/web-platform/tests/html/cross-origin-embedder-policy/reporting-subresource-corp.https.html b/testing/web-platform/tests/html/cross-origin-embedder-policy/reporting-subresource-corp.https.html
new file mode 100644
index 0000000000..e56124a4a0
--- /dev/null
+++ b/testing/web-platform/tests/html/cross-origin-embedder-policy/reporting-subresource-corp.https.html
@@ -0,0 +1,206 @@
+<!doctype html>
+<html>
+<meta name="timeout" content="long">
+<body>
+<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="/service-workers/service-worker/resources/test-helpers.sub.js"></script>
+<script>
+const {ORIGIN, REMOTE_ORIGIN} = get_host_info();
+const BASE = new URL("resources", location).pathname
+const FRAME_URL = `${ORIGIN}/common/blank.html` +
+ '?pipe=header(cross-origin-embedder-policy,require-corp)' +
+ `|header(cross-origin-embedder-policy-report-only,require-corp)`;
+const WORKER_URL = `${ORIGIN}${BASE}/reporting-worker.js` +
+ '?pipe=header(cross-origin-embedder-policy,require-corp)' +
+ `|header(cross-origin-embedder-policy-report-only,require-corp)`;
+const REPORTING_FRAME_URL = `${ORIGIN}${BASE}/reporting-empty-frame.html` +
+ '?pipe=header(cross-origin-embedder-policy,require-corp)' +
+ `|header(cross-origin-embedder-policy-report-only,require-corp)`;
+
+async function observeReports(global, expected_count) {
+ const reports = [];
+ const receivedEveryReports = new Promise(resolve => {
+ if (expected_count == 0)
+ resolve();
+
+ const observer = new global.ReportingObserver((rs) => {
+ for (const r of rs) {
+ reports.push(r.toJSON());
+ }
+ if (expected_count <= reports.length)
+ resolve();
+ });
+ observer.observe();
+
+ });
+
+ await receivedEveryReports;
+ // Wait 500ms more to catch additionnal unexpected reports.
+ await new Promise(r => step_timeout(r, 500));
+ return reports;
+}
+
+function checkReport(report, contextUrl, blockedUrl, disposition, destination) {
+ assert_equals(report.type, 'coep');
+ assert_equals(report.url, contextUrl);
+ assert_equals(report.body.type, 'corp');
+ assert_equals(report.body.blockedURL, blockedUrl);
+ assert_equals(report.body.disposition, disposition);
+ assert_equals(report.body.destination, destination);
+}
+
+async function fetchInFrame(t, frameUrl, url, expected_count) {
+ const frame = await with_iframe(frameUrl);
+ t.add_cleanup(() => frame.remove());
+
+ const init = { mode: 'no-cors', cache: 'no-store' };
+ let future_reports = observeReports(frame.contentWindow, expected_count);
+ await frame.contentWindow.fetch(url, init).catch(() => {});
+
+ return await future_reports;
+}
+
+async function fetchInWorker(workerOrPort, url) {
+ const script =
+ `fetch('${url}', {mode: 'no-cors', cache: 'no-store'}).catch(() => {});`;
+ const mc = new MessageChannel();
+ workerOrPort.postMessage({script, port: mc.port2}, [mc.port2]);
+ return (await new Promise(r => mc.port1.onmessage = r)).data;
+}
+
+// We want to test several URLs in various environments (document,
+// dedicated worker, shared worser, service worker). As expectations
+// are independent of environment except for the context URLs in reports,
+// we define ENVIRONMENTS and CASES to reduce the code duplication.
+//
+// ENVIRONMENTS is a list of dictionaries. Each dictionary consists of:
+// - tag: the name of the environment
+// - contextUrl: the URL of the environment settings object
+// - run: an async function which generates reports
+// - test: a testharness Test object
+// - url: the URL for a test case (see below)
+//
+// CASES is a list of test cases. Each test case consists of:
+// - name: the name of the test case
+// - url: the URL of the test case
+// - check: a function to check the results
+// - reports: the generated reports
+// - url: the URL of the test case
+// - contextUrl: the URL of the environment settings object (see
+// ENVORONMENTS)
+
+const ENVIRONMENTS = [{
+ tag: 'document',
+ contextUrl: FRAME_URL,
+ run: async (test, url, expected_count) => {
+ return await fetchInFrame(test, FRAME_URL, url, expected_count);
+ },
+}, {
+ tag: 'dedicated worker',
+ contextUrl: WORKER_URL,
+ run: async (test, url, expected_count) => {
+ const worker = new Worker(WORKER_URL);
+ worker.addEventListener('error', test.unreached_func('Worker.onerror'));
+ test.add_cleanup(() => worker.terminate());
+ return await fetchInWorker(worker, url);
+ },
+}, {
+ tag: 'shared worker',
+ contextUrl: WORKER_URL,
+ run: async (test, url, expected_count) => {
+ const worker = new SharedWorker(WORKER_URL);
+ worker.addEventListener('error', test.unreached_func('Worker.onerror'));
+ return await fetchInWorker(worker.port, url);
+ },
+}, {
+ tag: 'service worker',
+ contextUrl: WORKER_URL,
+ run: async (test, url, expected_count) => {
+ // As we don't want the service worker to control any page, generate a
+ // one-time scope.
+ const SCOPE = new URL(`resources/${token()}.html`, location).pathname;
+ const reg =
+ await service_worker_unregister_and_register(test, WORKER_URL, SCOPE);
+ test.add_cleanup(() => reg.unregister());
+ const worker = reg.installing || reg.waiting || reg.active;
+ worker.addEventListener('error', test.unreached_func('Worker.onerror'));
+ return await fetchInWorker(worker, url);
+ },
+}, {
+ tag: 'between service worker and page',
+ contextUrl: REPORTING_FRAME_URL,
+ run: async (test, url, expected_count) => {
+ // Here we use a Service Worker without COEP.
+ const WORKER_URL = `${ORIGIN}${BASE}/sw.js`;
+ const reg = await service_worker_unregister_and_register(
+ test, WORKER_URL, REPORTING_FRAME_URL);
+ test.add_cleanup(() => reg.unregister());
+ const worker = reg.installing || reg.waiting || reg.active;
+ worker.addEventListener('error', test.unreached_func('Worker.onerror'));
+ return await fetchInFrame(
+ test, REPORTING_FRAME_URL, url, expected_count);
+ },
+}];
+
+const CASES = [{
+ name: 'same-origin',
+ url: '/common/text-plain.txt',
+ expected_count: 0,
+ check: (reports, url, contextUrl) => {}
+}, {
+ name: 'blocked by CORP: same-origin',
+ url: `${REMOTE_ORIGIN}${BASE}/nothing-same-origin-corp.txt`,
+ expected_count: 0,
+ check: (reports, url, contextUrl) => {}
+}, {
+ name: 'blocked due to COEP',
+ url: `${REMOTE_ORIGIN}/common/text-plain.txt`,
+ expected_count: 2,
+ check: (reports, contextUrl, url) => {
+ checkReport(reports[0], contextUrl, url, 'reporting', '');
+ checkReport(reports[1], contextUrl, url, 'enforce', '');
+ }
+}, {
+ name: 'blocked during redirect',
+ url: `${ORIGIN}/common/redirect.py?location=` +
+ encodeURIComponent(`${REMOTE_ORIGIN}/common/text-plain.txt`),
+ expected_count: 2,
+ check: (reports, contextUrl, url) => {
+ checkReport(reports[0], contextUrl, url, 'reporting', '');
+ checkReport(reports[1], contextUrl, url, 'enforce', '');
+ },
+}];
+
+for (const env of ENVIRONMENTS) {
+ for (const testcase of CASES) {
+ promise_test(async (t) => {
+ const reports = await env.run(
+ t, testcase.url, testcase.expected_count);
+
+ assert_equals(reports.length, testcase.expected_count);
+ testcase.check(reports, env.contextUrl, testcase.url);
+ }, `[${env.tag}] ${testcase.name}`);
+ }
+}
+
+// A test for a non-empty destination.
+promise_test(async (t) => {
+ const frame = await with_iframe(FRAME_URL);
+ t.add_cleanup(() => frame.remove());
+
+ const url = `${REMOTE_ORIGIN}/common/utils.js`;
+ const script = frame.contentDocument.createElement('script');
+ script.src = url;
+ const future_reports = observeReports(frame.contentWindow, 2);
+ frame.contentDocument.body.appendChild(script);
+
+ const reports = await future_reports;
+ assert_equals(reports.length, 2);
+ checkReport(reports[0], FRAME_URL, url, 'reporting', 'script');
+ checkReport(reports[1], FRAME_URL, url, 'enforce', 'script');
+}, 'destination: script');
+
+</script>
diff --git a/testing/web-platform/tests/html/cross-origin-embedder-policy/reporting-to-document-reporting-endpoint.https.window.js b/testing/web-platform/tests/html/cross-origin-embedder-policy/reporting-to-document-reporting-endpoint.https.window.js
new file mode 100644
index 0000000000..09800db2b8
--- /dev/null
+++ b/testing/web-platform/tests/html/cross-origin-embedder-policy/reporting-to-document-reporting-endpoint.https.window.js
@@ -0,0 +1,140 @@
+// META: script=/common/get-host-info.sub.js
+// META: script=/common/utils.js
+
+// This file consists of tests for COEP reporting using Reporting-Endpoints
+// header. It exclusively tests that reports can be sent to Reporting-Endpoint
+// configured endpoint.
+const { REMOTE_ORIGIN } = get_host_info();
+
+const REPORT_ENDPOINT = token();
+const REPORT_ONLY_ENDPOINT = token();
+const FRAME_URL = `resources/reporting-empty-frame.html` +
+ `?pipe=header(cross-origin-embedder-policy,require-corp;report-to="endpoint")` +
+ `|header(cross-origin-embedder-policy-report-only,require-corp;report-to="report-only-endpoint")` +
+ `|header(reporting-endpoints, endpoint="/html/cross-origin-embedder-policy/resources/report.py?key=${REPORT_ENDPOINT}"\\, report-only-endpoint="/html/cross-origin-embedder-policy/resources/report.py?key=${REPORT_ONLY_ENDPOINT}")`;
+
+function wait(ms) {
+ return new Promise(resolve => step_timeout(resolve, ms));
+}
+
+async function fetchReports(endpoint) {
+ const res = await fetch(`resources/report.py?key=${endpoint}`, {
+ cache: 'no-store'
+ });
+ if (res.status == 200) {
+ return await res.json();
+ }
+ return [];
+}
+
+async function fetchCoepReport(
+ endpoint, type, blockedUrl, contextUrl, disposition) {
+ blockedUrl = new URL(blockedUrl, location).href;
+ contextUrl = new URL(contextUrl, location).href;
+ const reports = await fetchReports(endpoint);
+ return reports.find(r => (
+ r.type == 'coep' &&
+ r.url == contextUrl &&
+ r.body.type == type &&
+ r.body.blockedURL === blockedUrl &&
+ r.body.disposition === disposition));
+}
+
+async function checkCorpReportExists(
+ endpoint, blockedUrl, contextUrl, destination, disposition) {
+ blockedUrl = new URL(blockedUrl, location).href;
+ contextUrl = new URL(contextUrl, location).href;
+ contextUrl.replace(REPORT_ENDPOINT, "REPORT_ENDPOINT_UUID");
+ contextUrl.replace(REPORT_ONLY_ENDPOINT, "REPORT_ONLY_ENDPOINT_UUID");
+ const report = await fetchCoepReport(
+ endpoint, 'corp', blockedUrl, contextUrl, disposition);
+ assert_true(!!report,
+ `A corp report with blockedURL ${blockedUrl.split("?")[0]} ` +
+ `and url ${contextUrl} is not found.`);
+ assert_equals(report.body.destination, destination);
+}
+
+async function checkNavigationReportExists(
+ endpoint, blockedUrl, contextUrl, disposition) {
+ blockedUrl = new URL(blockedUrl, location).href;
+ contextUrl = new URL(contextUrl, location).href;
+ contextUrl.replace(REPORT_ENDPOINT, "REPORT_ENDPOINT_UUID");
+ contextUrl.replace(REPORT_ONLY_ENDPOINT, "REPORT_ONLY_ENDPOINT_UUID");
+ const report = await fetchCoepReport(
+ endpoint, 'navigation', blockedUrl, contextUrl, disposition);
+ assert_true(!!report,
+ `A navigation report with blockedURL ${blockedUrl.split("?")[0]} ` +
+ `and url ${contextUrl} is not found.`);
+}
+
+promise_test(async t => {
+ const iframe = document.createElement('iframe');
+ t.add_cleanup(() => iframe.remove());
+
+ iframe.src = FRAME_URL;
+ await new Promise(resolve => {
+ iframe.addEventListener('load', resolve, { once: true });
+ document.body.appendChild(iframe);
+ });
+
+ const url = `${REMOTE_ORIGIN}/common/text-plain.txt?${token()}`;
+ const init = { mode: 'no-cors', cache: 'no-store' };
+ // The response comes from cross-origin, and doesn't have a CORP
+ // header, so it is blocked.
+ iframe.contentWindow.fetch(url, init).catch(() => { });
+
+ // Wait for reports to be uploaded.
+ await wait(1000);
+ await checkCorpReportExists(
+ REPORT_ENDPOINT, url, iframe.src, '', 'enforce');
+ await checkCorpReportExists(
+ REPORT_ONLY_ENDPOINT, url, iframe.src, '', 'reporting');
+}, 'subresource CORP');
+
+promise_test(async t => {
+ const iframe = document.createElement('iframe');
+ t.add_cleanup(() => iframe.remove());
+
+ iframe.src = FRAME_URL;
+ await new Promise(resolve => {
+ iframe.addEventListener('load', resolve, { once: true });
+ document.body.appendChild(iframe);
+ });
+
+ const url = `${REMOTE_ORIGIN}/common/blank.html?${token()}`;
+ // The nested frame comes from cross-origin and doesn't have a CORP
+ // header, so it is blocked.
+ const nested = iframe.contentWindow.document.createElement('iframe');
+ nested.src = url;
+ iframe.contentWindow.document.body.appendChild(nested);
+
+ // Wait for reports to be uploaded.
+ await wait(1000);
+ await checkCorpReportExists(
+ REPORT_ENDPOINT, url, iframe.src, 'iframe', 'enforce');
+ await checkCorpReportExists(
+ REPORT_ONLY_ENDPOINT, url, iframe.src, 'iframe', 'reporting');
+}, 'navigation CORP on cross origin');
+
+promise_test(async (t) => {
+ const iframe = document.createElement('iframe');
+ t.add_cleanup(() => iframe.remove());
+
+ iframe.src = FRAME_URL;
+ const targetUrl = `/common/blank.html?${token()}`;
+ iframe.addEventListener('load', t.step_func(() => {
+ const nested = iframe.contentDocument.createElement('iframe');
+ nested.src = targetUrl;
+ // |nested| doesn't have COEP whereas |iframe| has, so it is blocked.
+ iframe.contentDocument.body.appendChild(nested);
+ }), { once: true });
+
+ document.body.appendChild(iframe);
+
+ // Wait for reports to be uploaded.
+ await wait(1000);
+ await checkNavigationReportExists(
+ REPORT_ENDPOINT, targetUrl, iframe.src, 'enforce');
+ await checkNavigationReportExists(
+ REPORT_ONLY_ENDPOINT, targetUrl, iframe.src, 'reporting');
+}, 'navigation CORP on same origin');
diff --git a/testing/web-platform/tests/html/cross-origin-embedder-policy/reporting-to-endpoint.https.html b/testing/web-platform/tests/html/cross-origin-embedder-policy/reporting-to-endpoint.https.html
new file mode 100644
index 0000000000..39c3de7076
--- /dev/null
+++ b/testing/web-platform/tests/html/cross-origin-embedder-policy/reporting-to-endpoint.https.html
@@ -0,0 +1,209 @@
+<!doctype html>
+<html>
+<meta name="timeout" content="long">
+<body>
+<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>
+// This file consists of tests for COEP reporting. The tests make COEP
+// violations and see whether reports are sent to the network as specified.
+// We only have basic tests in this file - one for each kind of reports,
+// because we can also test the reporting functionality with ReportingObserver,
+// and that way is faster, easier to debug, and less flaky.
+//
+// For more detailed tests and tests with workers, see tests in other files
+// such as
+// - reporting-navigation.https.html
+// - reporting-subresource-corp.https.html
+// - cache-storage-reporting*.https.html
+// .
+
+const { REMOTE_ORIGIN } = get_host_info();
+const BASE = new URL("resources", location).pathname
+const FRAME_URL = `resources/reporting-empty-frame.html` +
+ `?pipe=header(cross-origin-embedder-policy,require-corp;report-to="endpoint")` +
+ `|header(cross-origin-embedder-policy-report-only,require-corp;report-to="report-only-endpoint")`;
+const WORKER_URL = `resources/shared-worker.js` +
+ '?pipe=header(cross-origin-embedder-policy,require-corp;report-to="endpoint")' +
+ `|header(cross-origin-embedder-policy-report-only,require-corp;report-to="report-only-endpoint")`;
+const REPORT_UUID = "4d8b6d86-c9a8-47c1-871b-111169a8f79c";
+const REPORT_ONLY_UUID = "5d7c1e33-ef88-43c2-9ca3-c67ff300b8c2";
+
+function wait(ms) {
+ return new Promise(resolve => step_timeout(resolve, ms));
+}
+
+async function fetchReports(endpoint) {
+ const res = await fetch(`resources/report.py?key=${endpoint}`, {cache: 'no-store'});
+ if (res.status == 200) {
+ return await res.json();
+ }
+ return [];
+}
+
+async function checkCorpReportExistence(endpoint, blockedUrl, contextUrl, destination, disposition) {
+ blockedUrl = new URL(blockedUrl, location).href;
+ contextUrl = new URL(contextUrl, location).href;
+
+ const timeout = 3000;
+ const retryDelay = 200;
+ for (let i = 0; i * retryDelay < timeout; i++) {
+ const reports = await fetchReports(endpoint);
+ for (const report of reports) {
+ if (report.type !== 'coep' || report.url !== contextUrl ||
+ report.body.type !== 'corp') {
+ continue;
+ }
+ if (report.body.blockedURL === blockedUrl &&
+ report.body.disposition === disposition) {
+ assert_equals(report.body.destination, destination);
+ return;
+ }
+ }
+ await wait(retryDelay);
+ }
+ assert_unreached(`A report whose blockedURL is ${blockedUrl.split("?")[0]} and url is ${contextUrl} is not found.`);
+}
+
+async function checkNavigationReportExistence(endpoint, blockedUrl, contextUrl, disposition) {
+ blockedUrl = new URL(blockedUrl, location).href;
+ contextUrl = new URL(contextUrl, location).href;
+ const timeout = 3000;
+ const retryDelay = 200;
+ for (let i = 0; i * retryDelay < timeout; i++) {
+ const reports = await fetchReports(endpoint);
+ for (const report of reports) {
+ if (report.type !== 'coep' || report.url !== contextUrl ||
+ report.body.type !== 'navigation') {
+ continue;
+ }
+ if (report.body.blockedURL === blockedUrl &&
+ report.body.disposition === disposition) {
+ return;
+ }
+ }
+ await wait(retryDelay);
+ }
+ assert_unreached(`A report whose blockedURL is ${blockedUrl.split("?")[0]} and url is ${contextUrl} is not found.`);
+}
+
+promise_test(async t => {
+ const iframe = document.createElement('iframe');
+ t.add_cleanup(() => iframe.remove());
+
+ iframe.src = FRAME_URL
+ document.body.appendChild(iframe);
+ await new Promise(resolve => {
+ iframe.addEventListener('load', resolve, {once: true});
+ });
+
+ const url = `${REMOTE_ORIGIN}/common/text-plain.txt?${token()}`;
+ const init = { mode: 'no-cors', cache: 'no-store' };
+ // The response comes from cross-origin, and doesn't have a CORP
+ // header, so it is blocked.
+ iframe.contentWindow.fetch(url, init).catch(() => {});
+
+ await checkCorpReportExistence(REPORT_UUID, url, iframe.src, '', 'enforce');
+ await checkCorpReportExistence(
+ REPORT_ONLY_UUID, url, iframe.src, '', 'reporting');
+}, 'subresource CORP');
+
+promise_test(async t => {
+ const iframe = document.createElement('iframe');
+ t.add_cleanup(() => iframe.remove());
+
+ iframe.src = FRAME_URL
+ document.body.appendChild(iframe);
+ await new Promise(resolve => {
+ iframe.addEventListener('load', resolve, {once: true});
+ });
+
+ const w = iframe.contentWindow;
+
+ function attachFrame(url) {
+ const frame = w.document.createElement('iframe');
+ frame.src = url;
+ w.document.body.appendChild(frame);
+ }
+
+ const url = `${REMOTE_ORIGIN}/common/blank.html?${token()}`;
+ // The nested frame comes from cross-origin and doesn't have a CORP
+ // header, so it is blocked.
+ attachFrame(url);
+
+ await checkCorpReportExistence(
+ REPORT_UUID, url, iframe.src, 'iframe', 'enforce');
+ await checkCorpReportExistence(
+ REPORT_ONLY_UUID, url, iframe.src, 'iframe', 'reporting');
+}, 'navigation CORP');
+
+promise_test(async (t) => {
+ const iframe = document.createElement('iframe');
+ t.add_cleanup(() => iframe.remove());
+
+ iframe.src = FRAME_URL;
+ const targetUrl = `/common/blank.html?${token()}`;
+ iframe.addEventListener('load', t.step_func(() => {
+ const nested = iframe.contentDocument.createElement('iframe');
+ nested.src = targetUrl;
+ // |nested| doesn't have COEP whereas |iframe| has, so it is blocked.
+ iframe.contentDocument.body.appendChild(nested);
+ }), {once: true});
+
+ document.body.appendChild(iframe);
+
+ await checkNavigationReportExistence(
+ REPORT_UUID, targetUrl, iframe.src, 'enforce');
+ await checkNavigationReportExistence(
+ REPORT_ONLY_UUID, targetUrl, iframe.src, 'reporting');
+}, 'COEP violation on nested frame navigation');
+
+promise_test(async (t) => {
+ const iframe = document.createElement('iframe');
+ t.add_cleanup(() => iframe.remove());
+
+ iframe.src = 'resources/reporting-empty-frame-multiple-headers.html.asis';
+ const targetUrl = `/common/blank.html?${token()}`;
+
+ iframe.addEventListener('load', t.step_func(() => {
+ const nested = iframe.contentDocument.createElement('iframe');
+ nested.src = targetUrl;
+ // |nested| doesn't have COEP whereas |iframe| has, so it is blocked.
+ iframe.contentDocument.body.appendChild(nested);
+ }), {once: true});
+
+ document.body.appendChild(iframe);
+
+ await checkNavigationReportExistence(
+ REPORT_UUID, targetUrl, iframe.src, 'enforce');
+ await checkNavigationReportExistence(
+ REPORT_ONLY_UUID, targetUrl, iframe.src, 'reporting');
+
+}, 'Two COEP headers, split inside report-to value');
+
+// Shared worker do not support observer currently, so add test for endpoint
+// here.
+promise_test(async (t) => {
+ const iframe = document.createElement('iframe');
+ t.add_cleanup(() => iframe.remove());
+
+ iframe.src = FRAME_URL;
+ const targetUrl = `${REMOTE_ORIGIN}/common/blank.html?${token()}`;
+ document.body.appendChild(iframe);
+
+ const worker = new iframe.contentWindow.SharedWorker(WORKER_URL);
+ worker.port.start();
+ const script =
+ `fetch('${targetUrl}', {mode: 'no-cors', cache: 'no-store'}).catch(e => {});`;
+ worker.addEventListener('error', t.unreached_func('Worker.onerror'));
+ worker.port.postMessage(script);
+
+ await checkCorpReportExistence(
+ REPORT_UUID, targetUrl, WORKER_URL, 'iframe', 'enforce');
+ await checkCorpReportExistence(
+ REPORT_ONLY_UUID, targetUrl, WORKER_URL, 'iframe', 'reporting');
+}, 'Shared worker fetch');
+
+</script>
diff --git a/testing/web-platform/tests/html/cross-origin-embedder-policy/reporting-to-endpoint.https.html.sub.headers b/testing/web-platform/tests/html/cross-origin-embedder-policy/reporting-to-endpoint.https.html.sub.headers
new file mode 100644
index 0000000000..fe2f651dae
--- /dev/null
+++ b/testing/web-platform/tests/html/cross-origin-embedder-policy/reporting-to-endpoint.https.html.sub.headers
@@ -0,0 +1 @@
+Reporting-Endpoints: endpoint="https://{{host}}:{{ports[https][0]}}//html/cross-origin-embedder-policy/resources/report.py?key=4d8b6d86-c9a8-47c1-871b-111169a8f79c", report-only-endpoint="/html/cross-origin-embedder-policy/resources/report.py?key=5d7c1e33-ef88-43c2-9ca3-c67ff300b8c2"
diff --git a/testing/web-platform/tests/html/cross-origin-embedder-policy/reporting-to-frame-owner.https.html b/testing/web-platform/tests/html/cross-origin-embedder-policy/reporting-to-frame-owner.https.html
new file mode 100644
index 0000000000..331ad898eb
--- /dev/null
+++ b/testing/web-platform/tests/html/cross-origin-embedder-policy/reporting-to-frame-owner.https.html
@@ -0,0 +1,87 @@
+<!doctype html>
+<html>
+<head>
+<title>Check COEP reports are sent to iframe for 'new Worker()' failure</title>
+</head>
+<body>
+<script src="/common/get-host-info.sub.js"></script>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/service-workers/service-worker/resources/test-helpers.sub.js"></script>
+<script>
+const {ORIGIN} = get_host_info();
+const RESOURCES_PATH= new URL("resources", location).pathname;
+const iframe_path = "worker-owner-frame.html?pipe=";
+const worker_path = "universal-worker.js?pipe=";
+
+const coep_header= {
+ "coep-none" : "",
+ "coep-report-only" :
+ "header(Cross-Origin-Embedder-Policy-Report-Only,require-corp)",
+ "coep-require-corp" : "|header(Cross-Origin-Embedder-Policy,require-corp)",
+};
+
+function checkReport(report, url, blocked_url, disposition) {
+ assert_equals(report.type, "coep");
+ assert_equals(report.url, url);
+ assert_equals(report.body.type, "worker initialization");
+ assert_equals(report.body.blockedURL, blocked_url);
+ assert_equals(report.body.disposition, disposition);
+}
+
+// Test parameters:
+// - `owner_coep` the COEP header of the iframe document's response.
+// - `worker_coep` the COEP header of the DedicatedWorker's script response.
+//
+// Test expectations:
+// - `length` the length of reports.
+// - `disposition` the disposition in a report's body. Empty string if the
+// length of reports is expected to be 0.
+function check(
+ // Test parameters:
+ owner_coep,
+ worker_coep,
+ // Test expectations:
+ length,
+ disposition) {
+ promise_test(async (t) => {
+ const worker_url = worker_path + coep_header[worker_coep];
+ const iframe_url = iframe_path + coep_header[owner_coep];
+ const iframe = await with_iframe("./resources/" + iframe_url);
+ t.add_cleanup(() => iframe.remove());
+
+ const iframe_response = new Promise(resolve => window.onmessage = resolve);
+ iframe.contentWindow.startWorkerAndObserveReports(worker_url, length > 0);
+
+ const {data} = await iframe_response;
+ assert_equals(data.length, length);
+ if (data.length > 0) {
+ const blocked_url = `${ORIGIN}${RESOURCES_PATH}/${worker_url}`;
+ const url = `${ORIGIN}${RESOURCES_PATH}/${iframe_url}`;
+ checkReport(
+ data[0],
+ url,
+ blocked_url,
+ disposition
+ );
+ }
+ }, `Reporting to ${owner_coep} frame with ${worker_coep} worker`);
+}
+
+// -----------------------------------------------------------------------------
+// owner_coep , worker_coep , length , disposition
+// -----------------------------------------------------------------------------
+check("coep-none" , "coep-none" , 0 , "");
+check("coep-none" , "coep-report-only" , 0 , "");
+check("coep-none" , "coep-require-corp" , 0 , "");
+check("coep-report-only" , "coep-none" , 1 , "reporting");
+check("coep-report-only" , "coep-report-only" , 1 , "reporting");
+check("coep-report-only" , "coep-require-corp" , 0 , "");
+check("coep-require-corp" , "coep-none" , 1 , "enforce");
+check("coep-require-corp" , "coep-report-only" , 1 , "enforce");
+check("coep-require-corp" , "coep-require-corp" , 0 , "");
+
+</script>
+</body>
+</html>
+
diff --git a/testing/web-platform/tests/html/cross-origin-embedder-policy/reporting-to-worker-owner.https.html b/testing/web-platform/tests/html/cross-origin-embedder-policy/reporting-to-worker-owner.https.html
new file mode 100644
index 0000000000..c0010876fa
--- /dev/null
+++ b/testing/web-platform/tests/html/cross-origin-embedder-policy/reporting-to-worker-owner.https.html
@@ -0,0 +1,89 @@
+<!doctype html>
+<html>
+<head>
+<title>Check COEP reports are sent to parent worker for 'new Worker()' failure</title>
+</head>
+<body>
+<script src="/common/get-host-info.sub.js"></script>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/service-workers/service-worker/resources/test-helpers.sub.js"></script>
+<script>
+const {ORIGIN} = get_host_info();
+const RESOURCES_PATH= new URL("resources", location).pathname;
+const parent_worker_path = "worker-owner.js?pipe=";
+const worker_path = "universal-worker.js?pipe=";
+
+const coep_header= {
+ "coep-none" : "",
+ "coep-report-only" :
+ "header(Cross-Origin-Embedder-Policy-Report-Only,require-corp)",
+ "coep-require-corp" : "|header(Cross-Origin-Embedder-Policy,require-corp)",
+};
+
+function checkReport(report, url, blocked_url, disposition) {
+ assert_equals(report.type, "coep");
+ assert_equals(report.url, url);
+ assert_equals(report.body.type, "worker initialization");
+ assert_equals(report.body.blockedURL, blocked_url);
+ assert_equals(report.body.disposition, disposition);
+}
+
+// Test parameters:
+// - `owner_coep` the COEP header of the parent DedicatedWorker's script
+// response.
+// - `worker_coep` the COEP header of the DedicatedWorker's script response.
+//
+// Test expectations:
+// - `length` the length of reports.
+// - `disposition` the disposition in a report's body. Empty string if the
+// length of reports is expected to be 0.
+function check(
+ // Test parameters:
+ owner_coep,
+ worker_coep,
+ // Test expectations:
+ length,
+ disposition) {
+ promise_test(async (t) => {
+ const worker_url = worker_path + coep_header[worker_coep];
+ const parent_worker_url = parent_worker_path + coep_header[owner_coep];
+ const parent_worker = new Worker('./resources/' + parent_worker_url);
+
+ const worker_response =
+ new Promise(resolve => parent_worker.onmessage = resolve);
+ parent_worker.postMessage(
+ {worker_url: worker_url, wait_for_report: length > 0});
+
+ const {data} = await worker_response;
+ assert_equals(data.length, length);
+ if (data.length > 0) {
+ const blocked_url = `${ORIGIN}${RESOURCES_PATH}/${worker_url}`;
+ const url = `${ORIGIN}${RESOURCES_PATH}/${parent_worker_url}`;
+ checkReport(
+ data[0],
+ url,
+ blocked_url,
+ disposition
+ );
+ }
+ }, `Reporting to ${owner_coep} worker with ${worker_coep} worker`);
+}
+
+// -----------------------------------------------------------------------------
+// owner_coep , worker_coep , length , disposition
+// -----------------------------------------------------------------------------
+check("coep-none" , "coep-none" , 0 , "");
+check("coep-none" , "coep-report-only" , 0 , "");
+check("coep-none" , "coep-require-corp" , 0 , "");
+check("coep-report-only" , "coep-none" , 1 , "reporting");
+check("coep-report-only" , "coep-report-only" , 1 , "reporting");
+check("coep-report-only" , "coep-require-corp" , 0 , "");
+check("coep-require-corp" , "coep-none" , 1 , "enforce");
+check("coep-require-corp" , "coep-report-only" , 1 , "enforce");
+check("coep-require-corp" , "coep-require-corp" , 0 , "");
+
+</script>
+</body>
+</html>
+
diff --git a/testing/web-platform/tests/html/cross-origin-embedder-policy/require-corp-about-blank.https.html b/testing/web-platform/tests/html/cross-origin-embedder-policy/require-corp-about-blank.https.html
new file mode 100644
index 0000000000..945333b83d
--- /dev/null
+++ b/testing/web-platform/tests/html/cross-origin-embedder-policy/require-corp-about-blank.https.html
@@ -0,0 +1,48 @@
+<!doctype html>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+
+promise_test(t => {
+ return new Promise(resolve => {
+ window.addEventListener("DOMContentLoaded", resolve);
+ });
+}, "Wait for the DOM to be built.");
+
+promise_test(async t => {
+ let iframe = document.createElement("iframe");
+ let iframe_loaded = new Promise(resolve => iframe.onload = resolve);
+ iframe.src = "about:blank";
+ document.body.appendChild(iframe);
+
+ // The about:blank document can load.
+ await iframe_loaded;
+ assert_not_equals(iframe.contentDocument, null);
+}, "about:blank can always be embedded by a 'require-corp' document");
+
+promise_test(async t => {
+ let iframe_C = document.createElement("iframe");
+ let iframe_B = document.createElement("iframe");
+ iframe_B.src = "about:blank";
+ iframe_C.src = "/common/blank.html";
+ let iframe_B_loaded = new Promise(resolve => iframe_B.onload = resolve);
+ document.body.appendChild(iframe_B);
+
+ // The about:blank frame must be able to load.
+ await iframe_B_loaded;
+ assert_not_equals(iframe_B.contentDocument, null);
+ iframe_B.contentDocument.body.appendChild(iframe_C);
+
+
+ // The document nested under about:blank must not load because it does not
+ // specify the Cross-Origin-Embedder-Policy: require-corp header.
+ // An error page must be displayed instead.
+ // See https://github.com/whatwg/html/issues/125 for why a timeout is used
+ // here. Long term all network error handling should be similar and have a
+ // reliable event.
+ assert_equals(iframe_C.contentWindow.location.href, "about:blank");
+ assert_not_equals(iframe_C.contentDocument, null);
+ await t.step_wait(() => iframe_C.contentDocument === null);
+}, "A(B(C)) A=require-corp, B=about:blank, C=no-require-corp => C can't load");
+
+</script>
diff --git a/testing/web-platform/tests/html/cross-origin-embedder-policy/require-corp-about-blank.https.html.headers b/testing/web-platform/tests/html/cross-origin-embedder-policy/require-corp-about-blank.https.html.headers
new file mode 100644
index 0000000000..8df98474b5
--- /dev/null
+++ b/testing/web-platform/tests/html/cross-origin-embedder-policy/require-corp-about-blank.https.html.headers
@@ -0,0 +1 @@
+cross-origin-embedder-policy: require-corp
diff --git a/testing/web-platform/tests/html/cross-origin-embedder-policy/require-corp-about-srcdoc.https.html b/testing/web-platform/tests/html/cross-origin-embedder-policy/require-corp-about-srcdoc.https.html
new file mode 100644
index 0000000000..5d06286d91
--- /dev/null
+++ b/testing/web-platform/tests/html/cross-origin-embedder-policy/require-corp-about-srcdoc.https.html
@@ -0,0 +1,48 @@
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+
+promise_test(t => {
+ return new Promise(resolve => {
+ window.addEventListener("DOMContentLoaded", resolve);
+ });
+}, "Wait for the DOM to be built.");
+
+promise_test(async t => {
+ let iframe = document.createElement("iframe");
+ let iframe_loaded = new Promise(resolve => iframe.onload = resolve);
+ iframe.srcdoc = "loaded document";
+ document.body.appendChild(iframe);
+
+ // The about:srcdoc document can load.
+ await iframe_loaded;
+ assert_not_equals(iframe.contentDocument, null);
+ assert_equals(iframe.contentDocument.body.innerText, "loaded document");
+}, "about:srcdoc can always be embedded by a 'require-corp' document");
+
+promise_test(async t => {
+ let iframe_C = document.createElement("iframe");
+ let iframe_B = document.createElement("iframe");
+ iframe_B.srcdoc = "dummy content";
+ iframe_C.src = "/common/blank.html";
+ let iframe_B_loaded = new Promise(resolve => iframe_B.onload = resolve);
+ document.body.appendChild(iframe_B);
+
+ // The about:srcdoc frame must be able to load.
+ await iframe_B_loaded;
+ assert_not_equals(iframe_B.contentDocument, null);
+ assert_equals(iframe_B.contentDocument.body.innerText, "dummy content");
+ iframe_B.contentDocument.body.appendChild(iframe_C);
+
+ // The document nested under about:srcdoc must not load because it does not
+ // specify the Cross-Origin-Embedder-Policy: require-corp header.
+ // An error page must be displayed instead.
+ // See https://github.com/whatwg/html/issues/125 for why a timeout is used
+ // here. Long term all network error handling should be similar and have a
+ // reliable event.
+ assert_equals(iframe_C.contentWindow.location.href, "about:blank");
+ assert_not_equals(iframe_C.contentDocument, null);
+ await t.step_wait(() => iframe_C.contentDocument === null);
+}, "A(B(C)) A=require-corp, B=about:srcdoc, C=no-require-corp => C can't load");
+
+</script>
diff --git a/testing/web-platform/tests/html/cross-origin-embedder-policy/require-corp-about-srcdoc.https.html.headers b/testing/web-platform/tests/html/cross-origin-embedder-policy/require-corp-about-srcdoc.https.html.headers
new file mode 100644
index 0000000000..8df98474b5
--- /dev/null
+++ b/testing/web-platform/tests/html/cross-origin-embedder-policy/require-corp-about-srcdoc.https.html.headers
@@ -0,0 +1 @@
+cross-origin-embedder-policy: require-corp
diff --git a/testing/web-platform/tests/html/cross-origin-embedder-policy/require-corp-cached-images.https.html b/testing/web-platform/tests/html/cross-origin-embedder-policy/require-corp-cached-images.https.html
new file mode 100644
index 0000000000..269698bc1a
--- /dev/null
+++ b/testing/web-platform/tests/html/cross-origin-embedder-policy/require-corp-cached-images.https.html
@@ -0,0 +1,74 @@
+<!doctype html>
+<html>
+<title> Images on a page Cross-Origin-Embedder-Policy: require-corp should load the same from the cache or network</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>
+
+function remote(path) {
+ const REMOTE_ORIGIN = get_host_info().HTTPS_REMOTE_ORIGIN;
+ return new URL(path, REMOTE_ORIGIN);
+}
+
+//
+// This test loads a same-origin iframe resources/load-corp-images.html with
+// Cross-Origin-Embedder-Policy: require-corp
+// The iframe loads two cross origin images, one with a
+// Cross-Origin-Resource-Policy: cross-origin header, and one without.
+// We expect the image with the header to load successfully and the one without
+// to fail to load.
+// After the first load we then reload the iframe, with the same expectations
+// for the image loads when they are loaded from the cache.
+//
+
+const RUNS = ["NETWORK", "CACHED"];
+const RESOURCE_DESC = ["No CORP image", "CORP image"];
+
+let EXPECTED_LOADS = {
+ [`${RUNS[0]} - ${RESOURCE_DESC[0]}`]: false,
+ [`${RUNS[0]} - ${RESOURCE_DESC[1]}`]: true,
+ [`${RUNS[1]} - ${RESOURCE_DESC[0]}`]: false,
+ [`${RUNS[1]} - ${RESOURCE_DESC[1]}`]: true,
+}
+
+let TESTS = {};
+for (let t in EXPECTED_LOADS) {
+ TESTS[t] = async_test(t);
+}
+
+window.addEventListener("load", async () => {
+ const t = async_test("main_test");
+ const iframe = document.createElement("iframe");
+ // The token attribute is used to ensure the resource has never been seen by
+ // the HTTP cache. This can be useful if the cache isn't properly flushed in
+ // between two tests.
+ iframe.src = `resources/load-corp-images.html?revalidate=false&token=${token()}`;
+ let runCount = 0;
+ window.addEventListener("message", (event) => {
+ // After the first done event we reload the iframe.
+ if (event.data.done) {
+ ++runCount;
+ if (runCount < RUNS.length) {
+ iframe.contentWindow.location.reload();
+ } else {
+ // After the second done event the test is finished.
+ t.done();
+ }
+ return;
+ }
+
+ // Check that each image either loads or doesn't based on the expectations
+ let testName = `${RUNS[runCount]} - ${event.data.corp ? RESOURCE_DESC[1] : RESOURCE_DESC[0]}`;
+ let test = TESTS[testName];
+ test.step(() => {
+ assert_equals(event.data.loaded, EXPECTED_LOADS[testName], `${testName} should ${EXPECTED_LOADS[testName] ? "" : "not"} succeed`);
+ });
+ test.done();
+ }, false);
+ document.body.appendChild(iframe);
+});
+
+</script>
+</html>
diff --git a/testing/web-platform/tests/html/cross-origin-embedder-policy/require-corp-cached-images.https.html.headers b/testing/web-platform/tests/html/cross-origin-embedder-policy/require-corp-cached-images.https.html.headers
new file mode 100644
index 0000000000..6604450991
--- /dev/null
+++ b/testing/web-platform/tests/html/cross-origin-embedder-policy/require-corp-cached-images.https.html.headers
@@ -0,0 +1 @@
+Cross-Origin-Embedder-Policy: require-corp
diff --git a/testing/web-platform/tests/html/cross-origin-embedder-policy/require-corp-load-from-cache-storage.https.html b/testing/web-platform/tests/html/cross-origin-embedder-policy/require-corp-load-from-cache-storage.https.html
new file mode 100644
index 0000000000..489230a776
--- /dev/null
+++ b/testing/web-platform/tests/html/cross-origin-embedder-policy/require-corp-load-from-cache-storage.https.html
@@ -0,0 +1,179 @@
+<!doctype html>
+<html>
+<title> Retrieve resources from CacheStorage with Cross-Origin-Embedder-Policy: require-corp</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+<script src="/service-workers/service-worker/resources/test-helpers.sub.js"></script>
+<script>
+
+/*
+ This document has the header Cross-Origin-Embedder-Policy: require-corp.
+ Cross-Origin Embedder Policy Editor's draft: https://mikewest.github.io/corpp/
+
+ This test is retrieving same-origin and cross-origin resources from the
+ CacheStorage. The resources are generated from the ServiceWorker or from the
+ network with the header Cross-Origin-Resource-Policy being one of:
+ - 'same-origin'
+ - 'cross-origin'
+ - <undefined>
+*/
+
+promise_test(async (t) => {
+ const SCOPE = new URL(location.href).pathname;
+ const SCRIPT =
+ 'resources/sw-store-to-cache-storage.js?' +
+ `pipe=header(service-worker-allowed,${SCOPE})`;
+
+ const reg = await service_worker_unregister_and_register(t, SCRIPT, SCOPE);
+ add_completion_callback(() => reg.unregister());
+ await new Promise(resolve => {
+ navigator.serviceWorker.addEventListener('controllerchange', resolve);
+ });
+}, 'setting up');
+
+function remote(path) {
+ const REMOTE_ORIGIN = get_host_info().HTTPS_REMOTE_ORIGIN;
+ return new URL(path, REMOTE_ORIGIN);
+}
+
+function local(path) {
+ return new URL(path, location.origin);
+}
+
+// Send a message to the currently active ServiceWorker and wait for its
+// response.
+function executeCommandInServiceWorker(command) {
+ return new Promise(resolve => {
+ navigator.serviceWorker.addEventListener('message', e => resolve(e.data));
+ navigator.serviceWorker.controller.postMessage(command);
+ });
+}
+
+// Try loading an image from a |response|. Return a Promise resolving or
+// rejecting depending on the image loading result.
+const loadFailure = {name: "Image.onerror"};
+function readImageFromResponse(response) {
+ return new Promise((resolve, reject) => {
+ const img = document.createElement("img");
+ img.onload = resolve.bind(this, "");
+ img.onerror = reject.bind(this, loadFailure);
+ response.blob().then(blob => {
+ img.src = URL.createObjectURL(blob);
+ document.body.appendChild(img);
+ })
+ })
+}
+
+const image_path = "/images/blue.png?pipe=";
+
+const corp_header = {
+ "":"",
+ "corp-undefined": "",
+ "corp-same-origin": "|header(Cross-Origin-Resource-Policy,same-origin)",
+ "corp-cross-origin": "|header(Cross-Origin-Resource-Policy,cross-origin)",
+}
+
+const cors_header = {
+ "":"",
+ "cors-disabled": "",
+ "cors-enabled": "|header(Access-Control-Allow-Origin,*)",
+}
+
+function test(
+ // Test parameters:
+ request_source, request_origin, request_mode, response_cors, response_corp,
+ // Test expectations:
+ response_stored, response_type) {
+ promise_test(async (t) => {
+ // 0. Start from an empty CacheStorage.
+ await caches.delete("v1");
+
+ // 1. Store a cross-origin no-cors response generated from the SW into the
+ // CacheStorage.
+ const path = image_path +
+ corp_header[response_corp] +
+ cors_header[response_cors];
+ const url = (request_origin === "same-origin" ? local : remote)(path);
+ const command = {
+ url: url.href,
+ mode: request_mode,
+ source: request_source,
+ };
+
+ assert_equals(await executeCommandInServiceWorker(command), response_stored);
+ if (response_stored === "not-stored") {
+ return;
+ }
+
+ // 2. Retrieve it from the CacheStorage.
+ const cache = await caches.open('v1');
+
+ if (response_type === 'error') {
+ await promise_rejects_js(t, TypeError, cache.match(url));
+ return;
+ }
+
+ const response = await cache.match(url);
+
+ assert_equals(response.type, response_type);
+
+ if (request_source === "service-worker") {
+ assert_equals("foo", await response.text());
+ return;
+ }
+
+ // Opaque response can't be read from the document.
+ if (response_type === "opaque") {
+ await promise_rejects_exactly(t, loadFailure, readImageFromResponse(response));
+ return;
+ }
+
+ await readImageFromResponse(response);
+ }, `Fetch ${request_origin} ${request_mode} ${response_cors} ${response_corp} from ${request_source} and CacheStorage.`)
+}
+
+// Responses generated from the ServiceWorker.
+{
+ test("service-worker", "cross-origin", "cors", "", "", "stored", "default");
+ test("service-worker", "cross-origin", "no-cors", "", "", "stored", "default");
+ test("service-worker", "same-origin", "cors", "", "", "stored", "default");
+ test("service-worker", "same-origin", "no-cors", "", "", "stored", "default");
+}
+
+// Responses generated from a same-origin server.
+{
+ const t = test.bind(this, "network", "same-origin");
+ t("cors", "cors-disabled", "corp-cross-origin", "stored", "basic");
+ t("cors", "cors-disabled", "corp-same-origin", "stored", "basic");
+ t("cors", "cors-disabled", "corp-undefined", "stored", "basic");
+ t("cors", "cors-enabled", "corp-cross-origin", "stored", "basic");
+ t("cors", "cors-enabled", "corp-same-origin", "stored", "basic");
+ t("cors", "cors-enabled", "corp-undefined", "stored", "basic");
+ t("no-cors", "cors-disabled", "corp-cross-origin", "stored", "basic");
+ t("no-cors", "cors-disabled", "corp-same-origin", "stored", "basic");
+ t("no-cors", "cors-disabled", "corp-undefined", "stored", "basic");
+ t("no-cors", "cors-enabled", "corp-cross-origin", "stored", "basic");
+ t("no-cors", "cors-enabled", "corp-same-origin", "stored", "basic");
+ t("no-cors", "cors-enabled", "corp-undefined", "stored", "basic");
+}
+
+// Responses generated from a cross-origin server.
+{
+ const t = test.bind(this, "network", "cross-origin");
+ t("cors", "cors-disabled", "corp-cross-origin", "not-stored");
+ t("cors", "cors-disabled", "corp-same-origin", "not-stored");
+ t("cors", "cors-disabled", "corp-undefined", "not-stored");
+ t("cors", "cors-enabled", "corp-cross-origin", "stored", "cors");
+ t("cors", "cors-enabled", "corp-same-origin", "stored", "cors");
+ t("cors", "cors-enabled", "corp-undefined", "stored", "cors");
+ t("no-cors", "cors-disabled", "corp-cross-origin", "stored", "opaque");
+ t("no-cors", "cors-disabled", "corp-same-origin", "not-stored");
+ t("no-cors", "cors-disabled", "corp-undefined", "stored", "error");
+ t("no-cors", "cors-enabled", "corp-cross-origin", "stored", "opaque");
+ t("no-cors", "cors-enabled", "corp-same-origin", "not-stored");
+ t("no-cors", "cors-enabled", "corp-undefined", "stored", "error");
+}
+
+</script>
+</html>
diff --git a/testing/web-platform/tests/html/cross-origin-embedder-policy/require-corp-load-from-cache-storage.https.html.headers b/testing/web-platform/tests/html/cross-origin-embedder-policy/require-corp-load-from-cache-storage.https.html.headers
new file mode 100644
index 0000000000..8df98474b5
--- /dev/null
+++ b/testing/web-platform/tests/html/cross-origin-embedder-policy/require-corp-load-from-cache-storage.https.html.headers
@@ -0,0 +1 @@
+cross-origin-embedder-policy: require-corp
diff --git a/testing/web-platform/tests/html/cross-origin-embedder-policy/require-corp-revalidated-images.https.html b/testing/web-platform/tests/html/cross-origin-embedder-policy/require-corp-revalidated-images.https.html
new file mode 100644
index 0000000000..420190aad3
--- /dev/null
+++ b/testing/web-platform/tests/html/cross-origin-embedder-policy/require-corp-revalidated-images.https.html
@@ -0,0 +1,76 @@
+<!doctype html>
+<html>
+<title> Images on a page Cross-Origin-Embedder-Policy: require-corp should load the same from the cache or network, even with revalidation</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>
+
+function remote(path) {
+ const REMOTE_ORIGIN = get_host_info().HTTPS_REMOTE_ORIGIN;
+ return new URL(path, REMOTE_ORIGIN);
+}
+
+//
+// This test loads a same-origin iframe resources/load-corp-images.html with
+// Cross-Origin-Embedder-Policy: require-corp
+// The iframe loads two cross origin images, one with a
+// Cross-Origin-Resource-Policy: cross-origin header, and one without.
+// We expect the image with the header to load successfully and the one without
+// to fail to load.
+// After the first load we then reload the iframe, with the same expectations
+// for the image loads when they are loaded from the cache. Because of the
+// revalidate directive, we will receive a 304 response instead of directly
+// using the cache response.
+//
+
+const RUNS = ["NETWORK", "CACHED"];
+const RESOURCE_DESC = ["No CORP image", "CORP image"];
+
+let EXPECTED_LOADS = {
+ [`${RUNS[0]} - ${RESOURCE_DESC[0]}`]: false,
+ [`${RUNS[0]} - ${RESOURCE_DESC[1]}`]: true,
+ [`${RUNS[1]} - ${RESOURCE_DESC[0]}`]: false,
+ [`${RUNS[1]} - ${RESOURCE_DESC[1]}`]: true,
+}
+
+let TESTS = {};
+for (let t in EXPECTED_LOADS) {
+ TESTS[t] = async_test(t);
+}
+
+window.addEventListener("load", async () => {
+ const t = async_test("main_test");
+ const iframe = document.createElement("iframe");
+ // The token attribute is used to ensure the resource has never been seen by
+ // the HTTP cache. This can be useful if the cache isn't properly flushed in
+ // between two tests.
+ iframe.src = `resources/load-corp-images.html?revalidate=true&token=${token()}`;
+ let runCount = 0;
+ window.addEventListener("message", (event) => {
+ // After the first done event we reload the iframe.
+ if (event.data.done) {
+ ++runCount;
+ if (runCount < RUNS.length) {
+ iframe.contentWindow.location.reload();
+ } else {
+ // After the second done event the test is finished.
+ t.done();
+ }
+ return;
+ }
+
+ // Check that each image either loads or doesn't based on the expectations
+ let testName = `${RUNS[runCount]} - ${event.data.corp ? RESOURCE_DESC[1] : RESOURCE_DESC[0]}`;
+ let test = TESTS[testName];
+ test.step(() => {
+ assert_equals(event.data.loaded, EXPECTED_LOADS[testName], `${testName} should ${EXPECTED_LOADS[testName] ? "" : "not"} succeed`);
+ });
+ test.done();
+ }, false);
+ document.body.appendChild(iframe);
+});
+
+</script>
+</html>
diff --git a/testing/web-platform/tests/html/cross-origin-embedder-policy/require-corp-sw-from-none.https.html b/testing/web-platform/tests/html/cross-origin-embedder-policy/require-corp-sw-from-none.https.html
new file mode 100644
index 0000000000..451761e818
--- /dev/null
+++ b/testing/web-platform/tests/html/cross-origin-embedder-policy/require-corp-sw-from-none.https.html
@@ -0,0 +1,92 @@
+<!doctype html>
+<html>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script src="/common/get-host-info.sub.js"></script>
+<script src="/service-workers/service-worker/resources/test-helpers.sub.js"></script>
+<script>
+const SCOPE = new URL(location.href).pathname;
+const SCRIPT =
+ 'resources/sw.js?' +
+ `pipe=header(service-worker-allowed,${SCOPE})|` +
+ 'header(cross-origin-embedder-policy,require-corp)';
+
+function remote(path) {
+ const REMOTE_ORIGIN = get_host_info().HTTPS_REMOTE_ORIGIN;
+ return new URL(path, REMOTE_ORIGIN + '/html/cross-origin-embedder-policy/');
+}
+
+promise_test(async (t) => {
+ const reg = await service_worker_unregister_and_register(t, SCRIPT, SCOPE);
+ add_completion_callback(() => {
+ reg.unregister();
+ });
+ await new Promise(resolve => {
+ navigator.serviceWorker.addEventListener('controllerchange', resolve);
+ });
+}, 'setting up');
+
+promise_test(async (t) => {
+ await fetch('resources/nothing-same-origin-corp.txt', {mode: 'no-cors'});
+}, 'making a same-origin request for CORP: same-origin');
+
+promise_test(async (t) => {
+ await fetch('/common/blank.html', {mode: 'no-cors'});
+}, 'making a same-origin request for no CORP');
+
+promise_test(async (t) => {
+ await fetch('resources/nothing-cross-origin-corp.js', {mode: 'no-cors'});
+}, 'making a same-origin request for CORP: cross-origin');
+
+promise_test(async (t) => {
+ await promise_rejects_js(
+ t, TypeError,
+ fetch(remote('resources/nothing-same-origin-corp.txt'), {mode: 'no-cors'}));
+}, 'making a cross-origin request for CORP: same-origin');
+
+promise_test(async (t) => {
+ await promise_rejects_js(
+ t, TypeError, fetch(remote('/common/blank.html'), {mode: 'no-cors'}));
+}, 'making a cross-origin request for no CORP');
+
+promise_test(async (t) => {
+ await fetch(
+ remote('resources/nothing-cross-origin-corp.js'),
+ {mode: 'no-cors'});
+}, 'making a cross-origin request for CORP: cross-origin');
+
+promise_test(async (t) => {
+ await promise_rejects_js(
+ t, TypeError,
+ fetch(remote('resources/nothing-same-origin-corp.txt?passthrough'),
+ {mode: 'no-cors'}));
+}, 'making a cross-origin request for CORP: same-origin [PASS THROUGH]');
+
+promise_test(async (t) => {
+ await fetch(remote('/common/blank.html?passthrough'), {mode: 'no-cors'});
+}, 'making a cross-origin request for no CORP [PASS THROUGH]');
+
+promise_test(async (t) => {
+ await fetch(
+ remote('resources/nothing-cross-origin-corp.js?passthrough'),
+ {mode: 'no-cors'});
+}, 'making a cross-origin request for CORP: cross-origin [PASS THROUGH]');
+
+promise_test(async (t) => {
+ await promise_rejects_js(
+ t, TypeError, fetch(remote('/common/blank.html'), {mode: 'cors'}));
+}, 'making a cross-origin request with CORS without ACAO');
+
+promise_test(async (t) => {
+ const URL = remote(
+ '/common/blank.html?pipe=header(access-control-allow-origin,*)');
+ await fetch(URL, {mode: 'cors'});
+}, 'making a cross-origin request with CORS');
+
+promise_test(async (t) => {
+ const URL = remote('/fetch/api/resources/preflight.py?allow_headers=hoge');
+ await fetch(URL, {mode: 'cors', headers: {'hoge': 'fuga'}});
+}, 'making a cross-origin request with CORS-preflight');
+
+</script>
+</html>
diff --git a/testing/web-platform/tests/html/cross-origin-embedder-policy/require-corp-sw-from-require-corp.https.html b/testing/web-platform/tests/html/cross-origin-embedder-policy/require-corp-sw-from-require-corp.https.html
new file mode 100644
index 0000000000..c270e3a0d6
--- /dev/null
+++ b/testing/web-platform/tests/html/cross-origin-embedder-policy/require-corp-sw-from-require-corp.https.html
@@ -0,0 +1,93 @@
+<!doctype html>
+<html>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script src="/common/get-host-info.sub.js"></script>
+<script src="/service-workers/service-worker/resources/test-helpers.sub.js"></script>
+<script>
+const SCOPE = new URL(location.href).pathname;
+const SCRIPT =
+ 'resources/sw.js?' +
+ `pipe=header(service-worker-allowed,${SCOPE})|` +
+ 'header(cross-origin-embedder-policy,require-corp)';
+
+function remote(path) {
+ const REMOTE_ORIGIN = get_host_info().HTTPS_REMOTE_ORIGIN;
+ return new URL(path, REMOTE_ORIGIN + '/html/cross-origin-embedder-policy/');
+}
+
+promise_test(async (t) => {
+ const reg = await service_worker_unregister_and_register(t, SCRIPT, SCOPE);
+ add_completion_callback(() => {
+ reg.unregister();
+ });
+ await new Promise(resolve => {
+ navigator.serviceWorker.addEventListener('controllerchange', resolve);
+ });
+}, 'setting up');
+
+promise_test(async (t) => {
+ await fetch('resources/nothing-same-origin-corp.txt', {mode: 'no-cors'});
+}, 'making a same-origin request for CORP: same-origin');
+
+promise_test(async (t) => {
+ await fetch('/common/blank.html', {mode: 'no-cors'});
+}, 'making a same-origin request for no CORP');
+
+promise_test(async (t) => {
+ await fetch('resources/nothing-cross-origin-corp.js', {mode: 'no-cors'});
+}, 'making a same-origin request for CORP: cross-origin');
+
+promise_test(async (t) => {
+ await promise_rejects_js(
+ t, TypeError,
+ fetch(remote('resources/nothing-same-origin-corp.txt'), {mode: 'no-cors'}));
+}, 'making a cross-origin request for CORP: same-origin');
+
+promise_test(async (t) => {
+ await promise_rejects_js(
+ t, TypeError, fetch(remote('/common/blank.html'), {mode: 'no-cors'}));
+}, 'making a cross-origin request for no CORP');
+
+promise_test(async (t) => {
+ await fetch(
+ remote('resources/nothing-cross-origin-corp.js'),
+ {mode: 'no-cors'});
+}, 'making a cross-origin request for CORP: cross-origin');
+
+promise_test(async (t) => {
+ await promise_rejects_js(
+ t, TypeError,
+ fetch(remote('resources/nothing-same-origin-corp.txt?passthrough'),
+ {mode: 'no-cors'}));
+}, 'making a cross-origin request for CORP: same-origin [PASS THROUGH]');
+
+promise_test(async (t) => {
+ await promise_rejects_js(
+ t, TypeError,
+ fetch(remote('/common/blank.html?passthrough'), {mode: 'no-cors'}));
+}, 'making a cross-origin request for no CORP [PASS THROUGH]');
+
+promise_test(async (t) => {
+ await fetch(
+ remote('resources/nothing-cross-origin-corp.js?passthrough'),
+ {mode: 'no-cors'});
+}, 'making a cross-origin request for CORP: cross-origin [PASS THROUGH]');
+
+promise_test(async (t) => {
+ await promise_rejects_js(
+ t, TypeError, fetch(remote('/common/blank.html'), {mode: 'cors'}));
+}, 'making a cross-origin request with CORS without ACAO');
+
+promise_test(async (t) => {
+ const URL = remote(
+ '/common/blank.html?pipe=header(access-control-allow-origin,*)');
+ await fetch(URL, {mode: 'cors'});
+}, 'making a cross-origin request with CORS');
+
+promise_test(async (t) => {
+ const URL = remote('/fetch/api/resources/preflight.py?allow_headers=hoge');
+ await fetch(URL, {mode: 'cors', headers: {'hoge': 'fuga'}});
+}, 'making a cross-origin request with CORS-preflight');
+</script>
+</html>
diff --git a/testing/web-platform/tests/html/cross-origin-embedder-policy/require-corp-sw-from-require-corp.https.html.headers b/testing/web-platform/tests/html/cross-origin-embedder-policy/require-corp-sw-from-require-corp.https.html.headers
new file mode 100644
index 0000000000..8df98474b5
--- /dev/null
+++ b/testing/web-platform/tests/html/cross-origin-embedder-policy/require-corp-sw-from-require-corp.https.html.headers
@@ -0,0 +1 @@
+cross-origin-embedder-policy: require-corp
diff --git a/testing/web-platform/tests/html/cross-origin-embedder-policy/require-corp-sw.https.html b/testing/web-platform/tests/html/cross-origin-embedder-policy/require-corp-sw.https.html
new file mode 100644
index 0000000000..05272d41a4
--- /dev/null
+++ b/testing/web-platform/tests/html/cross-origin-embedder-policy/require-corp-sw.https.html
@@ -0,0 +1,53 @@
+<!doctype html>
+<title>Cross Origin Embedder Policy: requests initiated from a service worker with 'require-corp'</title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script src="/common/get-host-info.sub.js"></script>
+<script src="/service-workers/service-worker/resources/test-helpers.sub.js"></script>
+<script>
+'use strict';
+
+const SCRIPT = 'resources/require-corp-sw.js';
+const SCOPE = 'resources/in-scope';
+let worker = null;
+
+promise_test(async t => {
+ const registration =
+ await service_worker_unregister_and_register(t, SCRIPT, SCOPE);
+ promise_test(async t => registration.unregister(), 'Clean up global state');
+ worker = registration.installing;
+ await wait_for_state(t, worker, 'activated');
+}, 'Set up global state');
+
+promise_test(async t => {
+ const p = new Promise(resolve =>
+ navigator.serviceWorker.addEventListener('message', resolve,
+ {once: true}));
+ worker.postMessage('WithCorp');
+ assert_equals((await p).data, 'opaque');
+}, "fetch() to 'CORP: cross-origin' response should succeed.");
+
+promise_test(async t => {
+ const p = new Promise(resolve =>
+ navigator.serviceWorker.addEventListener('message', resolve,
+ {once: true}));
+ worker.postMessage('WithoutCorp');
+ assert_equals((await p).data, 'Exception: TypeError');
+}, "fetch() to no CORP response should not succeed.");
+
+promise_test(async t => {
+ const scope = `${SCOPE}-2`;
+ await service_worker_unregister(t, scope);
+ const promise = navigator.serviceWorker.register(
+ 'resources/require-corp-sw-import-scripts.js', {scope});
+ await promise_rejects_js(t, TypeError, promise, 'register() should fail.');
+}, 'importScripts() fails for a script with no corp.');
+
+promise_test(async t => {
+ const scope = `${SCOPE}-3`;
+ await service_worker_unregister(t, scope);
+ const registration = await navigator.serviceWorker.register(
+ 'resources/require-corp-sw-import-scripts.js?corp=cross-origin', {scope});
+ t.add_cleanup(() => registration.unregister());
+}, 'importScripts() succeeds for a script with corp: cross-origin.');
+</script>
diff --git a/testing/web-platform/tests/html/cross-origin-embedder-policy/require-corp-worker-script-revalidation.html b/testing/web-platform/tests/html/cross-origin-embedder-policy/require-corp-worker-script-revalidation.html
new file mode 100644
index 0000000000..74794967fb
--- /dev/null
+++ b/testing/web-platform/tests/html/cross-origin-embedder-policy/require-corp-worker-script-revalidation.html
@@ -0,0 +1,25 @@
+<!doctype html>
+<title>COEP and dedicated worker</title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script src="/common/get-host-info.sub.js"></script>
+<script src="resources/worker-support.js"></script>
+<body>
+<script>
+
+promise_test(async (t) => {
+ const worker1 = new Worker("/html/cross-origin-embedder-policy/resources/dedicated-worker-supporting-revalidation.py");
+ worker1.onerror = t.unreached_func('Worker.onerror should not be called for first worker');
+ worker1.postMessage("foo");
+ const result1 = await waitForMessage(worker1);
+ assert_equals(result1.data, 'LOADED');
+
+ // Load the worker a second time, which should trigger revalidation of the cached resource.
+ const worker2 = new Worker("/html/cross-origin-embedder-policy/resources/dedicated-worker-supporting-revalidation.py");
+ worker2.onerror = t.unreached_func('Worker.onerror should not be called worker second worker');
+ worker2.postMessage("foo");
+ const result2 = await waitForMessage(worker2);
+ assert_equals(result2.data, 'LOADED');
+}, 'COEP: require-corp with revalidated worker script');
+</script>
+</body>
diff --git a/testing/web-platform/tests/html/cross-origin-embedder-policy/require-corp.https.html b/testing/web-platform/tests/html/cross-origin-embedder-policy/require-corp.https.html
new file mode 100644
index 0000000000..ad64a485c0
--- /dev/null
+++ b/testing/web-platform/tests/html/cross-origin-embedder-policy/require-corp.https.html
@@ -0,0 +1,251 @@
+<!doctype html>
+<meta name="timeout" content="long">
+<title>Cross-Origin-Embedder-Policy header and nested navigable resource without such 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> <!-- Use token() to allow running tests in parallel -->
+<div id=log></div>
+<script>
+const HOST = get_host_info();
+const BASE = new URL("resources", location).pathname;
+
+async_test(t => {
+ const frame = document.createElement("iframe");
+ t.add_cleanup(() => frame.remove());
+ frame.src = "/common/blank.html";
+ document.body.append(frame);
+ // Make sure the iframe didn't load. See https://github.com/whatwg/html/issues/125 for why a
+ // timeout is used here. Long term all network error handling should be similar and have a
+ // reliable event.
+ assert_equals(frame.contentDocument.body.localName, "body");
+ t.step_wait_func_done(() => frame.contentDocument === null);
+}, `"require-corp" top-level: navigating a frame to "none" should fail`);
+
+async_test(t => {
+ const frame = document.createElement("iframe");
+ t.add_cleanup(() => frame.remove());
+ const bc = new BroadcastChannel(token());
+ bc.onmessage = t.step_func((event) => {
+ let payload = event.data;
+ assert_equals(payload, "loaded");
+ t.step_wait_func_done(() => frame.contentDocument === null);
+ });
+ frame.src = `resources/navigate-require-corp.sub.html?channelName=${bc.name}&to=/common/blank.html`;
+ document.body.append(frame);
+ assert_equals(frame.contentDocument.body.localName, "body");
+}, `"require-corp" top-level: navigating a frame from "require-corp" to "none" should fail`);
+
+async_test(t => {
+ let pageLoaded = false;
+ const bc = new BroadcastChannel(token());
+ let finished = false;
+ bc.onmessage = t.step_func((event) => {
+ let payload = event.data;
+ assert_equals(payload, "loaded");
+ pageLoaded = true;
+ });
+
+ const bc2 = new BroadcastChannel(token());
+ bc2.onmessage = t.step_func_done((event) => {
+ let payload = event.data;
+ assert_equals(payload, "loaded");
+ assert_equals(pageLoaded, true);
+ });
+
+ const win = window.open(`resources/navigate-none.sub.html?channelName=${bc.name}&to=navigate-none.sub.html?channelName=${bc2.name}`, "_blank", "noopener");
+ assert_equals(win, null);
+}, `"require-corp" top-level: creating a noopener "none" popup should succeed`);
+
+async_test(t => {
+ let pageLoaded = false;
+ const bc = new BroadcastChannel(token());
+ bc.onmessage = t.step_func_done((event) => {
+ pageLoaded = true;
+ let payload = event.data;
+ assert_equals(payload, "loaded");
+ });
+
+ const win = window.open(`resources/navigate-none.sub.html?channelName=${bc.name}&to=/common/blank.html`, "_blank");
+ t.add_cleanup(() => win.close());
+}, `"require-corp" top-level: creating a "none" popup should succeed.`);
+
+[
+ {
+ "name": "",
+ "title": "as popup"
+ },
+ {
+ "name": "noopener",
+ "title": "as noopener popup"
+ },
+ {
+ "name": "clear opener",
+ "title": "as popup with opener set to null"
+ }
+].forEach(({name, title}) => {
+ async_test(t => {
+ let pageLoaded = false;
+ const bc = new BroadcastChannel(token());
+ bc.onmessage = t.step_func(event => {
+ pageLoaded = true;
+ const payload = event.data;
+ assert_equals(payload, "loaded");
+ });
+
+ const bc2 = new BroadcastChannel(token());
+ bc2.onmessage = t.step_func_done(event => {
+ const payload = event.data;
+ assert_equals(payload, "loaded");
+ assert_equals(pageLoaded, true);
+ });
+
+ let clearOpener = "";
+ if (name === "clear opener") {
+ clearOpener = "&clearOpener=true"
+ }
+
+ let noopener = undefined;
+ if (name === "noopener") {
+ noopener = "noopener"
+ }
+
+ const win = window.open(`resources/navigate-require-corp.sub.html?channelName=${bc.name}${clearOpener}&to=navigate-none.sub.html?channelName=${bc2.name}`, "_blank", noopener);
+ if (name === "noopener") {
+ assert_equals(win, null);
+ } else {
+ t.add_cleanup(() => win.close());
+ }
+ }, `"require-corp" top-level (${title}): navigating to "none" should succeed`);
+});
+
+promise_test(async t => {
+ const response = await fetch(get_host_info().HTTPS_REMOTE_ORIGIN+"/html/cross-origin-embedder-policy/resources/nothing-cross-origin-corp.js", {mode: "no-cors"});
+ assert_equals(response.type, "opaque");
+}, `"require-corp" top-level: fetch() to CORP: cross-origin response should succeed`);
+
+promise_test(t => {
+ return promise_rejects_js(t, TypeError, fetch(get_host_info().HTTPS_REMOTE_ORIGIN+"/common/blank.html", {mode: "no-cors"}));
+}, `"require-corp" top-level: fetch() to response without CORP should fail`);
+
+promise_test(t => {
+ const w = window.open();
+ return promise_rejects_js(t, w.TypeError, w.fetch(get_host_info().HTTPS_REMOTE_ORIGIN+"/common/blank.html", {mode: "no-cors"}));
+}, `"require-corp" top-level: fetch() to response without CORP through a WindowProxy should fail`);
+
+async_test(t => {
+ let w = window.open();
+ const frame = w.document.createElement("iframe");
+ t.add_cleanup(() => {
+ w.close();
+ frame.remove();
+ });
+ frame.src = "/common/blank.html";
+ document.body.append(frame);
+ // Make sure the iframe didn't load. See https://github.com/whatwg/html/issues/125 for why a
+ // timeout is used here. Long term all network error handling should be similar and have a
+ // reliable event.
+ assert_equals(frame.contentDocument.body.localName, "body");
+ t.step_wait_func_done(() => frame.contentDocument === null);
+}, `"require-corp" top-level: navigating an iframe to a page without CORP, through a WindowProxy, should fail`);
+
+async_test(t => {
+ const frame = document.createElement("iframe");
+ const id = token();
+ t.add_cleanup(() => frame.remove());
+ window.addEventListener('message', t.step_func((e) => {
+ if (e.data === id) {
+ // Loaded!
+ t.done();
+ }
+ }));
+ // REMOTE_ORIGIN is cross-origin, same-site.
+ frame.src = `${HOST.HTTPS_REMOTE_ORIGIN}${BASE}/navigate-require-corp-same-site.sub.html?token=${id}`;
+ document.body.append(frame);
+}, 'CORP: same-site is checked and allowed.');
+
+async_test(t => {
+ const frame = document.createElement("iframe");
+ const id = token();
+ t.add_cleanup(() => frame.remove());
+ let loaded = false;
+ window.addEventListener('message', t.step_func((e) => {
+ if (e.data === id) {
+ loaded = true;
+ }
+ }));
+ t.step_timeout(() => {
+ // Make sure the iframe didn't load. See https://github.com/whatwg/html/issues/125 for why a
+ // timeout is used here. Long term all network error handling should be similar and have a
+ // reliable event.
+ assert_false(loaded);
+ t.done();
+ }, 2000);
+
+ // NOTESAMESITE_ORIGIN is cross-origin, cross-site.
+ frame.src = `${HOST.HTTPS_NOTSAMESITE_ORIGIN}${BASE}/navigate-require-corp-same-site.sub.html?token=${id}`;
+ document.body.append(frame);
+}, 'CORP: same-site is checked and blocked.');
+
+async_test(t => {
+ const frame = document.createElement("iframe");
+ const bc = new BroadcastChannel(token());
+ t.add_cleanup(() => frame.remove());
+ bc.onmessage = t.step_func_done((event) => {
+ const payload = event.data;
+ assert_equals(payload, "loaded");
+ });
+
+ const dest = `${HOST.ORIGIN}${BASE}/navigate-require-corp.sub.html?channelName=${bc.name}`;
+ // REMOTE_ORIGIN is cross-origin, same-site.
+ frame.src = `${HOST.REMOTE_ORIGIN}${BASE}/navigate-require-corp-same-site.sub.html?to=${encodeURIComponent(dest)}`;
+ document.body.append(frame);
+}, 'navigation CORP is checked with the parent frame, not the navigation source - to be allowed');
+
+async_test(t => {
+ const frame = document.createElement("iframe");
+ const bc = new BroadcastChannel(token());
+ t.add_cleanup(() => frame.remove());
+ let loaded = false;
+ bc.onmessage = t.step_func((event) => {
+ loaded = true;
+ });
+ t.step_timeout(() => {
+ // Make sure the iframe didn't load. See https://github.com/whatwg/html/issues/125 for why a
+ // timeout is used here. Long term all network error handling should be similar and have a
+ // reliable event.
+ assert_false(loaded);
+ t.done();
+ }, 2000);
+
+ const dest = `${HOST.REMOTE_ORIGIN}${BASE}/navigate-require-corp.sub.html?channelName=${bc.name}`;
+ // REMOTE_ORIGIN is cross-origin, same-site.
+ frame.src = `${HOST.REMOTE_ORIGIN}${BASE}/navigate-require-corp-same-site.sub.html?to=${encodeURIComponent(dest)}`;
+ document.body.append(frame);
+}, 'navigation CORP is checked with the parent frame, not the navigation source - to be blocked');
+
+async_test(t => {
+ const bc = new BroadcastChannel(token());
+ let loaded = false;
+ bc.onmessage = t.step_func((event) => {
+ loaded = true;
+ });
+ t.step_timeout(() => {
+ // Make sure the iframe didn't load. See
+ // https://github.com/whatwg/html/issues/125 for why a timeout is used
+ // here. Long term all network error handling should be similar and have a
+ // reliable event.
+ assert_false(loaded);
+ t.done();
+ }, 2000);
+
+ const dest = `${HOST.ORIGIN}${BASE}/navigate-require-corp.sub.html?channelName=${bc.name}`;
+ const frame = document.createElement("iframe");
+ t.add_cleanup(() => frame.remove());
+ // |dest| is a same-origin URL and hence not blocked by CORP but reidirect.py
+ // is a cross-origin (actually cross-site) URL, so blocked by CORP.
+ frame.src = `${HOST.HTTPS_NOTSAMESITE_ORIGIN}/common/redirect.py?location=${encodeURIComponent(dest)}`;
+ document.body.append(frame);
+}, 'navigation CORP is checked for each redirect');
+
+</script>
diff --git a/testing/web-platform/tests/html/cross-origin-embedder-policy/require-corp.https.html.headers b/testing/web-platform/tests/html/cross-origin-embedder-policy/require-corp.https.html.headers
new file mode 100644
index 0000000000..6604450991
--- /dev/null
+++ b/testing/web-platform/tests/html/cross-origin-embedder-policy/require-corp.https.html.headers
@@ -0,0 +1 @@
+Cross-Origin-Embedder-Policy: require-corp
diff --git a/testing/web-platform/tests/html/cross-origin-embedder-policy/resources/blob-url-factory.html b/testing/web-platform/tests/html/cross-origin-embedder-policy/resources/blob-url-factory.html
new file mode 100644
index 0000000000..928d404672
--- /dev/null
+++ b/testing/web-platform/tests/html/cross-origin-embedder-policy/resources/blob-url-factory.html
@@ -0,0 +1,24 @@
+<body>
+<script src="script-factory.js"></script>
+<script>
+const query = new URLSearchParams(window.location.search);
+const id = query.get("id");
+const variant = query.get("variant");
+let parent = "parent";
+if (variant === "subframe") {
+ parent = "parent.parent";
+} else if (variant === "popup") {
+ parent = "opener.parent";
+}
+const blob = new Blob([`<script>${createScript(window.origin, query.get("crossOrigin"), parent, id)}<\/script>`], { type: "text/html" });
+const blobURL = URL.createObjectURL(blob);
+if (variant === "subframe") {
+ const frame = document.createElement("iframe");
+ frame.src = blobURL;
+ document.body.append(frame);
+} else if (variant === "popup") {
+ window.open(blobURL);
+} else {
+ window.location = blobURL;
+}
+</script>
diff --git a/testing/web-platform/tests/html/cross-origin-embedder-policy/resources/blob-url-factory.html.headers b/testing/web-platform/tests/html/cross-origin-embedder-policy/resources/blob-url-factory.html.headers
new file mode 100644
index 0000000000..4e798cd9f5
--- /dev/null
+++ b/testing/web-platform/tests/html/cross-origin-embedder-policy/resources/blob-url-factory.html.headers
@@ -0,0 +1,2 @@
+Cross-Origin-Embedder-Policy: require-corp
+Cross-Origin-Resource-Policy: cross-origin
diff --git a/testing/web-platform/tests/html/cross-origin-embedder-policy/resources/cache-storage-reporting.js b/testing/web-platform/tests/html/cross-origin-embedder-policy/resources/cache-storage-reporting.js
new file mode 100644
index 0000000000..86dff9c845
--- /dev/null
+++ b/testing/web-platform/tests/html/cross-origin-embedder-policy/resources/cache-storage-reporting.js
@@ -0,0 +1,63 @@
+function remote(path) {
+ const REMOTE_ORIGIN = get_host_info().HTTPS_REMOTE_ORIGIN;
+ return new URL(path, REMOTE_ORIGIN).href;
+}
+
+function local(path) {
+ return new URL(path, location.origin).href;
+}
+
+function encode(url) {
+ return encodeURI(url).replace(/\;/g, '%3B');
+}
+
+const resource_path = (new URL('./resources', location)).pathname;
+const header_coep = '|header(Cross-Origin-Embedder-Policy,require-corp)';
+const header_coep_report_only =
+ '|header(Cross-Origin-Embedder-Policy-Report-Only,require-corp)';
+
+const iframe_path = resource_path + '/iframe.html?pipe=';
+const worker_path = resource_path + '/reporting-worker.js?pipe=';
+const image_url = remote('/images/blue.png');
+
+// This script attempt to load a COEP:require-corp CORP:undefined response from
+// the CacheStorage.
+//
+// Executed from different context:
+// - A Document
+// - A ServiceWorker
+// - A DedicatedWorker
+// - A SharedWorker
+//
+// The context has either COEP or COEP-Report-Only defined.
+const eval_script = `
+ (async function() {
+ try {
+ const cache = await caches.open('v1');
+ const request = new Request('${image_url}', { mode: 'no-cors' });
+ const response = await cache.match(request);
+ } catch(e) {
+ }
+ })()
+`;
+
+promise_setup(async (t) => {
+ const cache = await caches.open('v1');
+ const request = new Request(image_url, {mode: 'no-cors'});
+ const response = await fetch(request);
+ await cache.put(request, response);
+}, 'Setup: store a CORS:cross-origin COEP:none response into CacheStorage')
+
+async function makeIframe(test, iframe_url) {
+ const iframe = document.createElement('iframe');
+ test.add_cleanup(() => iframe.remove());
+ iframe.src = iframe_url;
+ const iframe_loaded = new Promise(resolve => iframe.onload = resolve);
+ document.body.appendChild(iframe);
+ await iframe_loaded;
+ return iframe;
+}
+
+function wait(ms) {
+ return new Promise(resolve => step_timeout(resolve, ms));
+}
diff --git a/testing/web-platform/tests/html/cross-origin-embedder-policy/resources/coep-frame.html b/testing/web-platform/tests/html/cross-origin-embedder-policy/resources/coep-frame.html
new file mode 100644
index 0000000000..78c1331132
--- /dev/null
+++ b/testing/web-platform/tests/html/cross-origin-embedder-policy/resources/coep-frame.html
@@ -0,0 +1,2 @@
+<!doctype html>
+<p><code>Cross-Origin-Embedder-Policy: require-corp</code> header set.
diff --git a/testing/web-platform/tests/html/cross-origin-embedder-policy/resources/coep-frame.html.headers b/testing/web-platform/tests/html/cross-origin-embedder-policy/resources/coep-frame.html.headers
new file mode 100644
index 0000000000..6604450991
--- /dev/null
+++ b/testing/web-platform/tests/html/cross-origin-embedder-policy/resources/coep-frame.html.headers
@@ -0,0 +1 @@
+Cross-Origin-Embedder-Policy: require-corp
diff --git a/testing/web-platform/tests/html/cross-origin-embedder-policy/resources/common.js b/testing/web-platform/tests/html/cross-origin-embedder-policy/resources/common.js
new file mode 100644
index 0000000000..8f038a7278
--- /dev/null
+++ b/testing/web-platform/tests/html/cross-origin-embedder-policy/resources/common.js
@@ -0,0 +1,19 @@
+async function createIsolatedFrame(origin, headers) {
+ const parent = document.createElement('iframe');
+ const parent_loaded = new Promise(r => parent.onload = () => { r(parent); });
+ const error = new Promise(r => parent.onerror = r);
+ parent.src = origin + "/common/blank.html?pipe=" + headers;
+ parent.anonymous = false;
+ document.body.appendChild(parent);
+ return [parent_loaded, error];
+}
+
+async function IsCrossOriginIsolated(from_token) {
+ const reply_token = token();
+ send(from_token, `
+ send("${reply_token}", self.crossOriginIsolated);
+ `);
+ const reply = await receive(reply_token);
+ assert_true(reply.match(/true|false/) != null);
+ return reply == 'true';
+} \ No newline at end of file
diff --git a/testing/web-platform/tests/html/cross-origin-embedder-policy/resources/corp-image.py b/testing/web-platform/tests/html/cross-origin-embedder-policy/resources/corp-image.py
new file mode 100644
index 0000000000..e507846181
--- /dev/null
+++ b/testing/web-platform/tests/html/cross-origin-embedder-policy/resources/corp-image.py
@@ -0,0 +1,31 @@
+import json
+import base64
+
+# A 1x1 PNG image.
+# Source: https://commons.wikimedia.org/wiki/File:1x1.png (Public Domain)
+IMAGE = "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABAQMAAAAl21bKAAAAA1BMVEUAAACnej3aAAAAAXRSTlMAQObYZgAAAApJREFUCNdjYAAAAAIAAeIhvDMAAAAASUVORK5CYII="
+
+def main(request, response):
+ response.headers.set(b'Access-Control-Allow-Origin', b'*')
+ response.headers.set(b'Access-Control-Allow-Methods', b'OPTIONS, GET, POST')
+ response.headers.set(b'Access-Control-Allow-Headers', b'Content-Type')
+
+ # CORS preflight
+ if request.method == u'OPTIONS':
+ return u''
+
+ if b'true' == request.GET.get(b'revalidate', None):
+ response.headers.set(b'Cache-Control', b'max-age=0, must-revalidate')
+ else:
+ response.headers.set(b'Cache-Control', b'max-age=3600');
+
+ if b'some-etag' == request.headers.get(b'If-None-Match', None):
+ response.status = 304
+ return u''
+
+ if request.GET.get(b'corp-cross-origin', None):
+ response.headers.set(b'Cross-Origin-Resource-Policy', b'cross-origin')
+
+ response.headers.set(b'Etag', b'some-etag')
+ response.headers.set(b'Content-Type', b'image/png')
+ return base64.b64decode(IMAGE)
diff --git a/testing/web-platform/tests/html/cross-origin-embedder-policy/resources/dedicated-worker-supporting-revalidation.py b/testing/web-platform/tests/html/cross-origin-embedder-policy/resources/dedicated-worker-supporting-revalidation.py
new file mode 100755
index 0000000000..eef86d1c55
--- /dev/null
+++ b/testing/web-platform/tests/html/cross-origin-embedder-policy/resources/dedicated-worker-supporting-revalidation.py
@@ -0,0 +1,15 @@
+#!/usr/bin/env python
+
+
+def main(request, response):
+ headers = []
+ if request.headers.get(b'if-none-match', None):
+ status = 304, u"Not Modified"
+ return status, headers, u""
+ else:
+ headers.append((b"Content-Type", b"text/javascript"))
+ headers.append((b"Cross-Origin-Embedder-Policy", b"require-corp"))
+ headers.append((b"Cache-Control", b"private, max-age=0, must-revalidate"))
+ headers.append((b"ETag", b"abcdef"))
+ status = 200, u"OK"
+ return status, headers, u"self.onmessage = (e) => { self.postMessage('LOADED'); };"
diff --git a/testing/web-platform/tests/html/cross-origin-embedder-policy/resources/dedicated-worker.js b/testing/web-platform/tests/html/cross-origin-embedder-policy/resources/dedicated-worker.js
new file mode 100644
index 0000000000..66f3cc3d41
--- /dev/null
+++ b/testing/web-platform/tests/html/cross-origin-embedder-policy/resources/dedicated-worker.js
@@ -0,0 +1,7 @@
+self.onmessage = (e) => {
+ fetch(e.data, {mode: 'no-cors'}).then(() => {
+ self.postMessage('LOADED');
+ }, () => {
+ self.postMessage('FAILED');
+ });
+};
diff --git a/testing/web-platform/tests/html/cross-origin-embedder-policy/resources/empty-coep.py b/testing/web-platform/tests/html/cross-origin-embedder-policy/resources/empty-coep.py
new file mode 100644
index 0000000000..d0e547b130
--- /dev/null
+++ b/testing/web-platform/tests/html/cross-origin-embedder-policy/resources/empty-coep.py
@@ -0,0 +1,7 @@
+def main(request, response):
+ headers = [(b'Content-Type', b'text/html')]
+
+ for value in request.GET.get_list(b'value'):
+ headers.append((b'Cross-Origin-Embedder-Policy', value))
+
+ return (200, headers, u'')
diff --git a/testing/web-platform/tests/html/cross-origin-embedder-policy/resources/fetch-and-create-url.html b/testing/web-platform/tests/html/cross-origin-embedder-policy/resources/fetch-and-create-url.html
new file mode 100644
index 0000000000..6b0f96221d
--- /dev/null
+++ b/testing/web-platform/tests/html/cross-origin-embedder-policy/resources/fetch-and-create-url.html
@@ -0,0 +1,91 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>Fetch and create Blob</title>
+<script>
+ async function responseToBlob(response) {
+ let blob;
+ try {
+ blob = await response.blob();
+ } catch (e) {
+ return { error: `blob error: ${e.name}` };
+ }
+
+ return { url: URL.createObjectURL(blob) };
+ }
+
+ async function responseToData(response) {
+ const mimeType = response.headers.get("content-type");
+
+ let text;
+ try {
+ text = await response.text();
+ } catch(e) {
+ return { error: `text error: ${e.name}` };
+ }
+
+ return { url: `data:${mimeType},${encodeURIComponent(text)}` };
+ }
+
+ async function responseToFilesystem(response) {
+ if (!window.webkitRequestFileSystem) {
+ return { error: "unimplemented" };
+ }
+
+ let blob;
+ try {
+ blob = await response.blob();
+ } catch (e) {
+ return { error: `blob error: ${e.name}` };
+ }
+
+ const fs = await new Promise(resolve => {
+ window.webkitRequestFileSystem(window.TEMPORARY, 1024*1024, resolve);
+ });
+
+ const file = await new Promise(resolve => {
+ fs.root.getFile('fetch-and-create-url', { create: true }, resolve);
+ });
+
+ const writer = await new Promise(resolve => file.createWriter(resolve));
+
+ try {
+ await new Promise((resolve, reject) => {
+ writer.onwriteend = resolve;
+ writer.onerror = reject;
+ writer.write(blob);
+ });
+ } catch (e) {
+ return { error: `file write error: ${e.name}` };
+ }
+
+ return { url: file.toURL() };
+ }
+
+ async function responseToScheme(response, scheme) {
+ switch (scheme) {
+ case "blob":
+ return responseToBlob(response);
+ case "data":
+ return responseToData(response);
+ case "filesystem":
+ return responseToFilesystem(response);
+ default:
+ return { error: `unknown scheme: ${scheme}` };
+ }
+ }
+
+ async function fetchToScheme(url, scheme) {
+ let response;
+ try {
+ response = await fetch(url);
+ } catch (e) {
+ return { error: `fetch error: ${e.name}` };
+ }
+
+ return responseToScheme(response, scheme);
+ }
+
+ const params = new URL(window.location).searchParams;
+ fetchToScheme(params.get("url"), params.get("scheme"))
+ .then((message) => { parent.postMessage(message, "*"); });
+</script>
diff --git a/testing/web-platform/tests/html/cross-origin-embedder-policy/resources/fetch-in-dedicated-worker.js b/testing/web-platform/tests/html/cross-origin-embedder-policy/resources/fetch-in-dedicated-worker.js
new file mode 100644
index 0000000000..bd60d07952
--- /dev/null
+++ b/testing/web-platform/tests/html/cross-origin-embedder-policy/resources/fetch-in-dedicated-worker.js
@@ -0,0 +1,6 @@
+self.addEventListener('message', async (e) => {
+ const param = e.data;
+ // Ignore network error.
+ await fetch(param.url, param.init).catch(() => {});
+ self.postMessage(param.url);
+});
diff --git a/testing/web-platform/tests/html/cross-origin-embedder-policy/resources/iframe.html b/testing/web-platform/tests/html/cross-origin-embedder-policy/resources/iframe.html
new file mode 100644
index 0000000000..a6b74ad924
--- /dev/null
+++ b/testing/web-platform/tests/html/cross-origin-embedder-policy/resources/iframe.html
@@ -0,0 +1,3 @@
+<script>
+ window.addEventListener("message", message => eval(message.data));
+</script>
diff --git a/testing/web-platform/tests/html/cross-origin-embedder-policy/resources/load-corp-images.html b/testing/web-platform/tests/html/cross-origin-embedder-policy/resources/load-corp-images.html
new file mode 100644
index 0000000000..288610046e
--- /dev/null
+++ b/testing/web-platform/tests/html/cross-origin-embedder-policy/resources/load-corp-images.html
@@ -0,0 +1,38 @@
+<!doctype html>
+<html>
+<script src="/common/get-host-info.sub.js"></script>
+<script>
+
+function remote(path) {
+ const REMOTE_ORIGIN = get_host_info().HTTPS_REMOTE_ORIGIN;
+ return new URL(path, REMOTE_ORIGIN);
+}
+
+let params = new URLSearchParams(location.search);
+let token = params.get('token');
+let revalidate = params.get('revalidate');
+
+let image_path = `/html/cross-origin-embedder-policy/resources/corp-image.py?token=${token}&revalidate=${revalidate}`;
+
+window.addEventListener("load", async () => {
+ await new Promise(resolve => {
+ let img = document.createElement("img");
+ img.src = remote(image_path);
+ img.onload = () => { window.parent.postMessage({corp: false, loaded: true}, "*"); resolve(); };
+ img.onerror = (e) => { window.parent.postMessage({corp: false, loaded: false}, "*"); resolve(); };
+ document.body.appendChild(img);
+ });
+
+ await new Promise(resolve => {
+ let img = document.createElement("img");
+ img.src = remote(image_path + "&corp-cross-origin=1");
+ img.onload = () => { window.parent.postMessage({corp: true, loaded: true}, "*"); resolve(); };
+ img.onerror = (e) => { window.parent.postMessage({corp: true, loaded: false}, "*"); resolve(); };
+ document.body.appendChild(img);
+ });
+
+ window.parent.postMessage({done: true}, "*")
+});
+
+</script>
+</html>
diff --git a/testing/web-platform/tests/html/cross-origin-embedder-policy/resources/load-corp-images.html.headers b/testing/web-platform/tests/html/cross-origin-embedder-policy/resources/load-corp-images.html.headers
new file mode 100644
index 0000000000..8df98474b5
--- /dev/null
+++ b/testing/web-platform/tests/html/cross-origin-embedder-policy/resources/load-corp-images.html.headers
@@ -0,0 +1 @@
+cross-origin-embedder-policy: require-corp
diff --git a/testing/web-platform/tests/html/cross-origin-embedder-policy/resources/navigate-none.sub.html b/testing/web-platform/tests/html/cross-origin-embedder-policy/resources/navigate-none.sub.html
new file mode 100644
index 0000000000..f1437ba90a
--- /dev/null
+++ b/testing/web-platform/tests/html/cross-origin-embedder-policy/resources/navigate-none.sub.html
@@ -0,0 +1,34 @@
+<!doctype html>
+<script>
+ let current = new URL(window.location.href);
+ let navigateTo = current.searchParams.get("to");
+ let channelName = current.searchParams.get("channelName");
+ let postMessageTo = current.searchParams.get("postMessageTo");
+ current.search = "";
+ if (navigateTo) {
+ let next = new URL(navigateTo, current);
+ window.addEventListener("load", () => {
+ window.location.href = next.href;
+ });
+ }
+
+ let target = undefined;
+ if (channelName) {
+ target = new BroadcastChannel(channelName);
+ } else if (postMessageTo) {
+ target = eval(postMessageTo);
+ }
+
+ if (target) {
+ // Broadcast only once the DOM is loaded, so that the caller can
+ // access reliably this document's body.
+ window.addEventListener("DOMContentLoaded", () =>
+ target.postMessage("loaded", "*"));
+
+ // The page can also be restored from the back-forward cache:
+ window.addEventListener('pageshow', function(event) {
+ if (event.persisted)
+ target.postMessage("loaded", "*");
+ });
+ }
+</script>
diff --git a/testing/web-platform/tests/html/cross-origin-embedder-policy/resources/navigate-require-corp-same-site.sub.html b/testing/web-platform/tests/html/cross-origin-embedder-policy/resources/navigate-require-corp-same-site.sub.html
new file mode 100644
index 0000000000..910317d29b
--- /dev/null
+++ b/testing/web-platform/tests/html/cross-origin-embedder-policy/resources/navigate-require-corp-same-site.sub.html
@@ -0,0 +1,29 @@
+<!doctype html>
+<script>
+ const current = new URL(window.location.href);
+ const token = current.searchParams.get("token");
+ const navigateTo = current.searchParams.get("to");
+ const channelName = current.searchParams.get("channelName");
+ const clearOpener = current.searchParams.get("clearOpener");
+
+ if (clearOpener) {
+ window.opener = null;
+ }
+
+ current.search = "";
+ if (navigateTo) {
+ let next = new URL(navigateTo, current);
+ window.addEventListener("load", () => {
+ window.location = next.href;
+ });
+ }
+
+ if (channelName) {
+ let bc = new BroadcastChannel(channelName);
+ bc.postMessage("loaded");
+ }
+
+ if (parent !== window && token) {
+ parent.postMessage(token, "*");
+ }
+</script>
diff --git a/testing/web-platform/tests/html/cross-origin-embedder-policy/resources/navigate-require-corp-same-site.sub.html.headers b/testing/web-platform/tests/html/cross-origin-embedder-policy/resources/navigate-require-corp-same-site.sub.html.headers
new file mode 100644
index 0000000000..56d0ac3428
--- /dev/null
+++ b/testing/web-platform/tests/html/cross-origin-embedder-policy/resources/navigate-require-corp-same-site.sub.html.headers
@@ -0,0 +1,2 @@
+Cross-Origin-Embedder-Policy: require-corp
+Cross-Origin-Resource-Policy: same-site
diff --git a/testing/web-platform/tests/html/cross-origin-embedder-policy/resources/navigate-require-corp.sub.html b/testing/web-platform/tests/html/cross-origin-embedder-policy/resources/navigate-require-corp.sub.html
new file mode 100644
index 0000000000..0a3c4dd8d7
--- /dev/null
+++ b/testing/web-platform/tests/html/cross-origin-embedder-policy/resources/navigate-require-corp.sub.html
@@ -0,0 +1,24 @@
+<!doctype html>
+<script>
+ let current = new URL(window.location.href);
+ let navigateTo = current.searchParams.get("to");
+ let channelName = current.searchParams.get("channelName");
+ let clearOpener = current.searchParams.get("clearOpener");
+
+ if (clearOpener) {
+ window.opener = null;
+ }
+
+ current.search = "";
+ if (navigateTo) {
+ let next = new URL(navigateTo, current);
+ window.addEventListener("load", () => {
+ window.location.href = next.href;
+ });
+ }
+
+ if (channelName) {
+ let bc = new BroadcastChannel(channelName);
+ bc.postMessage("loaded");
+ }
+</script>
diff --git a/testing/web-platform/tests/html/cross-origin-embedder-policy/resources/navigate-require-corp.sub.html.headers b/testing/web-platform/tests/html/cross-origin-embedder-policy/resources/navigate-require-corp.sub.html.headers
new file mode 100644
index 0000000000..6604450991
--- /dev/null
+++ b/testing/web-platform/tests/html/cross-origin-embedder-policy/resources/navigate-require-corp.sub.html.headers
@@ -0,0 +1 @@
+Cross-Origin-Embedder-Policy: require-corp
diff --git a/testing/web-platform/tests/html/cross-origin-embedder-policy/resources/nothing-cross-origin-corp.js b/testing/web-platform/tests/html/cross-origin-embedder-policy/resources/nothing-cross-origin-corp.js
new file mode 100644
index 0000000000..662e9364f9
--- /dev/null
+++ b/testing/web-platform/tests/html/cross-origin-embedder-policy/resources/nothing-cross-origin-corp.js
@@ -0,0 +1 @@
+/* Just an empty JS file */
diff --git a/testing/web-platform/tests/html/cross-origin-embedder-policy/resources/nothing-cross-origin-corp.js.headers b/testing/web-platform/tests/html/cross-origin-embedder-policy/resources/nothing-cross-origin-corp.js.headers
new file mode 100644
index 0000000000..1b88136c01
--- /dev/null
+++ b/testing/web-platform/tests/html/cross-origin-embedder-policy/resources/nothing-cross-origin-corp.js.headers
@@ -0,0 +1 @@
+Cross-Origin-Resource-Policy: cross-origin
diff --git a/testing/web-platform/tests/html/cross-origin-embedder-policy/resources/nothing-same-origin-corp.txt b/testing/web-platform/tests/html/cross-origin-embedder-policy/resources/nothing-same-origin-corp.txt
new file mode 100644
index 0000000000..b9ba801f78
--- /dev/null
+++ b/testing/web-platform/tests/html/cross-origin-embedder-policy/resources/nothing-same-origin-corp.txt
@@ -0,0 +1 @@
+nothing with same-origin CORP
diff --git a/testing/web-platform/tests/html/cross-origin-embedder-policy/resources/nothing-same-origin-corp.txt.headers b/testing/web-platform/tests/html/cross-origin-embedder-policy/resources/nothing-same-origin-corp.txt.headers
new file mode 100644
index 0000000000..30ddeac2e7
--- /dev/null
+++ b/testing/web-platform/tests/html/cross-origin-embedder-policy/resources/nothing-same-origin-corp.txt.headers
@@ -0,0 +1 @@
+Cross-Origin-Resource-Policy: same-origin
diff --git a/testing/web-platform/tests/html/cross-origin-embedder-policy/resources/postmessage-ready.html b/testing/web-platform/tests/html/cross-origin-embedder-policy/resources/postmessage-ready.html
new file mode 100644
index 0000000000..3282711dbc
--- /dev/null
+++ b/testing/web-platform/tests/html/cross-origin-embedder-policy/resources/postmessage-ready.html
@@ -0,0 +1,4 @@
+<!DOCTYPE html>
+<script>
+ opener.postMessage("ready", "*");
+</script>
diff --git a/testing/web-platform/tests/html/cross-origin-embedder-policy/resources/report.py b/testing/web-platform/tests/html/cross-origin-embedder-policy/resources/report.py
new file mode 100644
index 0000000000..100c642d6c
--- /dev/null
+++ b/testing/web-platform/tests/html/cross-origin-embedder-policy/resources/report.py
@@ -0,0 +1,41 @@
+import json
+
+def main(request, response):
+ response.headers.set(b'Access-Control-Allow-Origin', b'*')
+ response.headers.set(b'Access-Control-Allow-Methods', b'OPTIONS, GET, POST')
+ response.headers.set(b'Access-Control-Allow-Headers', b'Content-Type')
+ response.headers.set(b'Cache-Control', b'no-cache, no-store, must-revalidate')
+
+ # CORS preflight
+ if request.method == u'OPTIONS':
+ return u''
+
+ uuidMap = {
+ b'endpoint': b'01234567-0123-0123-0123-0123456789AB',
+ b'report-only-endpoint': b'01234567-0123-0123-0123-0123456789CD'
+ }
+ key = 0
+ if b'endpoint' in request.GET:
+ key = uuidMap.get(request.GET[b'endpoint'], 0)
+
+ if b'key' in request.GET:
+ key = request.GET[b'key']
+
+ if key == 0:
+ response.status = 400
+ return u'invalid endpoint'
+
+ path = u'/'.join(request.url_parts.path.split(u'/')[:-1]) + u'/'
+ if request.method == u'POST':
+ reports = request.server.stash.take(key, path) or []
+ for report in json.loads(request.body):
+ reports.append(report)
+ request.server.stash.put(key, reports, path)
+ return u'done'
+
+ if request.method == u'GET':
+ response.headers.set(b'Content-Type', b'application/json')
+ return json.dumps(request.server.stash.take(key, path) or [])
+
+ response.status = 400
+ return u'invalid method'
diff --git a/testing/web-platform/tests/html/cross-origin-embedder-policy/resources/reporting-empty-frame-multiple-headers.html.asis b/testing/web-platform/tests/html/cross-origin-embedder-policy/resources/reporting-empty-frame-multiple-headers.html.asis
new file mode 100644
index 0000000000..c0b352f1c7
--- /dev/null
+++ b/testing/web-platform/tests/html/cross-origin-embedder-policy/resources/reporting-empty-frame-multiple-headers.html.asis
@@ -0,0 +1,9 @@
+HTTP/1.1 200 OK
+Content-Type: text/html
+cross-origin-embedder-policy: require-corp; foo="
+cross-origin-embedder-policy: "; report-to="endpoint"
+cross-origin-embedder-policy-report-only: require-corp; foo="
+cross-origin-embedder-policy-report-only: "; report-to="report-only-endpoint"
+
+<!doctype html>
+<meta charset="utf-8">
diff --git a/testing/web-platform/tests/html/cross-origin-embedder-policy/resources/reporting-empty-frame.html b/testing/web-platform/tests/html/cross-origin-embedder-policy/resources/reporting-empty-frame.html
new file mode 100644
index 0000000000..b1579add2e
--- /dev/null
+++ b/testing/web-platform/tests/html/cross-origin-embedder-policy/resources/reporting-empty-frame.html
@@ -0,0 +1,5 @@
+<!doctype html>
+<html>
+<body>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/cross-origin-embedder-policy/resources/reporting-worker.js b/testing/web-platform/tests/html/cross-origin-embedder-policy/resources/reporting-worker.js
new file mode 100644
index 0000000000..0f8a2ce4c8
--- /dev/null
+++ b/testing/web-platform/tests/html/cross-origin-embedder-policy/resources/reporting-worker.js
@@ -0,0 +1,25 @@
+function run({script, port}) {
+ const reports = [];
+ const observer = new ReportingObserver((rs) => {
+ for (const r of rs) {
+ reports.push(r.toJSON());
+ }
+ });
+ // Wait 200ms for reports to settle.
+ setTimeout(() => {
+ observer.disconnect();
+ port.postMessage(reports);
+ }, 200);
+ observer.observe();
+
+ // This eval call may generate some reports.
+ eval(script);
+}
+
+// For DedicatedWorker and ServiceWorker
+self.addEventListener('message', (e) => run(e.data));
+
+// For SharedWorker
+self.addEventListener('connect', (e) => {
+ e.ports[0].onmessage = (ev) => run(ev.data);
+});
diff --git a/testing/web-platform/tests/html/cross-origin-embedder-policy/resources/require-corp-sw-import-scripts.js b/testing/web-platform/tests/html/cross-origin-embedder-policy/resources/require-corp-sw-import-scripts.js
new file mode 100644
index 0000000000..e652c5bf30
--- /dev/null
+++ b/testing/web-platform/tests/html/cross-origin-embedder-policy/resources/require-corp-sw-import-scripts.js
@@ -0,0 +1,23 @@
+// Service worker with 'COEP: require-corp' response header.
+// This service worker issues a network request to import scripts with or
+// without CORP response header.
+
+importScripts("/common/get-host-info.sub.js");
+
+function url_for_empty_js(corp) {
+ const url = new URL(get_host_info().HTTPS_REMOTE_ORIGIN);
+ url.pathname = '/service-workers/service-worker/resources/empty.js';
+ if (corp) {
+ url.searchParams.set(
+ 'pipe', `header(Cross-Origin-Resource-Policy, ${corp})`);
+ }
+ return url.href;
+}
+
+const params = new URL(location.href).searchParams;
+
+if (params.get('corp') === 'cross-origin') {
+ importScripts(url_for_empty_js('cross-origin'));
+} else {
+ importScripts(url_for_empty_js());
+}
diff --git a/testing/web-platform/tests/html/cross-origin-embedder-policy/resources/require-corp-sw-import-scripts.js.headers b/testing/web-platform/tests/html/cross-origin-embedder-policy/resources/require-corp-sw-import-scripts.js.headers
new file mode 100644
index 0000000000..6604450991
--- /dev/null
+++ b/testing/web-platform/tests/html/cross-origin-embedder-policy/resources/require-corp-sw-import-scripts.js.headers
@@ -0,0 +1 @@
+Cross-Origin-Embedder-Policy: require-corp
diff --git a/testing/web-platform/tests/html/cross-origin-embedder-policy/resources/require-corp-sw.js b/testing/web-platform/tests/html/cross-origin-embedder-policy/resources/require-corp-sw.js
new file mode 100644
index 0000000000..fedecee9ea
--- /dev/null
+++ b/testing/web-platform/tests/html/cross-origin-embedder-policy/resources/require-corp-sw.js
@@ -0,0 +1,27 @@
+// Service worker with 'COEP: require-corp' response header.
+// This service worker issues a network request for a resource with or without
+// CORP response header.
+
+importScripts("/common/get-host-info.sub.js");
+
+self.addEventListener('message', e => {
+ e.waitUntil((async () => {
+ let result;
+ try {
+ let url;
+ if (e.data === 'WithCorp') {
+ url = get_host_info().HTTPS_REMOTE_ORIGIN +
+ '/html/cross-origin-embedder-policy/resources/' +
+ 'nothing-cross-origin-corp.js';
+ } else if (e.data === 'WithoutCorp') {
+ url = get_host_info().HTTPS_REMOTE_ORIGIN + '/common/blank.html';
+ }
+ const response = await fetch(url, { mode: 'no-cors' });
+ result = response.type;
+ } catch (error) {
+ result = `Exception: ${error.name}`;
+ } finally {
+ e.source.postMessage(result);
+ }
+ })());
+});
diff --git a/testing/web-platform/tests/html/cross-origin-embedder-policy/resources/require-corp-sw.js.headers b/testing/web-platform/tests/html/cross-origin-embedder-policy/resources/require-corp-sw.js.headers
new file mode 100644
index 0000000000..6604450991
--- /dev/null
+++ b/testing/web-platform/tests/html/cross-origin-embedder-policy/resources/require-corp-sw.js.headers
@@ -0,0 +1 @@
+Cross-Origin-Embedder-Policy: require-corp
diff --git a/testing/web-platform/tests/html/cross-origin-embedder-policy/resources/script-factory.js b/testing/web-platform/tests/html/cross-origin-embedder-policy/resources/script-factory.js
new file mode 100644
index 0000000000..ac7a1fda06
--- /dev/null
+++ b/testing/web-platform/tests/html/cross-origin-embedder-policy/resources/script-factory.js
@@ -0,0 +1,30 @@
+// This creates a serialized <script> element that is useful for blob/data/srcdoc-style tests.
+
+function createScript(sameOrigin, crossOrigin, type="parent", id="") {
+ return `const data = { id: "${id}",
+ opener: !!window.opener,
+ origin: window.origin,
+ sameOriginNoCORPSuccess: false,
+ crossOriginNoCORPFailure: false };
+function record(promise, token, expectation) {
+ return promise.then(() => data[token] = expectation, () => data[token] = !expectation);
+}
+
+const records = [
+ record(fetch("${crossOrigin}/common/blank.html", { mode: "no-cors" }), "crossOriginNoCORPFailure", false)
+];
+
+if ("${sameOrigin}" !== "null") {
+ records.push(record(fetch("${sameOrigin}/common/blank.html", { mode: "no-cors" }), "sameOriginNoCORPSuccess", true));
+}
+
+Promise.all(records).then(() => {
+ // Using BroadcastChannel is useful for blob: URLs, which are always same-origin
+ if ("${type}" === "channel") {
+ const bc = new BroadcastChannel("${id}");
+ bc.postMessage(data);
+ } else {
+ window.${type}.postMessage(data, "*");
+ }
+});`;
+}
diff --git a/testing/web-platform/tests/html/cross-origin-embedder-policy/resources/shared-worker-fetch.js.py b/testing/web-platform/tests/html/cross-origin-embedder-policy/resources/shared-worker-fetch.js.py
new file mode 100644
index 0000000000..112d7ecbeb
--- /dev/null
+++ b/testing/web-platform/tests/html/cross-origin-embedder-policy/resources/shared-worker-fetch.js.py
@@ -0,0 +1,24 @@
+body = b'''
+'use strict';
+
+onconnect = (event) => {
+ const port = event.ports[0];
+
+ port.onmessage = (event) => {
+ fetch(event.data, { mode: 'no-cors' })
+ .then(
+ () => port.postMessage('success'),
+ () => port.postMessage('failure')
+ );
+ };
+
+ port.postMessage('ready');
+};'''
+
+def main(request, response):
+ headers = [(b'Content-Type', b'text/javascript')]
+
+ for value in request.GET.get_list(b'value'):
+ headers.append((b'Cross-Origin-Embedder-Policy', value))
+
+ return (200, headers, body)
diff --git a/testing/web-platform/tests/html/cross-origin-embedder-policy/resources/shared-worker.js b/testing/web-platform/tests/html/cross-origin-embedder-policy/resources/shared-worker.js
new file mode 100644
index 0000000000..c5f2c3cc2c
--- /dev/null
+++ b/testing/web-platform/tests/html/cross-origin-embedder-policy/resources/shared-worker.js
@@ -0,0 +1,7 @@
+onconnect = (event) => {
+ const port = event.ports[0];
+ port.onmessage = (event) => {
+ eval(event.data);
+ };
+ port.postMessage('ready');
+};
diff --git a/testing/web-platform/tests/html/cross-origin-embedder-policy/resources/sw-store-to-cache-storage.js b/testing/web-platform/tests/html/cross-origin-embedder-policy/resources/sw-store-to-cache-storage.js
new file mode 100644
index 0000000000..00b9e9395a
--- /dev/null
+++ b/testing/web-platform/tests/html/cross-origin-embedder-policy/resources/sw-store-to-cache-storage.js
@@ -0,0 +1,31 @@
+self.addEventListener('activate', (e) => {
+ e.waitUntil(clients.claim());
+});
+
+self.addEventListener('message', (e) => {
+ e.waitUntil((async () => {
+
+ const url = new URL(e.data.url);
+ const request = new Request(url, {mode: e.data.mode});
+ const cache = await caches.open('v1');
+
+ let response;
+ switch(e.data.source) {
+ case "service-worker":
+ response = new Response('foo');
+ break;
+
+ case "network":
+ try {
+ response = await fetch(request);
+ } catch(error) {
+ e.source.postMessage('not-stored');
+ return;
+ }
+ break;
+ }
+
+ await cache.put(request, response);
+ e.source.postMessage('stored');
+ })());
+})
diff --git a/testing/web-platform/tests/html/cross-origin-embedder-policy/resources/sw.js b/testing/web-platform/tests/html/cross-origin-embedder-policy/resources/sw.js
new file mode 100644
index 0000000000..57f0b41ba5
--- /dev/null
+++ b/testing/web-platform/tests/html/cross-origin-embedder-policy/resources/sw.js
@@ -0,0 +1,12 @@
+self.addEventListener('activate', (e) => {
+ e.waitUntil(clients.claim());
+});
+
+self.addEventListener('fetch', (e) => {
+ const url = new URL(e.request.url);
+ if (url.searchParams.has('passthrough')) {
+ return;
+ }
+
+ e.respondWith(fetch(e.request));
+});
diff --git a/testing/web-platform/tests/html/cross-origin-embedder-policy/resources/universal-worker.js b/testing/web-platform/tests/html/cross-origin-embedder-policy/resources/universal-worker.js
new file mode 100644
index 0000000000..5d46edcde2
--- /dev/null
+++ b/testing/web-platform/tests/html/cross-origin-embedder-policy/resources/universal-worker.js
@@ -0,0 +1 @@
+onmessage = message => eval(message.data);
diff --git a/testing/web-platform/tests/html/cross-origin-embedder-policy/resources/worker-owner-frame.html b/testing/web-platform/tests/html/cross-origin-embedder-policy/resources/worker-owner-frame.html
new file mode 100644
index 0000000000..509c904d2b
--- /dev/null
+++ b/testing/web-platform/tests/html/cross-origin-embedder-policy/resources/worker-owner-frame.html
@@ -0,0 +1,2 @@
+<!doctype html>
+<script src="worker-owner.js"></script>
diff --git a/testing/web-platform/tests/html/cross-origin-embedder-policy/resources/worker-owner.js b/testing/web-platform/tests/html/cross-origin-embedder-policy/resources/worker-owner.js
new file mode 100644
index 0000000000..d1f172a0b8
--- /dev/null
+++ b/testing/web-platform/tests/html/cross-origin-embedder-policy/resources/worker-owner.js
@@ -0,0 +1,36 @@
+const is_worker = !('window' in self);
+const parent_or_self = is_worker ? self : self.parent;
+
+function startWorkerAndObserveReports(worker_url, wait_for_report) {
+ const worker = new Worker(worker_url);
+ const result_promise = new Promise(resolve => {
+ worker.onmessage = _ => resolve('success');
+ worker.onerror = _ => resolve('error');
+ });
+ worker.postMessage("postMessage('reply to owner from worker');");
+
+ const report_promise = new Promise(resolve => {
+ const observer = new ReportingObserver(reports => {
+ observer.disconnect();
+ resolve(reports.map(r => r.toJSON()));
+ });
+ observer.observe();
+ });
+
+ if (wait_for_report) {
+ Promise.all([result_promise, report_promise]).then(results => {
+ parent_or_self.postMessage(results[1]);
+ });
+ } else {
+ result_promise.then(result => {
+ parent_or_self.postMessage([]);
+ });
+ }
+}
+
+if (is_worker) {
+ onmessage = e => {
+ startWorkerAndObserveReports(e.data.worker_url, e.data.wait_for_report);
+ };
+}
+
diff --git a/testing/web-platform/tests/html/cross-origin-embedder-policy/resources/worker-support.js b/testing/web-platform/tests/html/cross-origin-embedder-policy/resources/worker-support.js
new file mode 100644
index 0000000000..860ee6826c
--- /dev/null
+++ b/testing/web-platform/tests/html/cross-origin-embedder-policy/resources/worker-support.js
@@ -0,0 +1,81 @@
+// Configures `url` such that the response carries a `COEP: ${value}` header.
+//
+// `url` must be a `URL` instance.
+function setCoep(url, value) {
+ url.searchParams
+ .set("pipe", `header(cross-origin-embedder-policy,${value})`);
+}
+
+// Resolves the given `relativeUrl` relative to the current window's location.
+//
+// `options` can contain the following keys:
+//
+// - `coep`: value passed to `setCoep()`, if present.
+// - `host`: overrides the host of the returned URL.
+//
+// Returns a `URL` instance.
+function resolveUrl(relativeUrl, options) {
+ const url = new URL(relativeUrl, window.location);
+
+ if (options !== undefined) {
+ const { coep, host } = options;
+ if (coep !== undefined) {
+ setCoep(url, coep);
+ }
+ if (host !== undefined) {
+ url.host = host;
+ }
+ }
+
+ return url;
+}
+
+// Adds an iframe loaded from `url` to the current document, waiting for it to
+// load before returning.
+//
+// The returned iframe is removed from the document at the end of the test `t`.
+async function withIframe(t, url) {
+ const frame = document.createElement("iframe");
+ frame.src = url;
+
+ t.add_cleanup(() => frame.remove());
+
+ const loadedPromise = new Promise(resolve => {
+ frame.addEventListener('load', resolve, {once: true});
+ });
+ document.body.append(frame);
+ await loadedPromise;
+
+ return frame;
+}
+
+// Asynchronously waits for a single "message" event on the given `target`.
+function waitForMessage(target) {
+ return new Promise(resolve => {
+ target.addEventListener('message', resolve, {once: true});
+ });
+}
+
+// Fetches `url` from a document with COEP `creatorCoep`, then serializes it
+// and returns a URL pointing to the fetched body with the given `scheme`.
+//
+// - `creatorCoep` is passed to `setCoep()`.
+// - `scheme` may be one of: "blob", "data" or "filesystem".
+//
+// The returned URL is valid until the end of the test `t`.
+async function createLocalUrl(t, { url, creatorCoep, scheme }) {
+ const frameUrl = resolveUrl("resources/fetch-and-create-url.html", {
+ coep: creatorCoep,
+ });
+ frameUrl.searchParams.set("url", url);
+ frameUrl.searchParams.set("scheme", scheme);
+
+ const messagePromise = waitForMessage(window);
+ const frame = await withIframe(t, frameUrl);
+
+ const evt = await messagePromise;
+ const message = evt.data;
+ assert_equals(message.error, undefined, "url creation error");
+
+ return message.url;
+}
diff --git a/testing/web-platform/tests/html/cross-origin-embedder-policy/sandbox.https.html b/testing/web-platform/tests/html/cross-origin-embedder-policy/sandbox.https.html
new file mode 100644
index 0000000000..7a29ac1944
--- /dev/null
+++ b/testing/web-platform/tests/html/cross-origin-embedder-policy/sandbox.https.html
@@ -0,0 +1,40 @@
+<!doctype html>
+<meta charset=utf-8>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+<div id=log></div>
+<script>
+async_test(t => {
+ window.addEventListener("message", t.step_func_done(({ data }) => {
+ assert_equals(data.origin, "null");
+ assert_true(data.sameOriginWithoutCORP, "Request to same-origin resource without CORP did not fail");
+ assert_true(data.sameOriginWithSameOriginCORP, "Request to same-origin resource with same-origin CORP did not fail");
+ assert_true(data.sameOriginWithCrossOriginCORP, "Request to same-origin resource with cross-origin CORP did not succeed");
+ assert_true(data.crossOriginWithCrossOriginCORP, "Request to cross-origin resource with cross-origin CORP did not succeed");
+ }));
+
+ const origins = get_host_info();
+ const frame = document.createElement("iframe");
+ const nothingCrossOriginCORP = new URL("resources/nothing-cross-origin-corp.js", window.location).pathname;
+ const nothingSameOriginCORP = new URL("resources/nothing-same-origin-corp.txt", window.location).pathname;
+ frame.sandbox = "allow-scripts";
+ frame.srcdoc = `<script>
+const data = { sameOriginWithoutCORP: false,
+ sameOriginWithSameOriginCORP: false,
+ sameOriginWithCrossOriginCORP: false,
+ crossOriginWithCrossOriginCORP: false,
+ origin: self.origin };
+function record(promise, token, expectation) {
+ return promise.then(() => data[token] = expectation, () => data[token] = !expectation);
+}
+Promise.all([
+ record(fetch("/common/blank.html", { mode: "no-cors" }), "sameOriginWithoutCORP", false),
+ record(fetch("${nothingSameOriginCORP}", { mode: "no-cors" }), "sameOriginWithSameOriginCORP", false),
+ record(fetch("${nothingCrossOriginCORP}", { mode: "no-cors" }), "sameOriginWithCrossOriginCORP", true),
+ record(fetch("${origins.HTTPS_NOTSAMESITE_ORIGIN}${nothingCrossOriginCORP}", { mode: "no-cors" }), "crossOriginWithCrossOriginCORP", true)
+]).then(() => parent.postMessage(data, "*"));
+<\/script>`;
+ document.body.append(frame);
+}, "Cross-Origin-Embedder-Policy and sandbox");
+</script>
diff --git a/testing/web-platform/tests/html/cross-origin-embedder-policy/sandbox.https.html.headers b/testing/web-platform/tests/html/cross-origin-embedder-policy/sandbox.https.html.headers
new file mode 100644
index 0000000000..6604450991
--- /dev/null
+++ b/testing/web-platform/tests/html/cross-origin-embedder-policy/sandbox.https.html.headers
@@ -0,0 +1 @@
+Cross-Origin-Embedder-Policy: require-corp
diff --git a/testing/web-platform/tests/html/cross-origin-embedder-policy/service-worker-cache-storage.https.html b/testing/web-platform/tests/html/cross-origin-embedder-policy/service-worker-cache-storage.https.html
new file mode 100644
index 0000000000..873f06ce4f
--- /dev/null
+++ b/testing/web-platform/tests/html/cross-origin-embedder-policy/service-worker-cache-storage.https.html
@@ -0,0 +1,117 @@
+<!doctype html>
+<html>
+<title> Check enforcement of COEP in a ServiceWorker using CacheStorage. </title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+<script src="/service-workers/service-worker/resources/test-helpers.sub.js"></script>
+<script>
+// See also: ./dedicated-worker-cache-storage.https.html
+
+function remote(path) {
+ const REMOTE_ORIGIN = get_host_info().HTTPS_REMOTE_ORIGIN;
+ return new URL(path, REMOTE_ORIGIN);
+}
+
+const iframe_path = "./resources/iframe.html?pipe=";
+const service_worker_path = "./resources/universal-worker.js?pipe=";
+const ressource_path = "/images/blue.png?pipe=";
+
+const coep_header= {
+ "coep-none" : "",
+ "coep-require-corp" : "|header(Cross-Origin-Embedder-Policy,require-corp)",
+}
+
+const corp_header = {
+ "corp-undefined" : "",
+ "corp-cross-origin" : "|header(Cross-Origin-Resource-Policy,cross-origin)",
+}
+
+// Send a message to the |worker| and wait for its response.
+function executeCommandInServiceWorker(worker, command) {
+ const channel = new MessageChannel();
+ const response = new Promise(resolve => channel.port1.onmessage = resolve);
+ worker.postMessage(command, [ channel.port2 ]);
+ return response;
+}
+
+// Check enforcement of COEP in a ServiceWorker using CacheStorage.
+//
+// 1) Fetch a response from a document with COEP:none. Store it in the
+// CacheStorage. The response is cross-origin without any CORS header.
+// 2) From a ServiceWorker, retrieve the response from the CacheStorage.
+//
+// Test parameters:
+// - |worker_coep| the COEP header of the ServiceWorker's script response.
+// - |response_corp| the CORP header of the response.
+//
+// Test expectations:
+// |loaded| is true whenever the worker is able to fetch the response from
+// the CacheStorage. According to the specification:
+// https://mikewest.github.io/corpp/#initialize-embedder-policy-for-global
+// it must be false when:
+// - |worker_coep| is 'coep-require-corp' and
+// - |response-corp| is 'corp-undefined'.
+function check(
+ // Test parameters:
+ worker_coep,
+ response_corp,
+
+ // Test expectations:
+ loaded) {
+
+ promise_test(async (t) => {
+ // 1) Fetch a response from a document with COEP:none. Store it in the
+ // CacheStorage. The response is cross-origin without any CORS header.
+ const resource_path = ressource_path + corp_header[response_corp];
+ const resource_url = remote(resource_path);
+ const fetch_request = new Request(resource_url, {mode: 'no-cors'});
+ const cache = await caches.open('v1');
+ const fetch_response = await fetch(fetch_request);
+ await cache.put(fetch_request, fetch_response);
+
+ // 2) Start a ServiceWorker.
+ const SCOPE= new URL(location.href).pathname;
+ const service_worker_allowed = `|header(service-worker-allowed,${SCOPE})`;
+ const SCRIPT =
+ service_worker_path +
+ coep_header[worker_coep] +
+ service_worker_allowed;
+
+ const reg = await service_worker_unregister_and_register(t, SCRIPT, SCOPE);
+ add_completion_callback(() => reg.unregister());
+
+ // Start talking to the ServiceWorker, no matter its state.
+ const worker = reg.installing || reg.waiting || reg.active;
+
+ // 3) From the service worker, try to retrieve the response from the
+ // CacheStorage.
+ const response = executeCommandInServiceWorker(worker, `
+ (async function() {
+ const cache = await caches.open('v1');
+ const request = new Request('${resource_url}', {
+ mode: 'no-cors'
+ });
+ try {
+ const response = await cache.match(request);
+ message.ports[0].postMessage('success');
+ } catch(error) {
+ message.ports[0].postMessage('error');
+ }
+ })()
+ `);
+ const {data} = await response;
+ assert_equals(data === "success", loaded);
+ }, `A ServiceWorker with ${worker_coep} use CacheStorage to get a ${response_corp} response.`)
+}
+
+// ------------------------------------------------------
+// worker_coep , response_corp , loaded
+// ------------------------------------------------------
+check("coep-none" , "corp-undefined" , true);
+check("coep-none" , "corp-cross-origin" , true);
+check("coep-require-corp" , "corp-undefined" , false);
+check("coep-require-corp" , "corp-cross-origin" , true);
+
+</script>
+</html>
diff --git a/testing/web-platform/tests/html/cross-origin-embedder-policy/shared-workers.https.html b/testing/web-platform/tests/html/cross-origin-embedder-policy/shared-workers.https.html
new file mode 100644
index 0000000000..2558f2dd0b
--- /dev/null
+++ b/testing/web-platform/tests/html/cross-origin-embedder-policy/shared-workers.https.html
@@ -0,0 +1,228 @@
+<!doctype html>
+<html>
+<meta charset="utf-8">
+<title>COEP - policy derivation for Shared Workers</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+<script src="resources/worker-support.js"></script>
+<body>
+<p>Verify the Cross-Origin Embedder Policy for Shared Workers by performing a
+cross-domain "fetch" request for a resource that does not specify a COEP. Only
+Shared Workers with the default COEP should be able to successfully perform
+this operation.</p>
+<script>
+'use strict';
+
+const testUrl = resolveUrl("resources/empty-coep.py", {
+ host: get_host_info().REMOTE_HOST,
+}).href;
+
+function makeWorkerUrl(options) {
+ return resolveUrl("resources/shared-worker-fetch.js.py", options);
+}
+
+/**
+ * Create a Shared Worker within an iframe
+ *
+ * @param {object} t - a testharness.js subtest instance (used to reset global
+ * state)
+ * @param {string} url - the URL from which the Shared Worker should be
+ * created
+ * @param {string} options.ownerCoep - the Cross-Origin Embedder Policy of the
+ iframe
+ */
+async function createWorker(t, url, options) {
+ const { ownerCoep } = options || {};
+ const frameUrl = resolveUrl("/common/blank.html", { coep: ownerCoep });
+
+ const iframe = await withIframe(t, frameUrl);
+
+ const sw = new iframe.contentWindow.SharedWorker(url);
+ sw.onerror = t.unreached_func('SharedWorker.onerror should not be called');
+
+ await new Promise((resolve) => {
+ sw.port.addEventListener('message', resolve, { once: true });
+ sw.port.start();
+ });
+
+ return sw;
+}
+
+/**
+ * Instruct a Shared Worker to fetch from a specified URL and report on the
+ * success of the operation.
+ *
+ * @param {SharedWorker} worker
+ * @param {string} url - the URL that the worker should fetch
+ */
+function fetchFromWorker(worker, url) {
+ return new Promise((resolve) => {
+ worker.port.postMessage(url);
+ worker.port.addEventListener(
+ 'message', (event) => resolve(event.data), { once: true }
+ );
+ });
+};
+
+promise_test(async (t) => {
+ const worker = await createWorker(t, makeWorkerUrl());
+ const result = await fetchFromWorker(worker, testUrl);
+ assert_equals(result, 'success');
+}, 'default policy (derived from response)');
+
+promise_test(async (t) => {
+ const worker = await createWorker(t, makeWorkerUrl({ coep: 'require-corp' }));
+ const result = await fetchFromWorker(worker, testUrl);
+ assert_equals(result, 'failure');
+}, '"require-corp" (derived from response)');
+
+promise_test(async (t) => {
+ const blobUrl = await createLocalUrl(t, {
+ url: makeWorkerUrl(),
+ scheme: "blob",
+ });
+
+ const workers = await Promise.all([
+ createWorker(t, blobUrl),
+ createWorker(t, blobUrl),
+ createWorker(t, blobUrl),
+ ]);
+
+ const result = await fetchFromWorker(workers[0], testUrl);
+ assert_equals(result, 'success');
+}, 'default policy (derived from owner set due to use of local scheme - blob URL)');
+
+promise_test(async (t) => {
+ const blobUrl = await createLocalUrl(t, {
+ url: makeWorkerUrl(),
+ creatorCoep: "require-corp",
+ scheme: "blob",
+ });
+
+ const workers = await Promise.all([
+ createWorker(t, blobUrl),
+ createWorker(t, blobUrl),
+ createWorker(t, blobUrl),
+ ]);
+
+ const result = await fetchFromWorker(workers[0], testUrl);
+ assert_equals(result, 'failure');
+}, 'require-corp (derived from blob URL creator)');
+
+promise_test(async (t) => {
+ const blobUrl = await createLocalUrl(t, {
+ url: makeWorkerUrl(),
+ scheme: "blob",
+ });
+
+ const workers = await Promise.all([
+ createWorker(t, blobUrl),
+ createWorker(t, blobUrl, { ownerCoep: 'require-corp' }),
+ createWorker(t, blobUrl),
+ ]);
+
+ const result = await fetchFromWorker(workers[0], testUrl);
+ assert_equals(result, 'failure');
+}, '"require-corp" (derived from owner set due to use of local scheme - blob URL)');
+
+promise_test(async (t) => {
+ const dataUrl = await createLocalUrl(t, {
+ url: makeWorkerUrl(),
+ scheme: "data",
+ });
+
+ const workers = await Promise.all([
+ createWorker(t, dataUrl),
+ createWorker(t, dataUrl),
+ createWorker(t, dataUrl),
+ ]);
+
+ const result = await fetchFromWorker(workers[0], testUrl);
+ assert_equals(result, 'success');
+}, 'default policy (derived from owner set due to use of local scheme - data URL)');
+
+promise_test(async (t) => {
+ const dataUrl = await createLocalUrl(t, {
+ url: makeWorkerUrl(),
+ creatorCoep: "require-corp",
+ scheme: "data",
+ });
+
+ const workers = await Promise.all([
+ createWorker(t, dataUrl),
+ createWorker(t, dataUrl),
+ createWorker(t, dataUrl),
+ ]);
+
+ const result = await fetchFromWorker(workers[0], testUrl);
+ assert_equals(result, 'success');
+}, 'default policy (not derived from data URL creator)');
+
+promise_test(async (t) => {
+ const dataUrl = await createLocalUrl(t, {
+ url: makeWorkerUrl(),
+ scheme: "data",
+ });
+
+ const workers = await Promise.all([
+ createWorker(t, dataUrl),
+ createWorker(t, dataUrl, { ownercoep: 'require-corp' }),
+ createWorker(t, dataUrl),
+ ]);
+
+ const result = await fetchFromWorker(workers[0], testUrl);
+ assert_equals(result, 'failure');
+}, '"require-corp" (derived from owner set due to use of local scheme - data URL)');
+
+promise_test(async (t) => {
+ const filesystemUrl = await createLocalUrl(t, {
+ url: makeWorkerUrl(),
+ scheme: "filesystem",
+ });
+
+ const workers = await Promise.all([
+ createWorker(t, filesystemUrl),
+ createWorker(t, filesystemUrl),
+ createWorker(t, filesystemUrl),
+ ]);
+
+ const result = await fetchFromWorker(workers[0], testUrl);
+ assert_equals(result, 'success');
+}, 'default policy (derived from owner set due to use of local scheme - filesystem URL)');
+
+promise_test(async (t) => {
+ const filesystemUrl = await createLocalUrl(t, {
+ url: makeWorkerUrl(),
+ creatorCoep: "require-corp",
+ scheme: "filesystem",
+ });
+
+ const workers = await Promise.all([
+ createWorker(t, filesystemUrl),
+ createWorker(t, filesystemUrl),
+ createWorker(t, filesystemUrl),
+ ]);
+
+ const result = await fetchFromWorker(workers[0], testUrl);
+ assert_equals(result, 'failure');
+}, 'require-corp (derived from filesystem URL creator)');
+
+promise_test(async (t) => {
+ const filesystemUrl = await createLocalUrl(t, {
+ url: makeWorkerUrl(),
+ scheme: "filesystem",
+ });
+
+ const workers = await Promise.all([
+ createWorker(t, filesystemUrl),
+ createWorker(t, filesystemUrl, { ownerCoep: 'require-corp' }),
+ createWorker(t, filesystemUrl),
+ ]);
+
+ const result = await fetchFromWorker(workers[0], testUrl);
+ assert_equals(result, 'failure');
+}, '"require-corp" (derived from owner set due to use of local scheme - filesystem URL)');
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/html/cross-origin-embedder-policy/srcdoc.https.html b/testing/web-platform/tests/html/cross-origin-embedder-policy/srcdoc.https.html
new file mode 100644
index 0000000000..2937c13381
--- /dev/null
+++ b/testing/web-platform/tests/html/cross-origin-embedder-policy/srcdoc.https.html
@@ -0,0 +1,21 @@
+<!doctype html>
+<meta charset=utf-8>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/script-factory.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+<div id=log></div>
+<script>
+async_test(t => {
+ window.addEventListener("message", t.step_func_done(({ data }) => {
+ assert_equals(data.id, "");
+ assert_equals(data.origin, window.origin);
+ assert_true(data.sameOriginNoCORPSuccess, "Same-origin without CORP did not succeed");
+ assert_true(data.crossOriginNoCORPFailure, "Cross-origin without CORP did not fail");
+ }));
+ const frame = document.createElement("iframe");
+ t.add_cleanup(() => frame.remove());
+ frame.srcdoc = `<script>${createScript(window.origin, get_host_info().HTTPS_NOTSAMESITE_ORIGIN)}<\/script>`;
+ document.body.append(frame);
+}, "Cross-Origin-Embedder-Policy and srcdoc");
+</script>
diff --git a/testing/web-platform/tests/html/cross-origin-embedder-policy/srcdoc.https.html.headers b/testing/web-platform/tests/html/cross-origin-embedder-policy/srcdoc.https.html.headers
new file mode 100644
index 0000000000..6604450991
--- /dev/null
+++ b/testing/web-platform/tests/html/cross-origin-embedder-policy/srcdoc.https.html.headers
@@ -0,0 +1 @@
+Cross-Origin-Embedder-Policy: require-corp
diff --git a/testing/web-platform/tests/html/cross-origin-embedder-policy/worker-inheritance.sub.https.html b/testing/web-platform/tests/html/cross-origin-embedder-policy/worker-inheritance.sub.https.html
new file mode 100644
index 0000000000..e96c7f7e5d
--- /dev/null
+++ b/testing/web-platform/tests/html/cross-origin-embedder-policy/worker-inheritance.sub.https.html
@@ -0,0 +1,61 @@
+<!DOCTYPE html>
+<title>Test that local scheme workers inherit COEP: require-corp from the creating document</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+ promise_test(async t => {
+ let sameOrigin = "{{location[server]}}";
+ let crossOrigin = "https://{{hosts[][www]}}:{{ports[https][1]}}";
+
+ let testHarness = await fetch(`${sameOrigin}/resources/testharness.js`)
+ .then(r => r.text());
+
+ // Test that fetching same-origin is allowed by COEP.
+ let same_origin_allowed_test = testName => `
+ promise_test(async t => {
+ return fetch("${sameOrigin}/common/blank.html", { mode: "no-cors" });
+ }, "${testName}: Same origin should be allowed.");
+ `;
+
+ // For data URLs, since everything is cross-origin in that case.
+ let same_origin_blocked_test = testName => `
+ promise_test(t => {
+ return promise_rejects_js(
+ t, TypeError,
+ fetch("${sameOrigin}/common/blank.html", { mode: "no-cors" }));
+ }, "${testName}: Same origin should be blocked.");
+ `;
+
+ // Test that fetching cross-origin is blocked by COEP.
+ let cross_origin_blocked_test = testName => `
+ promise_test(t => {
+ return promise_rejects_js(
+ t, TypeError,
+ fetch("${crossOrigin}/common/blank.html", { mode: "no-cors" }));
+ }, "${testName}: Cross origin should be blocked.");
+ `;
+
+ let blob_string = testName => testHarness +
+ same_origin_allowed_test(testName) +
+ cross_origin_blocked_test(testName) + "done();";
+
+ let data_string = testName => testHarness +
+ same_origin_blocked_test(testName) +
+ cross_origin_blocked_test(testName) + "done();";
+
+ let blob_url = context => {
+ let blob = new Blob([blob_string(`blob URL ${context}`)],
+ { type: 'application/javascript' });
+ return URL.createObjectURL(blob);
+ };
+
+ await fetch_tests_from_worker(new Worker(blob_url("dedicated worker")));
+ await fetch_tests_from_worker(new SharedWorker(blob_url("shared worker")));
+
+ let data_url = context => `data:application/javascript,` +
+ `${encodeURIComponent(data_string("data URL " + context))}`;
+
+ await fetch_tests_from_worker(new Worker(data_url("dedicated worker")));
+ await fetch_tests_from_worker(new Worker(data_url("shared worker")));
+ });
+</script>
diff --git a/testing/web-platform/tests/html/cross-origin-embedder-policy/worker-inheritance.sub.https.html.headers b/testing/web-platform/tests/html/cross-origin-embedder-policy/worker-inheritance.sub.https.html.headers
new file mode 100644
index 0000000000..6604450991
--- /dev/null
+++ b/testing/web-platform/tests/html/cross-origin-embedder-policy/worker-inheritance.sub.https.html.headers
@@ -0,0 +1 @@
+Cross-Origin-Embedder-Policy: require-corp