summaryrefslogtreecommitdiffstats
path: root/testing/web-platform/tests/html/cross-origin-opener-policy/resources
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 09:22:09 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 09:22:09 +0000
commit43a97878ce14b72f0981164f87f2e35e14151312 (patch)
tree620249daf56c0258faa40cbdcf9cfba06de2a846 /testing/web-platform/tests/html/cross-origin-opener-policy/resources
parentInitial commit. (diff)
downloadfirefox-43a97878ce14b72f0981164f87f2e35e14151312.tar.xz
firefox-43a97878ce14b72f0981164f87f2e35e14151312.zip
Adding upstream version 110.0.1.upstream/110.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'testing/web-platform/tests/html/cross-origin-opener-policy/resources')
-rw-r--r--testing/web-platform/tests/html/cross-origin-opener-policy/resources/call-functionCalledByOpenee.html5
-rw-r--r--testing/web-platform/tests/html/cross-origin-opener-policy/resources/common.js86
-rw-r--r--testing/web-platform/tests/html/cross-origin-opener-policy/resources/coop-coep.py84
-rw-r--r--testing/web-platform/tests/html/cross-origin-opener-policy/resources/coop-same-origin-repeated.asis24
-rw-r--r--testing/web-platform/tests/html/cross-origin-opener-policy/resources/csp-sandbox.py29
-rw-r--r--testing/web-platform/tests/html/cross-origin-opener-policy/resources/fully-loaded.js10
-rw-r--r--testing/web-platform/tests/html/cross-origin-opener-policy/resources/iframe-test.js234
-rw-r--r--testing/web-platform/tests/html/cross-origin-opener-policy/resources/popup-test.js99
-rw-r--r--testing/web-platform/tests/html/cross-origin-opener-policy/resources/postback.html45
-rw-r--r--testing/web-platform/tests/html/cross-origin-opener-policy/resources/postback.html.headers2
-rw-r--r--testing/web-platform/tests/html/cross-origin-opener-policy/resources/redirect.py5
-rw-r--r--testing/web-platform/tests/html/cross-origin-opener-policy/resources/resource-cleanup.html11
-rw-r--r--testing/web-platform/tests/html/cross-origin-opener-policy/resources/resource-popup.html21
-rw-r--r--testing/web-platform/tests/html/cross-origin-opener-policy/resources/universal-worker.js2
14 files changed, 657 insertions, 0 deletions
diff --git a/testing/web-platform/tests/html/cross-origin-opener-policy/resources/call-functionCalledByOpenee.html b/testing/web-platform/tests/html/cross-origin-opener-policy/resources/call-functionCalledByOpenee.html
new file mode 100644
index 0000000000..d0ff0b723e
--- /dev/null
+++ b/testing/web-platform/tests/html/cross-origin-opener-policy/resources/call-functionCalledByOpenee.html
@@ -0,0 +1,5 @@
+<!doctype html>
+<meta charset=utf-8>
+<script>
+window.opener.functionCalledByOpenee();
+</script>
diff --git a/testing/web-platform/tests/html/cross-origin-opener-policy/resources/common.js b/testing/web-platform/tests/html/cross-origin-opener-policy/resources/common.js
new file mode 100644
index 0000000000..a005cb8a20
--- /dev/null
+++ b/testing/web-platform/tests/html/cross-origin-opener-policy/resources/common.js
@@ -0,0 +1,86 @@
+// To use the functions below, be sure to include the following files in your
+// test:
+// - "/common/get-host-info.sub.js" to get the different origin values.
+
+const SAME_ORIGIN = {origin: get_host_info().HTTPS_ORIGIN, name: "SAME_ORIGIN"};
+const SAME_SITE = {origin: get_host_info().HTTPS_REMOTE_ORIGIN, name: "SAME_SITE"};
+const CROSS_ORIGIN = {origin: get_host_info().HTTPS_NOTSAMESITE_ORIGIN, name: "CROSS_ORIGIN"}
+
+function addScriptAndTriggerOnload(src, onload){
+ return `script = document.createElement("script");
+ script.src= "${src}" ;
+ script.onload = () => {
+ ${onload}
+ };
+ document.head.append(script);`
+}
+
+function verify_window(callback, w, hasOpener) {
+ // If there's no opener, the w must be closed:
+ assert_equals(w.closed, !hasOpener, 'w.closed');
+ // Opener's access on w.length is possible only if hasOpener:
+ assert_equals(w.length, hasOpener? 1: 0, 'w.length');
+ callback();
+}
+
+function validate_results(callback, test, w, channelName, hasOpener, openerDOMAccess, payload) {
+ assert_equals(payload.name, hasOpener ? channelName : "", 'name');
+ assert_equals(payload.opener, hasOpener, 'opener');
+ // TODO(zcorpan): add openerDOMAccess expectations to all tests
+ if (openerDOMAccess !== undefined) {
+ assert_equals(payload.openerDOMAccess, openerDOMAccess, 'openerDOMAccess');
+ }
+
+ // The window proxy in Chromium might still reflect the previous frame,
+ // until its unloaded. This delays the verification of w here.
+ if( !w.closed && w.length == 0) {
+ test.step_timeout( () => {
+ verify_window(callback, w, hasOpener);
+ }, 500);
+ } else {
+ verify_window(callback, w, hasOpener);
+ }
+}
+
+// Note: This function is deprecated and should not be used by new tests.
+// Instead, use `dispatcher_url_test()`.
+function url_test(t, url, channelName, hasOpener, openerDOMAccess, callback) {
+ if (callback === undefined) {
+ callback = () => { t.done(); };
+ }
+ const bc = new BroadcastChannel(channelName);
+ bc.onmessage = t.step_func(event => {
+ const payload = event.data;
+ validate_results(callback, t, w, channelName, hasOpener, openerDOMAccess, payload);
+ });
+
+ const w = window.open(url, channelName);
+
+ // Close the popup once the test is complete.
+ // The browsing context might be closed hence use the broadcast channel
+ // to trigger the closure.
+ t.add_cleanup(() => {
+ bc.postMessage("close");
+ });
+}
+
+// Similar to `url_test()` above except that this uses a dispatcher instead of
+// BroadcastChannel (useful in cases where the context we are testing in is a
+// third-party iframe that doesn't share a partition with the top-level
+// site).
+async function dispatcher_url_test(t, url, responseToken, iframeToken, hasOpener, openerDOMAccess, callback) {
+
+ const w = window.open(url, responseToken);
+
+ // Close the popup once the test is complete.
+ // The browsing context might be closed hence we'll have the iframe trigger
+ // the closure by sending it a 'close' message.
+ t.add_cleanup(async () => {
+ await send(iframeToken, "close");
+ });
+
+ var payload = await receive(responseToken);
+ payload = JSON.parse(payload);
+ validate_results(callback, t, w, responseToken, hasOpener, openerDOMAccess, payload);
+}
+
diff --git a/testing/web-platform/tests/html/cross-origin-opener-policy/resources/coop-coep.py b/testing/web-platform/tests/html/cross-origin-opener-policy/resources/coop-coep.py
new file mode 100644
index 0000000000..d8e3bf0d42
--- /dev/null
+++ b/testing/web-platform/tests/html/cross-origin-opener-policy/resources/coop-coep.py
@@ -0,0 +1,84 @@
+import json
+
+def main(request, response):
+ requestData = request.GET
+ if request.method == u"POST":
+ requestData = request.POST
+
+ coop = requestData.first(b"coop")
+ coopReportOnly = requestData.first(b"coop-report-only", None)
+ coep = requestData.first(b"coep")
+ coepReportOnly = requestData.first(b"coep-report-only", None)
+ redirect = requestData.first(b"redirect", None)
+ if coop != b"":
+ response.headers.set(b"Cross-Origin-Opener-Policy", coop)
+ if coopReportOnly is not None:
+ response.headers.set(b"Cross-Origin-Opener-Policy-Report-Only", coopReportOnly)
+ if coep != b"":
+ response.headers.set(b"Cross-Origin-Embedder-Policy", coep)
+ if coepReportOnly is not None:
+ response.headers.set(b"Cross-Origin-Embedder-Policy-Report-Only", coepReportOnly)
+ if b'cache' in requestData:
+ response.headers.set(b'Cache-Control', b'max-age=3600')
+ host = request.url_parts[1]
+
+ if redirect != None:
+ response.status = 302
+ response.headers.set(b"Location", redirect)
+ return
+
+ # Collect relevant params to be visible to response JS
+ params = {}
+ for key in (b"navHistory", b"avoidBackAndForth", b"navigate", b"channel", b"responseToken", b"iframeToken"):
+ value = requestData.first(key, None)
+ params[key.decode()] = value and value.decode()
+
+ response.content = b"""
+<!doctype html>
+<meta charset=utf-8>
+<script src="/common/get-host-info.sub.js"></script>
+<script src="/html/cross-origin-opener-policy/resources/fully-loaded.js"></script>
+<body>
+<script>
+ const params = %s;
+ const navHistory = params.navHistory;
+ const avoidBackAndForth = params.avoidBackAndForth;
+ const navigate = params.navigate;
+ if (navHistory !== null) {
+ fullyLoaded().then(() => {
+ history.go(Number(navHistory));
+ });
+ } else if (navigate !== null && (history.length === 1 || !avoidBackAndForth)) {
+ fullyLoaded().then(() => {
+ self.location = navigate;
+ });
+ } else {
+ let openerDOMAccessAllowed = false;
+ try {
+ openerDOMAccessAllowed = !!self.opener.document.URL;
+ } catch(ex) {
+ }
+ // Handle the response from the frame, closing the popup once the
+ // test completes.
+ addEventListener("message", event => {
+ if (event.data == "close") {
+ close();
+ }
+ });
+ iframe = document.createElement("iframe");
+ iframe.onload = () => {
+ const payload = { name: self.name, opener: !!self.opener, openerDOMAccess: openerDOMAccessAllowed };
+ iframe.contentWindow.postMessage(payload, "*");
+ };
+ const channelName = params.channel;
+ const responseToken = params.responseToken;
+ const iframeToken = params.iframeToken;
+ iframe.src = `${get_host_info().HTTPS_ORIGIN}/html/cross-origin-opener-policy/resources/postback.html` +
+ `?channel=${encodeURIComponent(channelName)}` +
+ `&responseToken=${responseToken}` +
+ `&iframeToken=${iframeToken}`;
+ document.body.appendChild(iframe);
+ }
+</script>
+</body>
+""" % json.dumps(params).encode("utf-8")
diff --git a/testing/web-platform/tests/html/cross-origin-opener-policy/resources/coop-same-origin-repeated.asis b/testing/web-platform/tests/html/cross-origin-opener-policy/resources/coop-same-origin-repeated.asis
new file mode 100644
index 0000000000..082478e159
--- /dev/null
+++ b/testing/web-platform/tests/html/cross-origin-opener-policy/resources/coop-same-origin-repeated.asis
@@ -0,0 +1,24 @@
+HTTP/1.1 200 OK
+Cross-Origin-Opener-Policy: same-origin
+Cross-Origin-Opener-Policy: same-origin
+Server: BaseHTTP/0.3 Python/2.7.15+
+Date: Wed, 18 Dec 2019 00:47:08 GMT
+
+<!doctype html>
+<meta charset=utf-8>
+<script src="/common/get-host-info.sub.js"></script>
+<iframe></iframe>
+<script>
+ const navigate = new URL(location).searchParams.get("navigate");
+ if (navigate !== null) {
+ self.location = navigate;
+ } else {
+ const iframe = document.querySelector("iframe");
+ iframe.onload = () => {
+ const payload = { name: self.name, opener: !!self.opener };
+ iframe.contentWindow.postMessage(payload, "*");
+ };
+ const channelName = new URL(location).searchParams.get("channel");
+ iframe.src = `${get_host_info().HTTPS_ORIGIN}/html/cross-origin-opener-policy/resources/postback.html?channel=${channelName}`;
+ }
+</script>
diff --git a/testing/web-platform/tests/html/cross-origin-opener-policy/resources/csp-sandbox.py b/testing/web-platform/tests/html/cross-origin-opener-policy/resources/csp-sandbox.py
new file mode 100644
index 0000000000..6cf21aeccf
--- /dev/null
+++ b/testing/web-platform/tests/html/cross-origin-opener-policy/resources/csp-sandbox.py
@@ -0,0 +1,29 @@
+def main(request, response):
+ coop = request.GET.first(b"coop")
+ coep = request.GET.first(b"coep")
+ sandbox = request.GET.first(b"sandbox")
+ if coop != "":
+ response.headers.set(b"Cross-Origin-Opener-Policy", coop)
+ if coep != "":
+ response.headers.set(b"Cross-Origin-Embedder-Policy", coep)
+ response.headers.set(b"Content-Security-Policy", b"sandbox " + sandbox + b";")
+
+ # Open a popup to coop-coep.py with the same parameters (except sandbox)
+ response.content = b"""
+<!doctype html>
+<meta charset=utf-8>
+<script src="/common/get-host-info.sub.js"></script>
+<script src="/html/cross-origin-opener-policy/resources/fully-loaded.js"></script>
+<script>
+ const params = new URL(location).searchParams;
+ params.delete("sandbox");
+ const navigate = params.get("navigate");
+ if (navigate) {
+ fullyLoaded().then(() => {
+ self.location = navigate;
+ });
+ } else {
+ window.open(`/html/cross-origin-opener-policy/resources/coop-coep.py?${params}`);
+ }
+</script>
+"""
diff --git a/testing/web-platform/tests/html/cross-origin-opener-policy/resources/fully-loaded.js b/testing/web-platform/tests/html/cross-origin-opener-policy/resources/fully-loaded.js
new file mode 100644
index 0000000000..d40e00af43
--- /dev/null
+++ b/testing/web-platform/tests/html/cross-origin-opener-policy/resources/fully-loaded.js
@@ -0,0 +1,10 @@
+// Return a promise, which resolves when new navigations aren't considered
+// client-side redirects anymore.
+//
+// Note: A long `setTimeout` is used, because client-side redirect is an
+// heuristic and isn't clearly specified.
+function fullyLoaded() {
+ return new Promise((resolve, reject) => {
+ addEventListener('load', () => setTimeout(resolve, 2000))
+ });
+}
diff --git a/testing/web-platform/tests/html/cross-origin-opener-policy/resources/iframe-test.js b/testing/web-platform/tests/html/cross-origin-opener-policy/resources/iframe-test.js
new file mode 100644
index 0000000000..a18688caf7
--- /dev/null
+++ b/testing/web-platform/tests/html/cross-origin-opener-policy/resources/iframe-test.js
@@ -0,0 +1,234 @@
+// To use the functions below, be sure to include the following files in your
+// test:
+// - "/common/get-host-info.sub.js" to get the different origin values.
+// - "common.js" to have the origins easily available.
+// - "/common/dispatcher/dispatcher.js" for cross-origin messaging.
+// - "/common/utils.js" for token().
+
+function getBaseExecutorPath(origin) {
+ return origin + '/common/dispatcher/executor.html';
+}
+
+function getHeadersPipe(headers) {
+ const coop_header = headers.coop ?
+ `|header(Cross-Origin-Opener-Policy,${encodeURIComponent(headers.coop)})` : '';
+ const coep_header = headers.coep ?
+ `|header(Cross-Origin-Embedder-Policy,${encodeURIComponent(headers.coep)})` : '';
+ return coop_header + coep_header;
+}
+
+function getExecutorPath(uuid, origin, headers) {
+ return getBaseExecutorPath(origin) +
+ `?uuid=${uuid}` +
+ `&pipe=${getHeadersPipe(headers)}`;
+}
+
+function evaluate(target_token, script) {
+ const reply_token = token();
+ send(target_token, `send('${reply_token}', ${script});`);
+ return receive(reply_token);
+}
+
+// Return true if an opened iframe can access |property| on a stored
+// window.popup object without throwing an error.
+function iframeCanAccessProperty(iframe_token, property) {
+ const reply_token = token();
+ send(iframe_token,
+ `try {
+ const unused = window.popup['${property}'];
+ send('${reply_token}', 'true')
+ } catch (errors) {
+ send('${reply_token}', 'false')
+ }`);
+ return receive(reply_token);
+}
+
+// Returns the script necessary to open a popup, given the method in
+// `popup_via`. Supported methods are 'window_open' that leverages
+// window.open(), 'anchor' that creates an <a> HTML element and clicks on it,
+// and 'form' that creates a form and submits it.
+function popupOpeningScript(popup_via, popup_url, popup_origin, headers,
+ popup_token) {
+ if (popup_via === 'window_open')
+ return `window.popup = window.open('${popup_url}', '${popup_token}');`;
+
+ if (popup_via === 'anchor') {
+ return `
+ const anchor = document.createElement('a');
+ anchor.href = '${popup_url}';
+ anchor.rel = "opener";
+ anchor.target = '${popup_token}';
+ anchor.innerText = "anchor";
+ document.body.appendChild(anchor);
+ anchor.click();
+ `;
+ }
+
+ if (popup_via === "form") {
+ return `
+ const form = document.createElement("form");
+ form.action = '${getBaseExecutorPath(popup_origin.origin)}';
+ form.target = '${popup_token}';
+ form.method = 'GET';
+ const add_param = (name, value) => {
+ const input = document.createElement("input");
+ input.name = name;
+ input.value = value;
+ form.appendChild(input);
+ };
+ add_param("uuid", "${popup_token}");
+ add_param("pipe", "${getHeadersPipe(headers)}");
+ document.body.appendChild(form);
+ form.submit();
+ `;
+ }
+
+ assert_not_reached('Unrecognized popup opening method.');
+}
+
+
+// Verifies that a popup with origin `popup_origin` and headers `headers` has
+// the expected `opener_state` after being opened from an iframe with origin
+// `iframe_origin`.
+function iframe_test(description, iframe_origin, popup_origin, headers,
+ expected_opener_state) {
+ for (const popup_via of ['window_open', 'anchor','form']) {
+ promise_test(async t => {
+ const iframe_token = token();
+ const popup_token = token();
+ const reply_token = token();
+
+ const frame = document.createElement("iframe");
+ const iframe_url = getExecutorPath(
+ iframe_token,
+ iframe_origin.origin,
+ {});
+
+ frame.src = iframe_url;
+ document.body.append(frame);
+
+ send(iframe_token, `send('${reply_token}', 'Iframe loaded');`);
+ assert_equals(await receive(reply_token), 'Iframe loaded');
+
+ const popup_url = getExecutorPath(
+ popup_token,
+ popup_origin.origin,
+ headers);
+
+ // We open popup and then ping it, it will respond after loading.
+ send(iframe_token, popupOpeningScript(popup_via, popup_url, popup_origin,
+ headers, popup_token));
+ send(popup_token, `send('${reply_token}', 'Popup loaded');`);
+ assert_equals(await receive(reply_token), 'Popup loaded');
+
+ // Make sure the popup and the iframe are removed once the test has run,
+ // keeping a clean state.
+ add_completion_callback(() => {
+ frame.remove();
+ send(popup_token, `close()`);
+ });
+
+ // Give some time for things to settle across processes etc. before
+ // proceeding with verifications.
+ await new Promise(resolve => { t.step_timeout(resolve, 500); });
+
+ // Verify that the opener is in the state we expect it to be in.
+ switch (expected_opener_state) {
+ case 'preserved': {
+ assert_equals(
+ await evaluate(popup_token, 'opener != null'), "true",
+ 'Popup has an opener?');
+ assert_equals(
+ await evaluate(popup_token, `name === '${popup_token}'`), "true",
+ 'Popup has a name?');
+
+ // When the popup was created using window.open, we've kept a handle
+ // and we can do extra verifications.
+ if (popup_via === 'window_open') {
+ assert_equals(
+ await evaluate(iframe_token, 'popup != null'), "true",
+ 'Popup handle is non-null in iframe?');
+ assert_equals(
+ await evaluate(iframe_token, 'popup.closed'), "false",
+ 'Popup appears closed from iframe?');
+ assert_equals(
+ await iframeCanAccessProperty(iframe_token, "document"),
+ popup_origin === iframe_origin ? "true" : "false",
+ 'Iframe has dom access to the popup?');
+ assert_equals(
+ await iframeCanAccessProperty(iframe_token, "frames"), "true",
+ 'Iframe has cross origin access to the popup?');
+ }
+ break;
+ }
+ case 'restricted': {
+ assert_equals(
+ await evaluate(popup_token, 'opener != null'), "true",
+ 'Popup has an opener?');
+ assert_equals(
+ await evaluate(popup_token, `name === '${popup_token}'`), "true",
+ 'Popup has a name?');
+
+ // When the popup was created using window.open, we've kept a handle
+ // and we can do extra verifications.
+ if (popup_via === 'window_open') {
+ assert_equals(
+ await evaluate(iframe_token, 'popup != null'), "true",
+ 'Popup handle is non-null in iframe?');
+ assert_equals(
+ await evaluate(iframe_token, 'popup.closed'), "false",
+ 'Popup appears closed from iframe?');
+ assert_equals(
+ await iframeCanAccessProperty(iframe_token, "document"), "false",
+ 'Iframe has dom access to the popup?');
+ assert_equals(
+ await iframeCanAccessProperty(iframe_token, "frames"), "false",
+ 'Iframe has cross origin access to the popup?');
+ assert_equals(
+ await iframeCanAccessProperty(iframe_token, "closed"), "true",
+ 'Iframe has limited cross origin access to the popup?');
+ }
+ break;
+ }
+ case 'severed': {
+ assert_equals(await evaluate(popup_token, 'opener != null'), "false",
+ 'Popup has an opener?');
+ assert_equals(
+ await evaluate(popup_token, `name === '${popup_token}'`), "false",
+ 'Popup has a name?');
+
+ // When the popup was created using window.open, we've kept a handle
+ // and we can do extra verifications.
+ if (popup_via === 'window_open') {
+ assert_equals(
+ await evaluate(iframe_token, 'popup != null'), "true",
+ 'Popup handle is non-null in iframe?');
+ assert_equals(
+ await evaluate(iframe_token, 'popup.closed'), "true",
+ 'Popup appears closed from iframe?');
+ }
+ break;
+ }
+ case 'noopener': {
+ assert_equals(await evaluate(popup_token, 'opener != null'), "false",
+ 'Popup has an opener?');
+ assert_equals(
+ await evaluate(popup_token, `name === '${popup_token}'`), "false",
+ 'Popup has a name?');
+
+ // When the popup was created using window.open, we've kept a handle
+ // and we can do extra verifications.
+ if (popup_via === 'window_open') {
+ assert_equals(
+ await evaluate(iframe_token, 'popup == null'), "true",
+ 'Popup handle is null in iframe?');
+ }
+ break;
+ }
+ default:
+ assert_not_reached('Unrecognized opener state: ' +
+ expected_opener_state);
+ }
+ }, `${description} with ${popup_via}`);
+ }
+}
diff --git a/testing/web-platform/tests/html/cross-origin-opener-policy/resources/popup-test.js b/testing/web-platform/tests/html/cross-origin-opener-policy/resources/popup-test.js
new file mode 100644
index 0000000000..c2717bb135
--- /dev/null
+++ b/testing/web-platform/tests/html/cross-origin-opener-policy/resources/popup-test.js
@@ -0,0 +1,99 @@
+// To use the functions below, be sure to include the following files in your
+// test:
+// - "/common/get-host-info.sub.js" to get the different origin values.
+// - "common.js" to have the origins easily available.
+// - "/common/dispatcher/dispatcher.js" for cross-origin messaging.
+// - "/common/utils.js" for token().
+
+function getExecutorPath(uuid, origin, headers) {
+ const executor_path = '/common/dispatcher/executor.html?';
+ const coop_header = headers.coop ?
+ `|header(Cross-Origin-Opener-Policy,${encodeURIComponent(headers.coop)})` : '';
+ const coep_header = headers.coep ?
+ `|header(Cross-Origin-Embedder-Policy,${encodeURIComponent(headers.coep)})` : '';
+ return origin +
+ executor_path +
+ `uuid=${uuid}` +
+ '&pipe=' + coop_header + coep_header;
+}
+
+function getPopupHasOpener(popup_token) {
+ const reply_token = token();
+ send(popup_token, `send('${reply_token}', window.opener != null);`);
+ return receive(reply_token);
+}
+
+// Return true if |object|.|property| can be called without throwing an error.
+function canAccessProperty(object, property) {
+ try {
+ const unused = object[property];
+ return true;
+ } catch (errors) {
+ return false;
+ }
+}
+
+// Verifies that a popup with origin `origin` and headers `headers` has
+// the expected `opener_state` after being opened.
+async function popup_test(description, origin, headers, expected_opener_state) {
+ promise_test(async t => {
+ const popup_token = token();
+ const reply_token = token();
+
+ const popup_url = getExecutorPath(
+ popup_token,
+ origin.origin,
+ headers);
+
+ // We open popup and then ping it, it will respond after loading.
+ const popup = window.open(popup_url);
+ send(popup_token, `send('${reply_token}', 'Popup loaded');`);
+ assert_equals(await receive(reply_token), 'Popup loaded');
+
+ // Make sure the popup will be closed once the test has run, keeping a clean
+ // state.
+ t.add_cleanup(() => {
+ send(popup_token, `close()`);
+ });
+
+ // Give some time for things to settle across processes etc. before
+ // proceeding with verifications.
+ await new Promise(resolve => { t.step_timeout(resolve, 500); });
+
+ // Verify that the opener is in the state we expect it to be in.
+ switch (expected_opener_state) {
+ case 'preserved': {
+ assert_false(popup.closed, 'Popup is closed from opener?');
+ assert_true(await getPopupHasOpener(popup_token) === "true",
+ 'Popup has nulled opener?');
+ assert_equals(canAccessProperty(popup, "document"),
+ origin === SAME_ORIGIN,
+ 'Main page has dom access to the popup?');
+ assert_true(canAccessProperty(popup, "frames"),
+ 'Main page has cross origin access to the popup?');
+ break;
+ }
+ case 'restricted': {
+ assert_false(popup.closed, 'Popup is closed from opener?');
+ assert_true(await getPopupHasOpener(popup_token) === "true",
+ 'Popup has nulled opener?');
+ assert_false(canAccessProperty(popup, "document"),
+ 'Main page has dom access to the popup?');
+ assert_false(canAccessProperty(popup, "frames"),
+ 'Main page has cross origin access to the popup?');
+ assert_true(canAccessProperty(popup, "closed"),
+ 'Main page has limited cross origin access to the popup?');
+ break;
+ }
+ case 'severed': {
+ assert_true(popup.closed, 'Popup is closed from opener?');
+ assert_false(await getPopupHasOpener(popup_token) === "true",
+ 'Popup has nulled opener?');
+ break;
+ }
+ default:
+ assert_unreached(true, "Unrecognized opener relationship: " + expected_opener_state);
+ }
+ }, description);
+}
+
diff --git a/testing/web-platform/tests/html/cross-origin-opener-policy/resources/postback.html b/testing/web-platform/tests/html/cross-origin-opener-policy/resources/postback.html
new file mode 100644
index 0000000000..35b3be5c93
--- /dev/null
+++ b/testing/web-platform/tests/html/cross-origin-opener-policy/resources/postback.html
@@ -0,0 +1,45 @@
+<!doctype html>
+<meta charset=utf-8>
+<script src="/common/dispatcher/dispatcher.js"></script>
+<script>
+const params = new URL(location).searchParams;
+const channelName = params.get("channel");
+const responseToken = params.get("responseToken");
+const iframeToken = params.get("iframeToken");
+
+// If the channel parameter is set, use the BroadcastChannel-based communication
+// method. Otherwise, use the dispatcher (useful in cases where this is embedded
+// in a third-party iframe that doesn't share a partition with the top-level
+// site).
+if (channelName != "null") {
+ const bc = new BroadcastChannel(channelName);
+ // Handle the close message from the test-cleanup, forwarding it to the
+ // top level document, as this iframe and the opening document might not
+ // be able to close the popup.
+ bc.onmessage = event => {
+ if (event.data == "close") {
+ top.postMessage("close", "*");
+ }
+ };
+
+ window.addEventListener("message", event => {
+ bc.postMessage(event.data);
+ });
+
+} else {
+ window.addEventListener("message", event => {
+ send(responseToken, JSON.stringify(event.data));
+ });
+
+ async function waitToClose() {
+ response = await receive(iframeToken);
+ // Handle the close message from the test-cleanup, forwarding it to the
+ // top level document, as this iframe and the opening document might not
+ // be able to close the popup.
+ if (response == "close") {
+ top.postMessage("close", "*");
+ }
+ }
+ waitToClose();
+}
+</script>
diff --git a/testing/web-platform/tests/html/cross-origin-opener-policy/resources/postback.html.headers b/testing/web-platform/tests/html/cross-origin-opener-policy/resources/postback.html.headers
new file mode 100644
index 0000000000..4e798cd9f5
--- /dev/null
+++ b/testing/web-platform/tests/html/cross-origin-opener-policy/resources/postback.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-opener-policy/resources/redirect.py b/testing/web-platform/tests/html/cross-origin-opener-policy/resources/redirect.py
new file mode 100644
index 0000000000..88dbd60fae
--- /dev/null
+++ b/testing/web-platform/tests/html/cross-origin-opener-policy/resources/redirect.py
@@ -0,0 +1,5 @@
+from wptserve.utils import isomorphic_encode
+
+def main(request, response):
+ response.status = 302
+ response.headers.set(b"Location", isomorphic_encode(request.url[request.url.find(u'?')+1:]))
diff --git a/testing/web-platform/tests/html/cross-origin-opener-policy/resources/resource-cleanup.html b/testing/web-platform/tests/html/cross-origin-opener-policy/resources/resource-cleanup.html
new file mode 100644
index 0000000000..3ae5587c7d
--- /dev/null
+++ b/testing/web-platform/tests/html/cross-origin-opener-policy/resources/resource-cleanup.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html lang="en">
+<meta charset="utf-8">
+<title>Redirect destination for non-HTML documents to close themselves</title>
+<script>
+'use strict';
+const channel = new URL(location).searchParams.get('channel');
+const bc = new BroadcastChannel(channel);
+bc.onmessage = () => close();
+bc.postMessage({name: 'FAIL', closed: 'FAIL' });
+</script>
diff --git a/testing/web-platform/tests/html/cross-origin-opener-policy/resources/resource-popup.html b/testing/web-platform/tests/html/cross-origin-opener-policy/resources/resource-popup.html
new file mode 100644
index 0000000000..2957e35f59
--- /dev/null
+++ b/testing/web-platform/tests/html/cross-origin-opener-policy/resources/resource-popup.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<html lang="en">
+<meta charset="utf-8">
+<title></title>
+<script>
+'use strict';
+const params = new URL(location).searchParams;
+const bc = new BroadcastChannel(params.get('channel_name'));
+const win = open(params.get('resource'), params.get('resource_name'));
+
+bc.onmessage = () => {
+ win.close();
+ close();
+};
+const id = setInterval(() => {
+ if (win.closed || win.location.href !== 'about:blank') {
+ clearInterval(id);
+ bc.postMessage({name: win.name || null, closed: win.closed});
+ }
+}, 100);
+</script>
diff --git a/testing/web-platform/tests/html/cross-origin-opener-policy/resources/universal-worker.js b/testing/web-platform/tests/html/cross-origin-opener-policy/resources/universal-worker.js
new file mode 100644
index 0000000000..2441679372
--- /dev/null
+++ b/testing/web-platform/tests/html/cross-origin-opener-policy/resources/universal-worker.js
@@ -0,0 +1,2 @@
+onmessage = message => eval(message.data);
+onfetch = event => fetchHandler(event);