diff --git a/html/cross-origin-opener-policy/reporting-coop-navigated-popup.https.html b/html/cross-origin-opener-policy/reporting-coop-navigated-popup.https.html
new file mode 100644
index 00000000000000..8642a0aba710ba
--- /dev/null
+++ b/html/cross-origin-opener-policy/reporting-coop-navigated-popup.https.html
@@ -0,0 +1,82 @@
+
Cross-Origin-Opener-Policy: a navigated popup with reporting
+
+
+
+
+
+
diff --git a/html/cross-origin-opener-policy/reporting-coop-navigated-popup.https.html.sub.headers b/html/cross-origin-opener-policy/reporting-coop-navigated-popup.https.html.sub.headers
new file mode 100644
index 00000000000000..b4d5d16cf8d516
--- /dev/null
+++ b/html/cross-origin-opener-policy/reporting-coop-navigated-popup.https.html.sub.headers
@@ -0,0 +1,2 @@
+Cross-Origin-Opener-Policy: same-origin-allow-popups; report-to="coop-report-endpoint"
+report-to: { "group": "coop-report-endpoint", "max_age": 10886400, "endpoints": [{ "url": "https://{{hosts[][www]}}:{{ports[https][0]}}/html/cross-origin-opener-policy/resources/report.py?endpoint=coop-report-endpoint" }] }, { "group": "coop-report-only-endpoint", "max_age": 10886400, "endpoints": [{ "url": "https://{{hosts[][www]}}:{{ports[https][0]}}/html/cross-origin-opener-policy/resources/report.py?endpoint=coop-report-only-endpoint" }]}
diff --git a/html/cross-origin-opener-policy/reporting-popup-same-origin-allow-popups-report-to.https.html b/html/cross-origin-opener-policy/reporting-popup-same-origin-allow-popups-report-to.https.html
new file mode 100644
index 00000000000000..678fe83a9a7db8
--- /dev/null
+++ b/html/cross-origin-opener-policy/reporting-popup-same-origin-allow-popups-report-to.https.html
@@ -0,0 +1,123 @@
+
+reporting same origin with report-to
+
+
+
+
+
+
+
diff --git a/html/cross-origin-opener-policy/reporting-popup-same-origin-allow-popups-report-to.https.html.headers b/html/cross-origin-opener-policy/reporting-popup-same-origin-allow-popups-report-to.https.html.headers
new file mode 100644
index 00000000000000..bcc03a6fa38bc8
--- /dev/null
+++ b/html/cross-origin-opener-policy/reporting-popup-same-origin-allow-popups-report-to.https.html.headers
@@ -0,0 +1,3 @@
+report-to: { "group": "coop-report-endpoint", "max_age": 10886400, "endpoints": [{ "url": "https://{{hosts[][www]}}:{{ports[https][0]}}/html/cross-origin-opener-policy/resources/report.py?endpoint=coop-report-endpoint" }] }, { "group": "coop-report-only-endpoint", "max_age": 10886400, "endpoints": [{ "url": "https://{{hosts[][www]}}:{{ports[https][0]}}/html/cross-origin-opener-policy/resources/report.py?endpoint=coop-report-only-endpoint" }]}
+Cross-Origin-Opener-Policy: same-origin-allow-popups; report-to="coop-report-endpoint"
+Referrer-Policy: origin
diff --git a/html/cross-origin-opener-policy/reporting-popup-same-origin-coep-report-to.https.html b/html/cross-origin-opener-policy/reporting-popup-same-origin-coep-report-to.https.html
new file mode 100644
index 00000000000000..80a7853ce88030
--- /dev/null
+++ b/html/cross-origin-opener-policy/reporting-popup-same-origin-coep-report-to.https.html
@@ -0,0 +1,108 @@
+
+reporting same origin with report-to
+
+
+
+
+
+
+
diff --git a/html/cross-origin-opener-policy/reporting-popup-same-origin-coep-report-to.https.html.headers b/html/cross-origin-opener-policy/reporting-popup-same-origin-coep-report-to.https.html.headers
new file mode 100644
index 00000000000000..fb5526fbdac60f
--- /dev/null
+++ b/html/cross-origin-opener-policy/reporting-popup-same-origin-coep-report-to.https.html.headers
@@ -0,0 +1,4 @@
+report-to: { "group": "coop-report-endpoint", "max_age": 10886400, "endpoints": [{ "url": "https://{{hosts[][www]}}:{{ports[https][0]}}/html/cross-origin-opener-policy/resources/report.py?endpoint=coop-report-endpoint" }] }, { "group": "coop-report-only-endpoint", "max_age": 10886400, "endpoints": [{ "url": "https://{{hosts[][www]}}:{{ports[https][0]}}/html/cross-origin-opener-policy/resources/report.py?endpoint=coop-report-only-endpoint" }]}
+Cross-Origin-Opener-Policy: same-origin; report-to="coop-report-endpoint"
+Cross-Origin-Embedder-Policy: require-corp
+Referrer-Policy: origin
\ No newline at end of file
diff --git a/html/cross-origin-opener-policy/reporting-popup-same-origin-report-to.https.html b/html/cross-origin-opener-policy/reporting-popup-same-origin-report-to.https.html
new file mode 100644
index 00000000000000..02f70d5458ac50
--- /dev/null
+++ b/html/cross-origin-opener-policy/reporting-popup-same-origin-report-to.https.html
@@ -0,0 +1,213 @@
+
+reporting same origin with report-to
+
+
+
+
+
+
+
diff --git a/html/cross-origin-opener-policy/reporting-popup-same-origin-report-to.https.html.sub.headers b/html/cross-origin-opener-policy/reporting-popup-same-origin-report-to.https.html.sub.headers
new file mode 100644
index 00000000000000..3850783812d3ad
--- /dev/null
+++ b/html/cross-origin-opener-policy/reporting-popup-same-origin-report-to.https.html.sub.headers
@@ -0,0 +1,3 @@
+report-to: { "group": "coop-report-endpoint", "max_age": 10886400, "endpoints": [{ "url": "https://{{hosts[][www]}}:{{ports[https][0]}}/html/cross-origin-opener-policy/resources/report.py?endpoint=coop-report-endpoint" }] }, { "group": "coop-report-only-endpoint", "max_age": 10886400, "endpoints": [{ "url": "https://{{hosts[][www]}}:{{ports[https][0]}}/html/cross-origin-opener-policy/resources/report.py?endpoint=coop-report-only-endpoint" }]}
+Cross-Origin-Opener-Policy: same-origin; report-to="coop-report-endpoint"
+Referrer-Policy: no-referrer
diff --git a/html/cross-origin-opener-policy/reporting-popup-same-origin.https.html b/html/cross-origin-opener-policy/reporting-popup-same-origin.https.html
new file mode 100644
index 00000000000000..a2ed37e46752eb
--- /dev/null
+++ b/html/cross-origin-opener-policy/reporting-popup-same-origin.https.html
@@ -0,0 +1,96 @@
+
+reporting same origin
+
+
+
+
+
+
+
diff --git a/html/cross-origin-opener-policy/reporting-popup-same-origin.https.html.headers b/html/cross-origin-opener-policy/reporting-popup-same-origin.https.html.headers
new file mode 100644
index 00000000000000..46ad58d83bf6e9
--- /dev/null
+++ b/html/cross-origin-opener-policy/reporting-popup-same-origin.https.html.headers
@@ -0,0 +1 @@
+Cross-Origin-Opener-Policy: same-origin
diff --git a/html/cross-origin-opener-policy/reporting-popup-unafe-none-report-to.https.html b/html/cross-origin-opener-policy/reporting-popup-unafe-none-report-to.https.html
new file mode 100644
index 00000000000000..62eb006586478a
--- /dev/null
+++ b/html/cross-origin-opener-policy/reporting-popup-unafe-none-report-to.https.html
@@ -0,0 +1,123 @@
+
+reporting same origin with report-to
+
+
+
+
+
+
+
diff --git a/html/cross-origin-opener-policy/reporting-popup-unafe-none-report-to.https.html.headers b/html/cross-origin-opener-policy/reporting-popup-unafe-none-report-to.https.html.headers
new file mode 100644
index 00000000000000..d6a6e7ecdcd131
--- /dev/null
+++ b/html/cross-origin-opener-policy/reporting-popup-unafe-none-report-to.https.html.headers
@@ -0,0 +1,2 @@
+report-to: { "group": "coop-report-endpoint", "max_age": 10886400, "endpoints": [{ "url": "https://{{hosts[][www]}}:{{ports[https][0]}}/html/cross-origin-opener-policy/resources/report.py?endpoint=coop-report-endpoint" }] }, { "group": "coop-report-only-endpoint", "max_age": 10886400, "endpoints": [{ "url": "https://{{hosts[][www]}}:{{ports[https][0]}}/html/cross-origin-opener-policy/resources/report.py?endpoint=coop-report-only-endpoint" }]}
+Cross-Origin-Opener-Policy: unsafe-none; report-to="coop-report-endpoint"
diff --git a/html/cross-origin-opener-policy/resources/common.js b/html/cross-origin-opener-policy/resources/common.js
index fb517e8c40ac56..8a3cd133734dc3 100644
--- a/html/cross-origin-opener-policy/resources/common.js
+++ b/html/cross-origin-opener-policy/resources/common.js
@@ -29,11 +29,14 @@ function validate_results(callback, test, w, channelName, hasOpener, openerDOMAc
}
}
-function url_test(t, url, channelName, hasOpener, openerDOMAccess) {
+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(() => { t.done(); }, t, w, channelName, hasOpener, openerDOMAccess, payload);
+ validate_results(callback, t, w, channelName, hasOpener, openerDOMAccess, payload);
});
const w = window.open(url, channelName);
@@ -46,12 +49,12 @@ function url_test(t, url, channelName, hasOpener, openerDOMAccess) {
});
}
-function coop_coep_test(t, host, coop, coep, channelName, hasOpener, openerDOMAccess) {
- url_test(t, `${host.origin}/html/cross-origin-opener-policy/resources/coop-coep.py?coop=${encodeURIComponent(coop)}&coep=${coep}&channel=${channelName}`, channelName, hasOpener, openerDOMAccess);
+function coop_coep_test(t, host, coop, coep, channelName, hasOpener, openerDOMAccess, callback) {
+ url_test(t, `${host.origin}/html/cross-origin-opener-policy/resources/coop-coep.py?coop=${encodeURIComponent(coop)}&coep=${coep}&channel=${channelName}`, channelName, hasOpener, openerDOMAccess, callback);
}
-function coop_test(t, host, coop, channelName, hasOpener) {
- coop_coep_test(t, host, coop, "", channelName, hasOpener);
+function coop_test(t, host, coop, channelName, hasOpener, callback) {
+ coop_coep_test(t, host, coop, "", channelName, hasOpener, undefined /* openerDOMAccess */, callback);
}
function run_coop_tests(documentCOOPValueTitle, testArray) {
@@ -59,7 +62,7 @@ function run_coop_tests(documentCOOPValueTitle, testArray) {
async_test(t => {
coop_test(t, test[0], test[1],
`${documentCOOPValueTitle}_to_${test[0].name}_${test[1].replace(/ /g,"-")}`,
- test[2]);
+ test[2], () => { t.done(); });
}, `${documentCOOPValueTitle} document opening popup to ${test[0].origin} with COOP: "${test[1]}"`);
}
}
diff --git a/html/cross-origin-opener-policy/resources/coop-coep.py b/html/cross-origin-opener-policy/resources/coop-coep.py
index 8a7e0bc0a838fd..03fe6ccb1b96ce 100644
--- a/html/cross-origin-opener-policy/resources/coop-coep.py
+++ b/html/cross-origin-opener-policy/resources/coop-coep.py
@@ -1,13 +1,42 @@
+def get_reporting_group(host, endpoint):
+ return '\
+{{\
+ "group": "{endpoint}",\
+ "max_age": 10886400,\
+ "endpoints":\
+ [{{\
+ "url": "https://{host}/html/cross-origin-opener-policy/resources/report.py?endpoint={endpoint}"\
+ }}]\
+}}'.format(host=host, endpoint=endpoint)
+
def main(request, response):
coop = request.GET.first("coop")
+ coopReportOnly = request.GET.first("coop-report-only") if "coop-report-only" in request.GET else ""
coep = request.GET.first("coep")
+ coepReportOnly = request.GET.first("coep-report-only") if "coep-report-only" in request.GET else ""
redirect = request.GET.first("redirect", None)
if coop != "":
response.headers.set("Cross-Origin-Opener-Policy", coop)
+ if coop != "":
+ response.headers.set("Cross-Origin-Opener-Policy-Report-Only", coopReportOnly)
if coep != "":
response.headers.set("Cross-Origin-Embedder-Policy", coep)
+ if coep != "":
+ response.headers.set("Cross-Origin-Embedder-Policy-Report-Only", coepReportOnly)
if 'cache' in request.GET:
response.headers.set('Cache-Control', 'max-age=3600')
+ host = request.url_parts[1]
+
+ # add all possible reporting endpoints to the report-to header
+ # Note that this also returns the coop-report-endpoint, as it may override
+ # the test's endpoints if same-origin.
+ response.headers.set('report-to',
+ get_reporting_group(host, "coop-report-endpoint") + ',' +
+ get_reporting_group(host, "coop-report-only-endpoint") + ',' +
+ get_reporting_group(host, "coop-redirect-report-endpoint") + ',' +
+ get_reporting_group(host, "coop-redirect-report-only-endpoint") + ',' +
+ get_reporting_group(host, "coop-popup-report-endpoint") + ',' +
+ get_reporting_group(host, "coop-popup-report-only-endpoint") )
if redirect != None:
response.status = 302
diff --git a/html/cross-origin-opener-policy/resources/report.py b/html/cross-origin-opener-policy/resources/report.py
new file mode 100644
index 00000000000000..c9ea353a12fb18
--- /dev/null
+++ b/html/cross-origin-opener-policy/resources/report.py
@@ -0,0 +1,26 @@
+import json, uuid
+
+def main(request, response):
+ response.headers.set('Cache-Control', 'no-cache, no-store, must-revalidate');
+
+ key = 0;
+ if 'endpoint' in request.GET:
+ key = uuid.uuid5(uuid.NAMESPACE_OID, request.GET['endpoint']).get_urn()
+
+ if key == 0:
+ response.status = 400
+ return 'invalid endpoint'
+
+ if request.method == 'POST':
+ reports = request.server.stash.take(key) or []
+ for report in json.loads(request.body):
+ reports.append(report)
+ request.server.stash.put(key, reports)
+ return "done"
+
+ if request.method == 'GET':
+ response.headers.set('Content-Type', 'application/json')
+ return json.dumps(request.server.stash.take(key) or [])
+
+ response.status = 400
+ return 'invalid method'
diff --git a/html/cross-origin-opener-policy/resources/reporting-common.js b/html/cross-origin-opener-policy/resources/reporting-common.js
new file mode 100644
index 00000000000000..d834c080e44787
--- /dev/null
+++ b/html/cross-origin-opener-policy/resources/reporting-common.js
@@ -0,0 +1,177 @@
+// Allows RegExps to be pretty printed when printing unmatched expected reports.
+Object.defineProperty(RegExp.prototype, "toJSON", {
+ value: RegExp.prototype.toString
+});
+
+function wait(ms) {
+ return new Promise(resolve => step_timeout(resolve, ms));
+}
+
+async function pollReports(endpoint) {
+ const res = await fetch(
+ `resources/report.py?endpoint=${endpoint.name}`,
+ {cache: 'no-store'});
+ if (res.status !== 200) {
+ return;
+ }
+ for (const report of await res.json()) {
+ endpoint.reports.push(report);
+ }
+}
+
+// Recursively check that all members of expectedReport are present or matched
+// in report.
+// Report may have members not explicitly expected by expectedReport.
+function isObjectAsExpected(report, expectedReport) {
+ if (( report === undefined || report === null
+ || expectedReport === undefined || expectedReport === null )
+ && report !== expectedReport ) {
+ return false;
+ }
+ if (expectedReport instanceof RegExp && typeof report === "string") {
+ return expectedReport.test(report);
+ }
+ // Perform this check now, as RegExp and strings above have different typeof.
+ if (typeof report !== typeof expectedReport)
+ return false;
+ if (typeof expectedReport === 'object') {
+ return Object.keys(expectedReport).every(key => {
+ return isObjectAsExpected(report[key], expectedReport[key]);
+ });
+ }
+ return report == expectedReport;
+}
+
+async function checkForExpectedReport(expectedReport) {
+ return new Promise( async (resolve, reject) => {
+ const polls = 30;
+ const waitTime = 100;
+ for (var i=0; i < polls; ++i) {
+ pollReports(expectedReport.endpoint);
+ for (var j=0; j {
+ testFunction(resolve);
+ });
+ expectedReports = Array.from(
+ expectedReports,
+ report => replaceValuesInExpectedReport(report, channelName) );
+ await Promise.all(Array.from(expectedReports, checkForExpectedReport));
+}
+
+function coopCoepReportingTest(testName, host, coop, coep, hasOpener,
+ expectedReports){
+ const channelName = `${testName.replace(/[ ;"=]/g,"-")}_to_${host.name}_${coop.replace(/[ ;"=]/g,"-")}_${coep}`;
+ promise_test(async t => {
+ await reportingTest( (resolve) => {
+ coop_coep_test(t, host, coop, coep, channelName,
+ hasOpener, undefined /* openerDOMAccess */, resolve);
+ }, channelName, expectedReports);
+ }, `coop reporting test ${channelName}`);
+}
+
+// Run an array of reporting tests then verify there's no reports that were not
+// expected.
+// Tests' elements contain: host, coop, coep, hasOpener, expectedReports.
+// See isObjectAsExpected for explanations regarding the matching behavior.
+function runCoopReportingTest(testName, tests){
+ tests.forEach( test => {
+ coopCoepReportingTest(testName, ...test);
+ });
+ verifyRemainingReports();
+}
+
+const reportEndpoint = {
+ name: "coop-report-endpoint",
+ reports: []
+}
+const reportOnlyEndpoint = {
+ name: "coop-report-only-endpoint",
+ reports: []
+}
+const popupReportEndpoint = {
+ name: "coop-popup-report-endpoint",
+ reports: []
+}
+const popupReportOnlyEndpoint = {
+ name: "coop-popup-report-only-endpoint",
+ reports: []
+}
+const redirectReportEndpoint = {
+ name: "coop-redirect-report-endpoint",
+ reports: []
+}
+const redirectReportOnlyEndpoint = {
+ name: "coop-redirect-report-only-endpoint",
+ reports: []
+}
+
+const reportEndpoints = [
+ reportEndpoint,
+ reportOnlyEndpoint,
+ popupReportEndpoint,
+ popupReportOnlyEndpoint,
+ redirectReportEndpoint,
+ redirectReportOnlyEndpoint
+]
+
+function verifyRemainingReports() {
+ promise_test( async t => {
+ await Promise.all(Array.from(reportEndpoints, (endpoint) => {
+ return new Promise( async (resolve, reject) => {
+ await pollReports(endpoint);
+ if (endpoint.reports.length != 0)
+ reject( `${endpoint.name} not empty`);
+ resolve();
+ });
+ }));
+ }, "verify remaining reports");
+}