summaryrefslogtreecommitdiffstats
path: root/testing/web-platform/tests/reporting
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 01:47:29 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 01:47:29 +0000
commit0ebf5bdf043a27fd3dfb7f92e0cb63d88954c44d (patch)
treea31f07c9bcca9d56ce61e9a1ffd30ef350d513aa /testing/web-platform/tests/reporting
parentInitial commit. (diff)
downloadfirefox-esr-0ebf5bdf043a27fd3dfb7f92e0cb63d88954c44d.tar.xz
firefox-esr-0ebf5bdf043a27fd3dfb7f92e0cb63d88954c44d.zip
Adding upstream version 115.8.0esr.upstream/115.8.0esr
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'testing/web-platform/tests/reporting')
-rw-r--r--testing/web-platform/tests/reporting/META.yml5
-rw-r--r--testing/web-platform/tests/reporting/README.md19
-rw-r--r--testing/web-platform/tests/reporting/bufferSize.html27
-rw-r--r--testing/web-platform/tests/reporting/cross-origin-report-no-credentials.https.sub.html38
-rw-r--r--testing/web-platform/tests/reporting/cross-origin-report-no-credentials.https.sub.html.sub.headers2
-rw-r--r--testing/web-platform/tests/reporting/cross-origin-reports-isolated.https.sub.html45
-rw-r--r--testing/web-platform/tests/reporting/cross-origin-reports-isolated.https.sub.html.sub.headers2
-rw-r--r--testing/web-platform/tests/reporting/cross-origin-same-site-credentials.https.sub.html54
-rw-r--r--testing/web-platform/tests/reporting/disconnect.html26
-rw-r--r--testing/web-platform/tests/reporting/document-reporting-bypass-report-to.https.sub.html41
-rw-r--r--testing/web-platform/tests/reporting/document-reporting-bypass-report-to.https.sub.html.sub.headers3
-rw-r--r--testing/web-platform/tests/reporting/document-reporting-default-endpoint.https.sub.html35
-rw-r--r--testing/web-platform/tests/reporting/document-reporting-default-endpoint.https.sub.html.sub.headers1
-rw-r--r--testing/web-platform/tests/reporting/document-reporting-destroy-after-document-close.https.sub.html39
-rw-r--r--testing/web-platform/tests/reporting/document-reporting-named-endpoints.https.sub.html54
-rw-r--r--testing/web-platform/tests/reporting/document-reporting-named-endpoints.https.sub.html.sub.headers4
-rw-r--r--testing/web-platform/tests/reporting/document-reporting-not-batch-different-document.https.html35
-rw-r--r--testing/web-platform/tests/reporting/document-reporting-override-endpoint.https.sub.html41
-rw-r--r--testing/web-platform/tests/reporting/document-reporting-override-endpoint.https.sub.html.sub.headers3
-rw-r--r--testing/web-platform/tests/reporting/document-reporting-path-absolute.https.sub.html37
-rw-r--r--testing/web-platform/tests/reporting/document-reporting-path-absolute.https.sub.html.sub.headers1
-rw-r--r--testing/web-platform/tests/reporting/generateTestReport-honors-endpoint.https.sub.html51
-rw-r--r--testing/web-platform/tests/reporting/generateTestReport-honors-endpoint.https.sub.html.sub.headers1
-rw-r--r--testing/web-platform/tests/reporting/generateTestReport.html35
-rw-r--r--testing/web-platform/tests/reporting/idlharness.any.js14
-rw-r--r--testing/web-platform/tests/reporting/nestedReport.html30
-rw-r--r--testing/web-platform/tests/reporting/order.html32
-rw-r--r--testing/web-platform/tests/reporting/reporting-api-honors-limits.https.sub.html70
-rw-r--r--testing/web-platform/tests/reporting/reporting-api-honors-limits.https.sub.html.sub.headers7
-rw-r--r--testing/web-platform/tests/reporting/reporting-isolated-across-navigations.https.sub.html56
-rw-r--r--testing/web-platform/tests/reporting/resources/README.md75
-rw-r--r--testing/web-platform/tests/reporting/resources/csp-error.https.sub.html19
-rw-r--r--testing/web-platform/tests/reporting/resources/csp-error.https.sub.html.sub.headers2
-rw-r--r--testing/web-platform/tests/reporting/resources/fail.pngbin0 -> 759 bytes
-rw-r--r--testing/web-platform/tests/reporting/resources/first-csp-report.https.sub.html22
-rw-r--r--testing/web-platform/tests/reporting/resources/first-csp-report.https.sub.html.sub.headers2
-rw-r--r--testing/web-platform/tests/reporting/resources/generate-csp-report.https.sub.html4
-rw-r--r--testing/web-platform/tests/reporting/resources/generate-csp-report.https.sub.html.sub.headers1
-rw-r--r--testing/web-platform/tests/reporting/resources/generate-report-once.py34
-rw-r--r--testing/web-platform/tests/reporting/resources/generate-report.https.sub.html6
-rw-r--r--testing/web-platform/tests/reporting/resources/middle-frame.https.sub.html15
-rw-r--r--testing/web-platform/tests/reporting/resources/report-helper.js38
-rw-r--r--testing/web-platform/tests/reporting/resources/report.py143
-rw-r--r--testing/web-platform/tests/reporting/resources/same-origin-report.https.sub.html15
-rw-r--r--testing/web-platform/tests/reporting/resources/same-origin-report.https.sub.html.sub.headers2
-rw-r--r--testing/web-platform/tests/reporting/resources/second-csp-report.https.sub.html24
-rw-r--r--testing/web-platform/tests/reporting/resources/second-csp-report.https.sub.html.sub.headers2
-rw-r--r--testing/web-platform/tests/reporting/same-origin-cross-site-credentials.https.sub.html53
-rw-r--r--testing/web-platform/tests/reporting/same-origin-report-credentials.https.sub.html39
-rw-r--r--testing/web-platform/tests/reporting/same-origin-report-credentials.https.sub.html.sub.headers2
-rw-r--r--testing/web-platform/tests/reporting/same-origin-same-site-credentials.https.sub.html53
51 files changed, 1359 insertions, 0 deletions
diff --git a/testing/web-platform/tests/reporting/META.yml b/testing/web-platform/tests/reporting/META.yml
new file mode 100644
index 0000000000..70f3136dd3
--- /dev/null
+++ b/testing/web-platform/tests/reporting/META.yml
@@ -0,0 +1,5 @@
+spec: https://w3c.github.io/reporting/
+suggested_reviewers:
+ - clelland
+ - dcreager
+ - igrigorik
diff --git a/testing/web-platform/tests/reporting/README.md b/testing/web-platform/tests/reporting/README.md
new file mode 100644
index 0000000000..52f08265c5
--- /dev/null
+++ b/testing/web-platform/tests/reporting/README.md
@@ -0,0 +1,19 @@
+Tests for the [Reporting API](https://w3c.github.io/reporting/).
+
+The tests in this directory validate the generic functionaity of the Reporting
+API. Since reports are not actually generated by that specification, these tests
+occasionally make use of other integrations, like CSP or Permissions Policy.
+
+## Testing integration with the Reporting API
+
+More comprehensive tests for other specifications' generated reports should be
+in those specs' respective directories.
+
+There are two general methods of testing reporting integration:
+
+* The simpler is with the ReportingObserver interface, generating reports
+ within a document and reading them from script running in that document.
+* For reports which cannot be observed from a document, there is a reporting
+ collector provided which can receive reports sent over HTTP and then serve
+ them in response to queries from the test script. See resources/README.md for
+ details.
diff --git a/testing/web-platform/tests/reporting/bufferSize.html b/testing/web-platform/tests/reporting/bufferSize.html
new file mode 100644
index 0000000000..0e50526772
--- /dev/null
+++ b/testing/web-platform/tests/reporting/bufferSize.html
@@ -0,0 +1,27 @@
+<!DOCTYPE HTML>
+<meta charset=utf-8>
+<title>Reporting: Buffer size</title>
+<link rel="author" title="Paul Meyer" href="paulmeyer@chromium.org">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script>
+ // Test the buffer size (100) of ReportingObserver.
+ promise_test(async function(test) {
+ for (let i = 0; i != 110; ++i)
+ await test_driver.generate_test_report("" + i);
+
+ let reports = await new Promise(resolve => {
+ let observer = new ReportingObserver(resolve, {buffered:true});
+ observer.observe();
+ });
+
+ // Only (the most recent) 100 reports should be observed, even though
+ // 110 were buffered.
+ assert_equals(reports.length, 100);
+ for (let i = 0; i != 100; ++i) {
+ assert_equals(reports[i].body.message, "" + (i + 10));
+ }
+ }, "Buffer size");
+</script>
diff --git a/testing/web-platform/tests/reporting/cross-origin-report-no-credentials.https.sub.html b/testing/web-platform/tests/reporting/cross-origin-report-no-credentials.https.sub.html
new file mode 100644
index 0000000000..f10d4cef3e
--- /dev/null
+++ b/testing/web-platform/tests/reporting/cross-origin-report-no-credentials.https.sub.html
@@ -0,0 +1,38 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test that reports are sent without credentials to cross-origin endpoints</title>
+ <script src='/resources/testharness.js'></script>
+ <script src='/resources/testharnessreport.js'></script>
+ <script src='resources/report-helper.js'></script>
+</head>
+<body>
+ <script>
+ const base_url = `${location.protocol}//${location.host}`;
+ const endpoint = `${base_url}/reporting/resources/report.py`;
+ const id = 'fe5ca189-269a-4e74-a4dd-d7a3b33139d5';
+
+ promise_test(async t => {
+ // Set credentials, and set up test to clear them afterwards.
+ await fetch('/cookies/resources/set-cookie.py?name=report&path=%2F', {mode: 'no-cors', credentials: 'include', cache: 'no-store'});
+ t.add_cleanup(() => fetch("/cookies/resources/set.py?report=; path=%2F; expires=Thu, 01 Jan 1970 00:00:01 GMT"));
+
+ // Trigger a CSP error.
+ await new Promise(resolve => {
+ const img = document.createElement('img');
+ img.src = "/reporting/resources/fail.png";
+ img.addEventListener('error', resolve);
+ document.body.appendChild(img);
+ });
+
+ // Wait for report to be received.
+ const reports = await pollReports(endpoint, id);
+ checkReportExists(reports, 'csp-violation', location.href);
+
+ // Validate that credentials were not sent to cross-origin endpoint.
+ const cookies = await pollCookies(endpoint, id);
+ assert_equals(Object.keys(cookies).length, 0, "Credentials were absent from report");
+ }, "Reporting endpoints did not receive credentials.");
+ </script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/reporting/cross-origin-report-no-credentials.https.sub.html.sub.headers b/testing/web-platform/tests/reporting/cross-origin-report-no-credentials.https.sub.html.sub.headers
new file mode 100644
index 0000000000..24eaf19fec
--- /dev/null
+++ b/testing/web-platform/tests/reporting/cross-origin-report-no-credentials.https.sub.html.sub.headers
@@ -0,0 +1,2 @@
+Reporting-Endpoints: csp-endpoint="https://{{domains[www1]}}:{{ports[https][0]}}/reporting/resources/report.py?reportID=fe5ca189-269a-4e74-a4dd-d7a3b33139d5"
+Content-Security-Policy: script-src 'self' 'unsafe-inline'; img-src 'none'; report-to csp-endpoint
diff --git a/testing/web-platform/tests/reporting/cross-origin-reports-isolated.https.sub.html b/testing/web-platform/tests/reporting/cross-origin-reports-isolated.https.sub.html
new file mode 100644
index 0000000000..4e9cb1eb01
--- /dev/null
+++ b/testing/web-platform/tests/reporting/cross-origin-reports-isolated.https.sub.html
@@ -0,0 +1,45 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test that reports from different origins are not sent together</title>
+ <script src='/resources/testharness.js'></script>
+ <script src='/resources/testharnessreport.js'></script>
+ <script src='resources/report-helper.js'></script>
+</head>
+<body>
+ <script>
+ const base_url = `${location.protocol}//${location.host}`;
+ const endpoint = `${base_url}/reporting/resources/report.py`;
+ const id = 'd0d517bf-891b-457a-b970-8b2b2c81a0bf';
+
+ promise_test(async t => {
+
+ // Attach a cross-origin iframe which should post back here immediately
+ // before generating a CSP error. That error should be reported to the
+ // same endpoint that this frame reports to.
+ await new Promise(resolve => {
+ const iframe = document.createElement('iframe');
+ iframe.src = "https://{{domains[www]}}:{{ports[https][0]}}/reporting/resources/csp-error.https.sub.html";
+ addEventListener('message', resolve);
+ document.body.appendChild(iframe);
+ });
+
+ // Trigger a CSP error and report in this frame as well.
+ await new Promise(resolve => {
+ const img = document.createElement('img');
+ img.src = "/reporting/resources/fail.png";
+ img.addEventListener('error', resolve);
+ document.body.appendChild(img);
+ });
+
+ // Wait for 2 reports to be received.
+ const reports = await pollReports(endpoint, id, 2);
+ assert_equals(reports.length, 2);
+
+ // Validate that reports were sent in separate requests.
+ const request_count = await pollNumResults(endpoint, id);
+ assert_equals(request_count, 2);
+ }, "Reports were sent in two requests.");
+ </script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/reporting/cross-origin-reports-isolated.https.sub.html.sub.headers b/testing/web-platform/tests/reporting/cross-origin-reports-isolated.https.sub.html.sub.headers
new file mode 100644
index 0000000000..00b60a2d0c
--- /dev/null
+++ b/testing/web-platform/tests/reporting/cross-origin-reports-isolated.https.sub.html.sub.headers
@@ -0,0 +1,2 @@
+Reporting-Endpoints: csp-endpoint="https://{{domains[www]}}:{{ports[https][0]}}/reporting/resources/report.py?reportID=d0d517bf-891b-457a-b970-8b2b2c81a0bf"
+Content-Security-Policy: script-src 'self' 'unsafe-inline'; img-src 'none'; report-to csp-endpoint
diff --git a/testing/web-platform/tests/reporting/cross-origin-same-site-credentials.https.sub.html b/testing/web-platform/tests/reporting/cross-origin-same-site-credentials.https.sub.html
new file mode 100644
index 0000000000..2f3f5fefca
--- /dev/null
+++ b/testing/web-platform/tests/reporting/cross-origin-same-site-credentials.https.sub.html
@@ -0,0 +1,54 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test that credentials are sent properly in a cross-origin but same-site nested context</title>
+ <script src='/resources/testharness.js'></script>
+ <script src='/resources/testharnessreport.js'></script>
+ <script src='resources/report-helper.js'></script>
+</head>
+<body>
+ <script>
+ const base_url = `${location.protocol}//${location.host}`;
+ const endpoint = `${base_url}/reporting/resources/report.py`;
+ const id = 'd0d517bf-891b-457a-b970-8b2b2c81a0bf';
+
+ promise_test(async t => {
+ // If this is not run from the expected origin, then the A->A->www.A frame embedding will not be correct,
+ // and the cookies set in the top-level page will never be returned with the reports.
+ assert_true(location.href.startsWith("https://{{hosts[][]}}:{{ports[https][0]}}/"),
+ "Test running on unexpected origin; subsequent assertions will fail.");
+
+ // Set credentials, and set up test to clear them afterwards. Cookies are set with the Domain
+ // attribute, so that they may be sent to same-site resources.
+ await fetch('/cookies/resources/setSameSiteDomain.py?reporting', {mode: 'no-cors', credentials: 'include', cache: 'no-store'});
+ t.add_cleanup(() => fetch("/cookies/resources/dropSameSite.py", {mode: 'no-cors', credentials: 'include', cache: 'no-store'}));
+
+ // Insert a same-origin frame, which will then frame a same-site but cross-origin page to
+ // trigger a CSP error.
+ const frame = document.createElement('iframe');
+ frame.src = "https://{{hosts[][]}}:{{ports[https][0]}}/reporting/resources/middle-frame.https.sub.html?host={{hosts[][www]}}";
+
+ // Wait for the inner frame to signal that the report has been generated.
+ await new Promise(resolve => {
+ window.addEventListener('message', ev => {
+ if (ev.data === "done")
+ resolve(ev.data);
+ });
+ document.body.appendChild(frame);
+ });
+
+ const reports = await pollReports(endpoint, id);
+ checkReportExists(reports, 'csp-violation', "https://{{hosts[][www]}}:{{ports[https][0]}}/reporting/resources/same-origin-report.https.sub.html");
+
+ // All credentials set at the top-level should be received.
+ const cookies = await pollCookies(endpoint, id);
+ assert_equals(cookies.samesite_none, "[samesite_none=reporting]", "Credential value was correct");
+ assert_equals(cookies.samesite_unspecified, "[samesite_unspecified=reporting]", "Credential value was correct");
+ assert_equals(cookies.samesite_lax, "[samesite_lax=reporting]", "Credential value was correct");
+ assert_equals(cookies.samesite_strict, "[samesite_strict=reporting]", "Credential value was correct");
+ assert_equals(Object.keys(cookies).length, 4, "No additional cookies were received");
+
+ }, "Reporting endpoints received credentials.");
+ </script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/reporting/disconnect.html b/testing/web-platform/tests/reporting/disconnect.html
new file mode 100644
index 0000000000..12d33db8ff
--- /dev/null
+++ b/testing/web-platform/tests/reporting/disconnect.html
@@ -0,0 +1,26 @@
+<!DOCTYPE HTML>
+<meta charset=utf-8>
+<title>Reporting: Disconnect</title>
+<link rel="author" title="Paul Meyer" href="paulmeyer@chromium.org">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script>
+promise_test(async test => {
+ let observer;
+ const reportsPromise = new Promise(resolve => {
+ observer = new ReportingObserver(resolve);
+ observer.observe();
+ });
+
+ // The observer should still receive this report even though disconnect()
+ // is called immediately afterwards.
+ await test_driver.generate_test_report("Test message.")
+ .then(() => { observer.disconnect(); });
+
+ const reports = await reportsPromise;
+ assert_equals(reports.length, 1);
+ assert_equals(reports[0].body.message, "Test message.");
+ }, "Disconnect");
+</script>
diff --git a/testing/web-platform/tests/reporting/document-reporting-bypass-report-to.https.sub.html b/testing/web-platform/tests/reporting/document-reporting-bypass-report-to.https.sub.html
new file mode 100644
index 0000000000..394bc9e40a
--- /dev/null
+++ b/testing/web-platform/tests/reporting/document-reporting-bypass-report-to.https.sub.html
@@ -0,0 +1,41 @@
+<!DOCTYPE HTML>
+<html>
+
+<head>
+ <title>Test that reports ignore Report-To header when Reporting-Endpoints is configured</title>
+ <script src='/resources/testharness.js'></script>
+ <script src='/resources/testharnessreport.js'></script>
+ <script src='resources/report-helper.js'></script>
+</head>
+
+<body>
+ <script>
+ promise_test(async t => {
+ return new Promise(resolve => {
+ new ReportingObserver((reports, observer) => resolve(reports),
+ { types: ['document-policy-violation'] }).observe();
+ }).then((reports) => {
+ assert_equals(reports[0].type, 'document-policy-violation');
+ })
+ }, "document policy violation observed");
+ </script>
+ <script>document.write("This should be written into the document");</script>
+ <script>
+ const base_url = `${location.protocol}//${location.host}`;
+ const endpoint = `${base_url}/reporting/resources/report.py`;
+ const report_to_id = 'caddb022-90ea-48e8-a675-4cebaf7e8388';
+ const reporting_endpoints_id = '6c2131d0-1e9b-4ee8-a196-952f2ae4ae97';
+ promise_test(async t => {
+ await wait(3000);
+ // Verify no reports sent to Report-To endpoint
+ let reports = await pollReports(endpoint, report_to_id);
+ assert_equals(reports.length, 0);
+ // Verify report is received on Reporting-Endpoints endpoint
+ reports = await pollReports(endpoint, reporting_endpoints_id);
+ checkReportExists(reports, 'document-policy-violation', location.href);
+ }, "Only the Reporting-Endpoints configured endpoint received reports.");
+ </script>
+
+</body>
+
+</html>
diff --git a/testing/web-platform/tests/reporting/document-reporting-bypass-report-to.https.sub.html.sub.headers b/testing/web-platform/tests/reporting/document-reporting-bypass-report-to.https.sub.html.sub.headers
new file mode 100644
index 0000000000..b2a3d20f48
--- /dev/null
+++ b/testing/web-platform/tests/reporting/document-reporting-bypass-report-to.https.sub.html.sub.headers
@@ -0,0 +1,3 @@
+Reporting-Endpoints: group1="https://{{host}}:{{ports[https][0]}}/reporting/resources/report.py?reportID=6c2131d0-1e9b-4ee8-a196-952f2ae4ae97"
+Report-To: { "group": "group1", "max_age": 10886400, "endpoints": [{ "url": "/reporting/resources/report.py?reportID=caddb022-90ea-48e8-a675-4cebaf7e8388" }] }
+Document-Policy-Report-Only: document-write=?0;report-to=group1
diff --git a/testing/web-platform/tests/reporting/document-reporting-default-endpoint.https.sub.html b/testing/web-platform/tests/reporting/document-reporting-default-endpoint.https.sub.html
new file mode 100644
index 0000000000..f1951e3469
--- /dev/null
+++ b/testing/web-platform/tests/reporting/document-reporting-default-endpoint.https.sub.html
@@ -0,0 +1,35 @@
+<!DOCTYPE HTML>
+<meta charset=utf-8>
+<title>Test that document level reports are sent to default endpoint</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src='resources/report-helper.js'></script>
+<p id="error">No error</p>
+<script>
+ async_test(function (test) {
+ var observer = new ReportingObserver(function (reports) {
+ test.step(function () {
+ assert_equals(reports.length, 1);
+ assert_equals(reports[0].type, "deprecation");
+ });
+ test.done();
+ });
+ observer.observe();
+ }, "report generated");
+</script>
+<script>webkitRequestAnimationFrame(() => {});</script>
+<script>
+ const base_url = `${location.protocol}//${location.host}`;
+ const endpoint = `${base_url}/reporting/resources/report.py`;
+ const id = '46ecac28-6d27-4763-a692-bcc588054716';
+ promise_test(async t => {
+ await wait(3000);
+ const reports = await pollReports(endpoint, id);
+ checkReportExists(reports, 'deprecation', location.href);
+ }, "Reporting-Endpoints defined endpoint received reports.");
+</script>
+</body>
+
+</html>
diff --git a/testing/web-platform/tests/reporting/document-reporting-default-endpoint.https.sub.html.sub.headers b/testing/web-platform/tests/reporting/document-reporting-default-endpoint.https.sub.html.sub.headers
new file mode 100644
index 0000000000..e374d79be0
--- /dev/null
+++ b/testing/web-platform/tests/reporting/document-reporting-default-endpoint.https.sub.html.sub.headers
@@ -0,0 +1 @@
+Reporting-Endpoints: default="https://{{host}}:{{ports[https][0]}}/reporting/resources/report.py?reportID=46ecac28-6d27-4763-a692-bcc588054716"
diff --git a/testing/web-platform/tests/reporting/document-reporting-destroy-after-document-close.https.sub.html b/testing/web-platform/tests/reporting/document-reporting-destroy-after-document-close.https.sub.html
new file mode 100644
index 0000000000..e6ec91ade3
--- /dev/null
+++ b/testing/web-platform/tests/reporting/document-reporting-destroy-after-document-close.https.sub.html
@@ -0,0 +1,39 @@
+<!DOCTYPE HTML>
+<html>
+
+<head>
+ <title>Test that reports are not sent without Reporting-Endpoints header, with previous header set on same URL</title>
+ <script src="/common/utils.js"></script>
+ <script src='/resources/testharness.js'></script>
+ <script src='/resources/testharnessreport.js'></script>
+ <script src='resources/report-helper.js'></script>
+</head>
+
+<body>
+ <iframe name="test"></iframe>
+ <script>
+ const base_url = `${location.protocol}//${location.host}`;
+ const endpoint = `${base_url}/reporting/resources/report.py`;
+ const report_id = token();
+ const document_url =
+ `resources/generate-report-once.py?reportID=${report_id}`;
+ promise_test(async t => {
+ // Load a document that generates report into iframe. Server should return
+ // Reporting-Endpoints header.
+ const w = window.open(document_url, "test");
+ let reports = await pollReports(endpoint, report_id);
+ // Verify that reporting is configured on the document.
+ assert_equals(reports.length, 1);
+ // reload opened window. This time server will not return
+ // Reporting-Endpoints header.
+ w.location.reload();
+ reports = await pollReports(endpoint, report_id);
+ // Verify no reports are sent this time.
+ assert_equals(reports.length, 0);
+
+ }, "No more reports received after navigation to same document without endpoint header");
+ </script>
+
+</body>
+
+</html>
diff --git a/testing/web-platform/tests/reporting/document-reporting-named-endpoints.https.sub.html b/testing/web-platform/tests/reporting/document-reporting-named-endpoints.https.sub.html
new file mode 100644
index 0000000000..c24601147a
--- /dev/null
+++ b/testing/web-platform/tests/reporting/document-reporting-named-endpoints.https.sub.html
@@ -0,0 +1,54 @@
+<!DOCTYPE HTML>
+<html>
+
+<head>
+ <title>Test that reports are sent to multiple named endpoints</title>
+ <script src='/resources/testharness.js'></script>
+ <script src='/resources/testharnessreport.js'></script>
+ <script src='resources/report-helper.js'></script>
+</head>
+
+<body>
+ <script>
+ const t = async_test("Test that image does not load");
+ async_test(function (t) {
+ const observer = new ReportingObserver((reports, observer) => {
+ t.step(() => {
+ assert_equals(reports[0].type, 'csp-violation');
+ });
+ t.done();
+ }, { types: ['csp-violation'] });
+ observer.observe();
+ }, "csp violation report observed");
+
+ promise_test(async t => {
+ return new Promise(resolve => {
+ new ReportingObserver((reports, observer) => resolve(reports),
+ { types: ['document-policy-violation'] }).observe();
+ }).then((reports) => {
+ assert_equals(reports[0].type, 'document-policy-violation');
+ })
+ }, "document policy violation observed");
+ </script>
+ <img src='/reporting/resources/fail.png' onload='t.unreached_func("The image should not have loaded");'
+ onerror='t.done();'>
+ <script>document.write("This should be written into the document");</script>
+ <script>
+ const base_url = `${location.protocol}//${location.host}`;
+ const endpoint = `${base_url}/reporting/resources/report.py`;
+ const group1_id = '0d334af1-1c5c-4e59-9079-065131ff2a45';
+ const group2_id = '09c1a265-5fc7-4c49-b35c-32078c2d0c19';
+ promise_test(async t => {
+ await wait(3000);
+ // Verify CSP reports are sent to configured endpoint.
+ const csp_reports = await pollReports(endpoint, group1_id);
+ checkReportExists(csp_reports, 'csp-violation', location.href);
+ // Verify Document Policy reports are sent to configured endpoint.
+ const dp_reports = await pollReports(endpoint, group2_id);
+ checkReportExists(dp_reports, 'document-policy-violation', location.href);
+ }, "Reporting endpoints received reports.");
+ </script>
+
+</body>
+
+</html>
diff --git a/testing/web-platform/tests/reporting/document-reporting-named-endpoints.https.sub.html.sub.headers b/testing/web-platform/tests/reporting/document-reporting-named-endpoints.https.sub.html.sub.headers
new file mode 100644
index 0000000000..2d5a308db2
--- /dev/null
+++ b/testing/web-platform/tests/reporting/document-reporting-named-endpoints.https.sub.html.sub.headers
@@ -0,0 +1,4 @@
+Reporting-Endpoints: group1="https://{{host}}:{{ports[https][0]}}/reporting/resources/report.py?reportID=0d334af1-1c5c-4e59-9079-065131ff2a45"
+Reporting-Endpoints: group2="https://{{host}}:{{ports[https][0]}}/reporting/resources/report.py?reportID=09c1a265-5fc7-4c49-b35c-32078c2d0c19"
+Content-Security-Policy: script-src 'self' 'unsafe-inline'; img-src 'none'; report-to group1
+Document-Policy-Report-Only: document-write=?0;report-to=group2
diff --git a/testing/web-platform/tests/reporting/document-reporting-not-batch-different-document.https.html b/testing/web-platform/tests/reporting/document-reporting-not-batch-different-document.https.html
new file mode 100644
index 0000000000..e124bd7fbd
--- /dev/null
+++ b/testing/web-platform/tests/reporting/document-reporting-not-batch-different-document.https.html
@@ -0,0 +1,35 @@
+<!DOCTYPE HTML>
+<html>
+
+<head>
+ <title>Test that reports are sent to multiple named endpoints</title>
+ <script src='/resources/testharness.js'></script>
+ <script src='/resources/testharnessreport.js'></script>
+ <script src='resources/report-helper.js'></script>
+</head>
+
+<body>
+ <iframe name="report1"></iframe>
+ <iframe name="report2"></iframe>
+ <script>
+ const base_url = `${location.protocol}//${location.host}`;
+ const endpoint = `${base_url}/reporting/resources/report.py`;
+ const report_id = '204d2fb2-018b-4e35-964c-5e298e89d4e2';
+ promise_test(async t => {
+ const w = window.open(`resources/generate-report.https.sub.html?pipe=header(Reporting-Endpoints,default="/reporting/resources/report.py?reportID=${report_id}")`, "report1");
+ const w2 = window.open(`resources/generate-csp-report.https.sub.html?pipe=header(Reporting-Endpoints,default="/reporting/resources/report.py?reportID=${report_id}")`, "report2");
+ await wait(3000);
+ // Verify that each iframe generated and sent one report.
+ const reports = await pollReports(endpoint, report_id);
+ assert_equals(reports.length, 2, "Number of reports");
+ checkReportExists(reports, 'deprecation', w.location.href);
+ checkReportExists(reports, 'csp-violation', w2.location.href);
+ const request_count = await pollNumResults(endpoint, report_id);
+ // Verify that requests are sent separately.
+ assert_equals(request_count, 2, "Count of requests");
+ }, "Reports are not batched for same url in different document.");
+ </script>
+
+</body>
+
+</html>
diff --git a/testing/web-platform/tests/reporting/document-reporting-override-endpoint.https.sub.html b/testing/web-platform/tests/reporting/document-reporting-override-endpoint.https.sub.html
new file mode 100644
index 0000000000..9264786093
--- /dev/null
+++ b/testing/web-platform/tests/reporting/document-reporting-override-endpoint.https.sub.html
@@ -0,0 +1,41 @@
+<!DOCTYPE HTML>
+<html>
+
+<head>
+ <title>Test that Reporting-Endpoints header endpoint with same name override previous value</title>
+ <script src='/resources/testharness.js'></script>
+ <script src='/resources/testharnessreport.js'></script>
+ <script src='resources/report-helper.js'></script>
+</head>
+
+<body>
+ <script>
+ promise_test(async t => {
+ return new Promise(resolve => {
+ new ReportingObserver((reports, observer) => resolve(reports),
+ { types: ['document-policy-violation'] }).observe();
+ }).then((reports) => {
+ assert_equals(reports[0].type, 'document-policy-violation');
+ })
+ }, "document policy violation observed");
+ </script>
+ <script>document.write("This should be written into the document");</script>
+ <script>
+ const base_url = `${location.protocol}//${location.host}`;
+ const endpoint = `${base_url}/reporting/resources/report.py`;
+ const first_group1_id = 'b523d7f5-28f0-4be6-9460-e163ee9b4ab8';
+ const second_group1_id = '03e4474d-768c-42f2-8e17-39aa95b309e3';
+ promise_test(async t => {
+ await wait(3000);
+ // Verify that no reports are sent to old header endpoint.
+ let reports = await pollReports(endpoint, first_group1_id);
+ assert_equals(reports.length, 0);
+ // Verify that reports are sent to the new header endpoint.
+ reports = await pollReports(endpoint, second_group1_id);
+ checkReportExists(reports, 'document-policy-violation', location.href);
+ }, "Only the second reporting endpoint received reports.");
+ </script>
+
+</body>
+
+</html>
diff --git a/testing/web-platform/tests/reporting/document-reporting-override-endpoint.https.sub.html.sub.headers b/testing/web-platform/tests/reporting/document-reporting-override-endpoint.https.sub.html.sub.headers
new file mode 100644
index 0000000000..46954f4d5c
--- /dev/null
+++ b/testing/web-platform/tests/reporting/document-reporting-override-endpoint.https.sub.html.sub.headers
@@ -0,0 +1,3 @@
+Reporting-Endpoints: group1="https://{{host}}:{{ports[https][0]}}/reporting/resources/report.py?reportID=b523d7f5-28f0-4be6-9460-e163ee9b4ab8"
+Reporting-Endpoints: group1="https://{{host}}:{{ports[https][0]}}/reporting/resources/report.py?reportID=03e4474d-768c-42f2-8e17-39aa95b309e3"
+Document-Policy-Report-Only: document-write=?0;report-to=group1
diff --git a/testing/web-platform/tests/reporting/document-reporting-path-absolute.https.sub.html b/testing/web-platform/tests/reporting/document-reporting-path-absolute.https.sub.html
new file mode 100644
index 0000000000..48be010a00
--- /dev/null
+++ b/testing/web-platform/tests/reporting/document-reporting-path-absolute.https.sub.html
@@ -0,0 +1,37 @@
+<!DOCTYPE HTML>
+<meta charset=utf-8>
+<title>Test that Reporting-Endpoints report received for absolute path endpoint.</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src='resources/report-helper.js'></script>
+<p id="error">No error</p>
+<script>
+ async_test(function (test) {
+ var observer = new ReportingObserver(function (reports) {
+ test.step(function () {
+ // Reports should be received in the same order that they were
+ // generated.
+ assert_equals(reports.length, 1);
+ assert_equals(reports[0].type, "deprecation");
+ });
+ test.done();
+ });
+ observer.observe();
+ }, "report generated");
+</script>
+<script>window.webkitCancelAnimationFrame(() => {});</script>
+<script>
+ const base_url = `${location.protocol}//${location.host}`;
+ const endpoint = `${base_url}/reporting/resources/report.py`;
+ const id = '8106c1d6-55f7-4c82-a8e1-fabc59f890f8';
+ promise_test(async t => {
+ await wait(3000);
+ const reports = await pollReports(endpoint, id);
+ checkReportExists(reports, 'deprecation', location.href);
+ }, "Reporting-Endpoints defined endpoint received reports.");
+</script>
+</body>
+
+</html>
diff --git a/testing/web-platform/tests/reporting/document-reporting-path-absolute.https.sub.html.sub.headers b/testing/web-platform/tests/reporting/document-reporting-path-absolute.https.sub.html.sub.headers
new file mode 100644
index 0000000000..547cd6a588
--- /dev/null
+++ b/testing/web-platform/tests/reporting/document-reporting-path-absolute.https.sub.html.sub.headers
@@ -0,0 +1 @@
+Reporting-Endpoints: default="/reporting/resources/report.py?reportID=8106c1d6-55f7-4c82-a8e1-fabc59f890f8"
diff --git a/testing/web-platform/tests/reporting/generateTestReport-honors-endpoint.https.sub.html b/testing/web-platform/tests/reporting/generateTestReport-honors-endpoint.https.sub.html
new file mode 100644
index 0000000000..f04f5e00f9
--- /dev/null
+++ b/testing/web-platform/tests/reporting/generateTestReport-honors-endpoint.https.sub.html
@@ -0,0 +1,51 @@
+<!doctype html>
+<html>
+<head>
+ <title>Test that the reporting-api generate_test_report feature honors endpoints</title>
+ <link rel="author" title="Brent Fulgham" href="bfulgham@apple.com">
+ <script src='/resources/testharness.js'></script>
+ <script src='/resources/testharnessreport.js'></script>
+ <script src="/resources/testdriver.js"></script>
+ <script src="/resources/testdriver-vendor.js"></script>
+ <script src='resources/report-helper.js'></script>
+</head>
+<body>
+ <script>
+ const base_url = `${location.protocol}//${location.host}`;
+ const endpoint = `${base_url}/reporting/resources/report.py`;
+ const id = '41534b09-65b2-498a-9fd3-104281ed63ce';
+
+ function checkReportIsValid(reports, type, url) {
+ for (const report of reports) {
+ if (report.type !== type) continue;
+ if (report.url.endsWith("reporting/generateTestReport-honors-endpoint.https.sub.html"))
+ return true;
+ }
+ assert_unreached(`A report of ${type} from ${url} was not found.`);
+ }
+
+ async_test(function(test) {
+ var observer = new ReportingObserver(function(reports) {
+ test.step(function() {
+ assert_equals(reports.length, 1);
+ // Ensure that the contents of the report are valid.
+ assert_equals(reports[0].type, "test");
+ assert_true(reports[0].url.endsWith("reporting/generateTestReport-honors-endpoint.https.sub.html"));
+ assert_equals(reports[0].body.message, "Test message.");
+ });
+ test.done();
+ });
+ observer.observe();
+
+ // This should result in a "test" type report being generated and observed.
+ test_driver.generate_test_report("Test message.")
+ .catch(test.unreached_func('generate test report failed'));
+ }, "Generate Test Report");
+
+ promise_test(async t => {
+ const reports = await pollReports(endpoint, id);
+ checkReportIsValid(reports, 'test', location.href);
+ }, "Reporting-Endpoints target received the test report.");
+</script>
+</body>
+</html> \ No newline at end of file
diff --git a/testing/web-platform/tests/reporting/generateTestReport-honors-endpoint.https.sub.html.sub.headers b/testing/web-platform/tests/reporting/generateTestReport-honors-endpoint.https.sub.html.sub.headers
new file mode 100644
index 0000000000..7167d54d83
--- /dev/null
+++ b/testing/web-platform/tests/reporting/generateTestReport-honors-endpoint.https.sub.html.sub.headers
@@ -0,0 +1 @@
+Reporting-Endpoints: default="https://{{host}}:{{ports[https][0]}}/reporting/resources/report.py?reportID=41534b09-65b2-498a-9fd3-104281ed63ce"
diff --git a/testing/web-platform/tests/reporting/generateTestReport.html b/testing/web-platform/tests/reporting/generateTestReport.html
new file mode 100644
index 0000000000..1c6e7dc225
--- /dev/null
+++ b/testing/web-platform/tests/reporting/generateTestReport.html
@@ -0,0 +1,35 @@
+<!DOCTYPE html>
+<meta charset="utf-8" />
+<title>Reporting: Generate Test Report</title>
+<link rel="author" title="Paul Meyer" href="paulmeyer@chromium.org">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script>
+ // Test that the "generate_test_report" API works.
+ async_test(function(test) {
+ var observer = new ReportingObserver(function(reports) {
+ test.step(function() {
+ assert_equals(reports.length, 1);
+ // Ensure that the contents of the report are valid.
+ assert_equals(reports[0].type, "test");
+ assert_true(reports[0].url.endsWith("reporting/generateTestReport.html"));
+ assert_equals(reports[0].body.message, "Test message.");
+ // Ensure that the toJSON() call of the report are valid.
+ const reportJSON = reports[0].toJSON();
+ assert_equals(reports[0].type, reportJSON.type);
+ assert_equals(reports[0].url, reportJSON.url);
+ assert_equals(reports[0].body.message, reportJSON.body.message);
+ // Ensure that report can be successfully JSON serialized.
+ assert_equals(JSON.stringify(reports[0]), JSON.stringify(reportJSON));
+ });
+ test.done();
+ });
+ observer.observe();
+
+ // This should result in a "test" type report being generated and observed.
+ test_driver.generate_test_report("Test message.")
+ .catch(test.unreached_func('generate test report failed'));
+ }, "Generate Test Report");
+</script>
diff --git a/testing/web-platform/tests/reporting/idlharness.any.js b/testing/web-platform/tests/reporting/idlharness.any.js
new file mode 100644
index 0000000000..17cef81835
--- /dev/null
+++ b/testing/web-platform/tests/reporting/idlharness.any.js
@@ -0,0 +1,14 @@
+// META: script=/resources/WebIDLParser.js
+// META: script=/resources/idlharness.js
+
+'use strict';
+
+idl_test(
+ ['reporting'],
+ [],
+ idl_array => {
+ idl_array.add_objects({
+ // TODO: objects
+ });
+ }
+);
diff --git a/testing/web-platform/tests/reporting/nestedReport.html b/testing/web-platform/tests/reporting/nestedReport.html
new file mode 100644
index 0000000000..156338ee74
--- /dev/null
+++ b/testing/web-platform/tests/reporting/nestedReport.html
@@ -0,0 +1,30 @@
+<!DOCTYPE HTML>
+<meta charset=utf-8>
+<title>Reporting: Nested report</title>
+<link rel="author" title="Paul Meyer" href="paulmeyer@chromium.org">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script>
+ // Test that reports can be generated within a ReportingObserver
+ // callback. These reports should be received by the same observer.
+ async_test(function(test) {
+ var step = 0;
+ var observer = new ReportingObserver(async function(reports, observer) {
+ test.step(function() {
+ assert_equals(reports.length, 1);
+ assert_equals(reports[0].body.message, "" + step);
+ });
+
+ ++step;
+ if (step == 3)
+ test.done();
+
+ test_driver.generate_test_report("" + step);
+ });
+ observer.observe();
+
+ test_driver.generate_test_report("0");
+ }, "Nested report");
+</script>
diff --git a/testing/web-platform/tests/reporting/order.html b/testing/web-platform/tests/reporting/order.html
new file mode 100644
index 0000000000..c43964220c
--- /dev/null
+++ b/testing/web-platform/tests/reporting/order.html
@@ -0,0 +1,32 @@
+<!DOCTYPE HTML>
+<meta charset=utf-8>
+<title>Reporting: Order</title>
+<link rel="author" title="Paul Meyer" href="paulmeyer@chromium.org">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<p id="error">No error</p>
+<script>
+ var count = 0;
+ async_test(function(test) {
+ var observer = new ReportingObserver(function(reports) {
+ test.step(function() {
+ // Reports should be received in the same order that they were
+ // generated.
+ for(i in reports) {
+ assert_equals(reports[i].body.message, "" + count++);
+ }
+ });
+
+ if (count == 10)
+ test.done();
+ });
+ observer.observe();
+
+ for (i = 0; i != 10; ++i)
+ test_driver.generate_test_report("" + i);
+ }, "Order");
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/reporting/reporting-api-honors-limits.https.sub.html b/testing/web-platform/tests/reporting/reporting-api-honors-limits.https.sub.html
new file mode 100644
index 0000000000..4732711888
--- /dev/null
+++ b/testing/web-platform/tests/reporting/reporting-api-honors-limits.https.sub.html
@@ -0,0 +1,70 @@
+<!doctype html>
+<html>
+<head>
+ <title>Test that the report-api honors buffer limits on a per-report type basis</title>
+ <link rel="author" title="Brent Fulgham" href="bfulgham@apple.com">
+ <script src='/resources/testharness.js'></script>
+ <script src='/resources/testharnessreport.js'></script>
+ <script src="/resources/testdriver.js"></script>
+ <script src="/resources/testdriver-vendor.js"></script>
+</head>
+<body>
+ <script>
+ var t1 = async_test("Test that image does not load");
+
+ promise_test(async function() {
+ for (let i = 0; i < 110; ++i)
+ await test_driver.generate_test_report("" + i);
+ }, "Buffer filled");
+
+ async_test(function(t2) {
+ window.addEventListener("securitypolicyviolation", t2.step_func(function(e) {
+ assert_equals(e.blockedURI, "{{location[scheme]}}://{{location[host]}}/reporting/support/fail.png");
+ assert_equals(e.violatedDirective, "img-src");
+ t2.done();
+ }));
+ }, "Event is fired");
+
+ promise_test(async function(test) {
+ const cspReports = await new Promise(resolve => {
+ let observer = new ReportingObserver(resolve, {types:["csp-violation"], buffered:true});
+ observer.observe();
+ });
+
+ // WebKit generates two CSP reports for the blocked image load (https://bugs.webkit.org/show_bug.cgi?id=153162)
+ assert_true(cspReports.length > 0 && cspReports.length < 3);
+
+ // Ensure that the contents of the report are valid.
+ assert_equals(cspReports[0].type, "csp-violation");
+ }, "CSP Report limits were honored");
+
+ promise_test(async function(test) {
+ const testReports = await new Promise(resolve => {
+ let observer = new ReportingObserver(resolve, {types:["test"], buffered:true});
+ observer.observe();
+ });
+
+ assert_equals(testReports.length, 100);
+
+ for (let i = 0; i < 100; ++i) {
+ assert_equals(testReports[i].type, "test");
+ assert_equals(testReports[i].body.message, "" + (i + 10));
+ }
+ }, "Test Report limits were honored");
+
+ promise_test(async function(test) {
+ const allReports = await new Promise(resolve => {
+ let observer = new ReportingObserver(resolve, {buffered:true});
+ observer.observe();
+ });
+
+ // WebKit generates two CSP reports for the blocked image load (https://bugs.webkit.org/show_bug.cgi?id=153162)
+ // Other browsers produce only one.
+ assert_true(allReports.length >= 101 && allReports.length <= 102);
+ }, "Combined report limits were honored");
+ </script>
+ <img src='/reporting/support/fail.png'
+ onload='t1.unreached_func("The image should not have loaded");'
+ onerror='t1.done();'>
+</body>
+</html>
diff --git a/testing/web-platform/tests/reporting/reporting-api-honors-limits.https.sub.html.sub.headers b/testing/web-platform/tests/reporting/reporting-api-honors-limits.https.sub.html.sub.headers
new file mode 100644
index 0000000000..376788443e
--- /dev/null
+++ b/testing/web-platform/tests/reporting/reporting-api-honors-limits.https.sub.html.sub.headers
@@ -0,0 +1,7 @@
+Expires: Mon, 26 Jul 1997 05:00:00 GMT
+Cache-Control: no-store, no-cache, must-revalidate
+Cache-Control: post-check=0, pre-check=0, false
+Pragma: no-cache
+Set-Cookie: reporting-api-honors-limits={{$id:uuid()}}; Path=/reporting
+Reporting-Endpoints: csp-group="https://{{host}}:{{ports[https][0]}}/reporting/resources/report.py?op=put&reportID={{$id}}"
+Content-Security-Policy: script-src 'self' 'unsafe-inline'; img-src 'none'; report-to csp-group
diff --git a/testing/web-platform/tests/reporting/reporting-isolated-across-navigations.https.sub.html b/testing/web-platform/tests/reporting/reporting-isolated-across-navigations.https.sub.html
new file mode 100644
index 0000000000..df61afa833
--- /dev/null
+++ b/testing/web-platform/tests/reporting/reporting-isolated-across-navigations.https.sub.html
@@ -0,0 +1,56 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>Bug test page 1</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="resources/report-helper.js"></script>
+<script>
+promise_test(async t => {
+ await new Promise(resolve => {
+ window.addEventListener("message", resolve);
+ });
+ // At this point, the reporting endpoint should have received all three
+ // reports. Ensure that reports from the first page are not batched with
+ // those from the second, or sent to its endpoint.
+ const csp1_uuid = "112868aa-4b59-57c7-a388-db909ef24295";
+ const csp2_uuid = "612bf2ee-b9b8-5f8d-a239-0981c6ff057e";
+ const reports1 = await pollReports('/reporting/resources/report.py', csp1_uuid);
+ const reports2 = await pollReports('/reporting/resources/report.py', csp2_uuid);
+
+ const url_prefix = "https://{{location[host]}}/reporting/resources/";
+
+ // Validate that both received reports were CSP img-src violations from the
+ // same reporting source. Each image should be represented once, although the
+ // order does not matter.
+
+ assert_equals(reports1.length, 2, "First endpoint should receive two reports");
+
+ assert_equals(reports1[0].type, "csp-violation");
+ assert_equals(reports1[0].url, url_prefix + "first-csp-report.https.sub.html");
+ assert_equals(reports1[0].body.disposition, "enforce");
+ assert_equals(reports1[0].body.effectiveDirective, "img-src");
+
+ assert_equals(reports1[1].type, "csp-violation");
+ assert_equals(reports1[1].url, url_prefix + "first-csp-report.https.sub.html");
+ assert_equals(reports1[1].body.disposition, "enforce");
+ assert_equals(reports1[1].body.effectiveDirective, "img-src");
+
+ var image_sources = [reports1[0].body.blockedURL, reports1[1].body.blockedURL].sort();
+ assert_equals(image_sources[0], url_prefix + "missing1.png");
+ assert_equals(image_sources[1], url_prefix + "missing2.png");
+
+ // Validate that the report received from the second endpoint was also a CSP
+ // img-source violation, from a different URL.
+
+ assert_equals(reports2.length, 1, "Second endpoint should reecive one report");
+ assert_equals(reports2[0].type, "csp-violation");
+ assert_equals(reports2[0].url, url_prefix + "second-csp-report.https.sub.html");
+ assert_equals(reports2[0].body.disposition, "enforce");
+ assert_equals(reports2[0].body.effectiveDirective, "img-src");
+ assert_equals(reports2[0].body.blockedURL, url_prefix + "missing3.png");
+}, "Reports should be sent to the correct endpoints");
+</script>
+<body>
+<h1>Bug test main frame</h1>
+<iframe id="frame" src="resources/first-csp-report.https.sub.html"></iframe>
+</body>
diff --git a/testing/web-platform/tests/reporting/resources/README.md b/testing/web-platform/tests/reporting/resources/README.md
new file mode 100644
index 0000000000..b77d1f96b7
--- /dev/null
+++ b/testing/web-platform/tests/reporting/resources/README.md
@@ -0,0 +1,75 @@
+# Using the common report collector
+
+To send reports to the collector, configure the reporting API to POST reports
+to the collector's URL. This can be same- or cross- origin with the reporting
+document, as the collector will follow the CORS protocol.
+
+The collector supports both CSP Level 2 (report-uri) reports as well as
+Reporting API reports.
+
+A GET request can be used to retrieve stored reports for analysis.
+
+A POST request can be used to clear reports stored in the server.
+
+Sent credentials are stored with the reports, and can be retrieved separately.
+
+CORS Notes:
+* Preflight requests originating from www2.web-platform.test will be rejected.
+ This allows tests to ensure that cross-origin report uploads are not sent when
+ the endpoint does not support CORS.
+
+## Supported GET parameters:
+ `op`: For GET requests, a string indicating the operation to perform (see
+ below for description of supported operations). Defaults to
+ `retrieve_report`.
+
+ `reportID`: A UUID to associate with the reports sent from this document. This
+ can be used to distinguish between reports from multiple documents, and to
+ provide multiple distinct endpoints for a single document. Either `reportID`
+ or `endpoint` must be provided.
+
+ `endpoint`: A string which will be used to generate a UUID to be used as the
+ reportID. Either `reportID` or `endpoint` must be provided.
+
+ `timeout`: The amount of time to wait, in seconds, before responding. Defaults
+ to 0.5s.
+
+ `min_count`: The minimum number of reports to return with the `retrieve_report`
+ operation. If there have been fewer than this many reports received, then an
+ empty report list will be returned instead.
+
+ `retain`: If present, reports will remain in the stash after being retrieved.
+ By default, reports are cleared once retrieved.
+
+### Operations:
+ `retrieve_report`: Returns all reports received so far for this reportID, as a
+ JSON-formatted list. If no reports have been received, an empty list will be
+ returned.
+
+ `retrieve_cookies`: Returns the cookies sent with the most recent reports for
+ this reportID, as a JSON-formatted object.
+
+ `retrieve_count`: Returns the number of POST requests for reports with this
+ reportID so far.
+
+## Supported POST JSON payload:
+
+ `op`: For POST requests, a string indicating the operation to perform (see
+ below for description of supported operations).
+
+ `reportIDs`: A list of `reportID`s, each one a UUID associated with reports stored in the server stash.
+
+### Operations
+`DELETE`: Clear all reports associated with `reportID` listed in `reportIDs` list.
+
+### Example usage:
+```
+# Clear reports on the server.
+fetch('/reporting/resources/report.py', {
+ method: "POST",
+ body: JSON.stringify({
+ op: "DELETE",
+ reportIDs: [...] # a list of reportID
+ })
+});
+```
diff --git a/testing/web-platform/tests/reporting/resources/csp-error.https.sub.html b/testing/web-platform/tests/reporting/resources/csp-error.https.sub.html
new file mode 100644
index 0000000000..c883051945
--- /dev/null
+++ b/testing/web-platform/tests/reporting/resources/csp-error.https.sub.html
@@ -0,0 +1,19 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Notify parent on load and generate a CSP error</title>
+</head>
+<body>
+ <script>
+ addEventListener('load', () => {
+ // Alert the parent frame that this frame has loaded.
+ parent.postMessage('Loaded','*');
+
+ // Trigger a CSP error, which should generate a report.
+ const img = document.createElement('img');
+ img.src = "/reporting/resources/fail.png";
+ document.body.appendChild(img);
+ });
+ </script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/reporting/resources/csp-error.https.sub.html.sub.headers b/testing/web-platform/tests/reporting/resources/csp-error.https.sub.html.sub.headers
new file mode 100644
index 0000000000..00b60a2d0c
--- /dev/null
+++ b/testing/web-platform/tests/reporting/resources/csp-error.https.sub.html.sub.headers
@@ -0,0 +1,2 @@
+Reporting-Endpoints: csp-endpoint="https://{{domains[www]}}:{{ports[https][0]}}/reporting/resources/report.py?reportID=d0d517bf-891b-457a-b970-8b2b2c81a0bf"
+Content-Security-Policy: script-src 'self' 'unsafe-inline'; img-src 'none'; report-to csp-endpoint
diff --git a/testing/web-platform/tests/reporting/resources/fail.png b/testing/web-platform/tests/reporting/resources/fail.png
new file mode 100644
index 0000000000..b593380333
--- /dev/null
+++ b/testing/web-platform/tests/reporting/resources/fail.png
Binary files differ
diff --git a/testing/web-platform/tests/reporting/resources/first-csp-report.https.sub.html b/testing/web-platform/tests/reporting/resources/first-csp-report.https.sub.html
new file mode 100644
index 0000000000..9887769128
--- /dev/null
+++ b/testing/web-platform/tests/reporting/resources/first-csp-report.https.sub.html
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>Bug test page 1</title>
+</head>
+<body>
+<h1>Bug test page 1</h1>
+<!-- This image will cause a CSP violation, which will trigger an immediate report -->
+<img src="missing1.png">
+<script>
+setTimeout(()=>{
+ var img = document.createElement('img');
+ img.src = "missing2.png";
+ // Appending this image will cause a second CSP violation, which will trigger
+ // a second report.
+ document.body.appendChild(img);
+ location.href = "second-csp-report.https.sub.html";
+}, 1);
+</script>
+</body>
+</html>
+
diff --git a/testing/web-platform/tests/reporting/resources/first-csp-report.https.sub.html.sub.headers b/testing/web-platform/tests/reporting/resources/first-csp-report.https.sub.html.sub.headers
new file mode 100644
index 0000000000..2f5eeb4d8c
--- /dev/null
+++ b/testing/web-platform/tests/reporting/resources/first-csp-report.https.sub.html.sub.headers
@@ -0,0 +1,2 @@
+Reporting-Endpoints: csp="/reporting/resources/report.py?pipe=trickle(d1)&endpoint=csp1"
+Content-Security-Policy: img-src 'none'; report-to csp
diff --git a/testing/web-platform/tests/reporting/resources/generate-csp-report.https.sub.html b/testing/web-platform/tests/reporting/resources/generate-csp-report.https.sub.html
new file mode 100644
index 0000000000..7adec0f309
--- /dev/null
+++ b/testing/web-platform/tests/reporting/resources/generate-csp-report.https.sub.html
@@ -0,0 +1,4 @@
+<!DOCTYPE html>
+<meta charset="utf-8" />
+<title>Generate CSP reports </title>
+<img src='/reporting/resources/fail.png'>
diff --git a/testing/web-platform/tests/reporting/resources/generate-csp-report.https.sub.html.sub.headers b/testing/web-platform/tests/reporting/resources/generate-csp-report.https.sub.html.sub.headers
new file mode 100644
index 0000000000..44242c3c89
--- /dev/null
+++ b/testing/web-platform/tests/reporting/resources/generate-csp-report.https.sub.html.sub.headers
@@ -0,0 +1 @@
+Content-Security-Policy: script-src 'self' 'unsafe-inline'; img-src 'none'; report-to default
diff --git a/testing/web-platform/tests/reporting/resources/generate-report-once.py b/testing/web-platform/tests/reporting/resources/generate-report-once.py
new file mode 100644
index 0000000000..163846a4b9
--- /dev/null
+++ b/testing/web-platform/tests/reporting/resources/generate-report-once.py
@@ -0,0 +1,34 @@
+def main(request, response):
+ # Handle CORS preflight requests
+ if request.method == u'OPTIONS':
+ # Always reject preflights for one subdomain
+ if b"www2" in request.headers[b"Origin"]:
+ return (400, [], u"CORS preflight rejected for www2")
+ return [
+ (b"Content-Type", b"text/plain"),
+ (b"Access-Control-Allow-Origin", b"*"),
+ (b"Access-Control-Allow-Methods", b"get"),
+ (b"Access-Control-Allow-Headers", b"Content-Type"),
+ ], u"CORS allowed"
+
+ if b"reportID" in request.GET:
+ key = request.GET.first(b"reportID")
+ else:
+ response.status = 400
+ return "reportID parameter is required."
+
+ with request.server.stash.lock:
+ visited = request.server.stash.take(key=key)
+ if visited is None:
+ response.headers.set("Reporting-Endpoints",
+ b"default=\"/reporting/resources/report.py?reportID=%s\"" % key)
+ request.server.stash.put(key=key, value=True)
+
+ response.content = b"""
+<!DOCTYPE HTML>
+<meta charset=utf-8>
+<title>Generate deprecation report</title>
+<script>
+ webkitRequestAnimationFrame(() => {});
+</script>
+"""
diff --git a/testing/web-platform/tests/reporting/resources/generate-report.https.sub.html b/testing/web-platform/tests/reporting/resources/generate-report.https.sub.html
new file mode 100644
index 0000000000..f1f4e96300
--- /dev/null
+++ b/testing/web-platform/tests/reporting/resources/generate-report.https.sub.html
@@ -0,0 +1,6 @@
+<!DOCTYPE HTML>
+<meta charset=utf-8>
+<title>Generate deprecation report</title>
+<script>
+ webkitRequestAnimationFrame(() => {});
+</script>
diff --git a/testing/web-platform/tests/reporting/resources/middle-frame.https.sub.html b/testing/web-platform/tests/reporting/resources/middle-frame.https.sub.html
new file mode 100644
index 0000000000..0dd26ecc2b
--- /dev/null
+++ b/testing/web-platform/tests/reporting/resources/middle-frame.https.sub.html
@@ -0,0 +1,15 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Utility page which embeds a reporting page on the chosen host</title>
+</head>
+<body>
+ <script>
+ const searchParams = new URLSearchParams(window.location.search);
+ const host = searchParams.get('host') || "{{hosts[][]}}";
+ const frame = document.createElement('iframe');
+ frame.src=`https://${host}:{{ports[https][0]}}/reporting/resources/same-origin-report.https.sub.html`;
+ document.body.appendChild(frame);
+ </script>
+</body>
+</html> \ No newline at end of file
diff --git a/testing/web-platform/tests/reporting/resources/report-helper.js b/testing/web-platform/tests/reporting/resources/report-helper.js
new file mode 100644
index 0000000000..213a635c49
--- /dev/null
+++ b/testing/web-platform/tests/reporting/resources/report-helper.js
@@ -0,0 +1,38 @@
+function wait(ms) {
+ return new Promise(resolve => step_timeout(resolve, ms));
+}
+
+async function pollReports(endpoint, id, min_count) {
+ const res = await fetch(`${endpoint}?reportID=${id}${min_count ? `&min_count=${min_count}` : ''}`, { cache: 'no-store' });
+ const reports = [];
+ if (res.status === 200) {
+ for (const report of await res.json()) {
+ reports.push(report);
+ }
+ }
+ return reports;
+}
+
+async function pollCookies(endpoint, id) {
+ const res = await fetch(`${endpoint}?reportID=${id}&op=retrieve_cookies`, { cache: 'no-store' });
+ const dict = await res.json();
+ if (dict.reportCookies == 'None')
+ return {};
+ return dict.reportCookies;
+}
+
+async function pollNumResults(endpoint, id) {
+ const res = await fetch(`${endpoint}?reportID=${id}&op=retrieve_count`, { cache: 'no-store' });
+ const dict = await res.json();
+ if (dict.report_count == 'None')
+ return 0;
+ return dict.report_count;
+}
+
+function checkReportExists(reports, type, url) {
+ for (const report of reports) {
+ if (report.type !== type) continue;
+ if (report.body.documentURL == url || report.body.sourceFile === url) return true;
+ }
+ assert_unreached(`A report of ${type} from ${url} is not found.`);
+}
diff --git a/testing/web-platform/tests/reporting/resources/report.py b/testing/web-platform/tests/reporting/resources/report.py
new file mode 100644
index 0000000000..b5ee0c07d8
--- /dev/null
+++ b/testing/web-platform/tests/reporting/resources/report.py
@@ -0,0 +1,143 @@
+import time
+import json
+import re
+import uuid
+
+from wptserve.utils import isomorphic_decode
+
+
+def retrieve_from_stash(request, key, timeout, default_value, min_count=None, retain=False):
+ """Retrieve the set of reports for a given report ID.
+
+ This will extract either the set of reports, credentials, or request count
+ from the stash (depending on the key passed in) and return it encoded as JSON.
+
+ When retrieving reports, this will not return any reports until min_count
+ reports have been received.
+
+ If timeout seconds elapse before the requested data can be found in the stash,
+ or before at least min_count reports are received, default_value will be
+ returned instead."""
+ t0 = time.time()
+ while time.time() - t0 < timeout:
+ time.sleep(0.5)
+ with request.server.stash.lock:
+ value = request.server.stash.take(key=key)
+ if value is not None:
+ have_sufficient_reports = (
+ min_count is None or len(value) >= min_count)
+ if retain or not have_sufficient_reports:
+ request.server.stash.put(key=key, value=value)
+ if have_sufficient_reports:
+ return json.dumps(value)
+
+ return default_value
+
+
+def main(request, response):
+ # Handle CORS preflight requests
+ if request.method == u'OPTIONS':
+ # Always reject preflights for one subdomain
+ if b"www2" in request.headers[b"Origin"]:
+ return (400, [], u"CORS preflight rejected for www2")
+ return [
+ (b"Content-Type", b"text/plain"),
+ (b"Access-Control-Allow-Origin", b"*"),
+ (b"Access-Control-Allow-Methods", b"post"),
+ (b"Access-Control-Allow-Headers", b"Content-Type"),
+ ], u"CORS allowed"
+
+ # Delete reports as requested
+ if request.method == u'POST':
+ body = json.loads(request.body)
+ if (isinstance(body, dict) and "op" in body):
+ if body["op"] == "DELETE" and "reportIDs" in body:
+ with request.server.stash.lock:
+ for key in body["reportIDs"]:
+ request.server.stash.take(key=key)
+ return "reports cleared"
+ response.status = 400
+ return "op parameter value not recognized"
+
+ if b"reportID" in request.GET:
+ key = request.GET.first(b"reportID")
+ elif b"endpoint" in request.GET:
+ key = uuid.uuid5(uuid.NAMESPACE_OID, isomorphic_decode(
+ request.GET[b'endpoint'])).urn.encode('ascii')[9:]
+ else:
+ response.status = 400
+ return "Either reportID or endpoint parameter is required."
+
+ # Cookie and count keys are derived from the report ID.
+ cookie_key = re.sub(b'^....', b'cccc', key)
+ count_key = re.sub(b'^....', b'dddd', key)
+
+ if request.method == u'GET':
+ try:
+ timeout = float(request.GET.first(b"timeout"))
+ except:
+ timeout = 0.5
+ try:
+ min_count = int(request.GET.first(b"min_count"))
+ except:
+ min_count = 1
+ retain = (b"retain" in request.GET)
+
+ op = request.GET.first(b"op", b"")
+ if op in (b"retrieve_report", b""):
+ return [(b"Content-Type", b"application/json")], retrieve_from_stash(request, key, timeout, u'[]', min_count, retain)
+
+ if op == b"retrieve_cookies":
+ return [(b"Content-Type", b"application/json")], u"{ \"reportCookies\" : " + str(retrieve_from_stash(request, cookie_key, timeout, u"\"None\"")) + u"}"
+
+ if op == b"retrieve_count":
+ return [(b"Content-Type", b"application/json")], u"{ \"report_count\": %s }" % retrieve_from_stash(request, count_key, timeout, 0)
+
+ response.status = 400
+ return "op parameter value not recognized."
+
+ # Save cookies.
+ if len(request.cookies.keys()) > 0:
+ # Convert everything into strings and dump it into a dict.
+ temp_cookies_dict = {}
+ for dict_key in request.cookies.keys():
+ temp_cookies_dict[isomorphic_decode(dict_key)] = str(
+ request.cookies.get_list(dict_key))
+ with request.server.stash.lock:
+ # Clear any existing cookie data for this request before storing new data.
+ request.server.stash.take(key=cookie_key)
+ request.server.stash.put(key=cookie_key, value=temp_cookies_dict)
+
+ # Append new report(s).
+ new_reports = json.loads(request.body)
+
+ # If the incoming report is a CSP report-uri report, then it will be a single
+ # dictionary rather than a list of reports. To handle this case, ensure that
+ # any non-list request bodies are wrapped in a list.
+ if not isinstance(new_reports, list):
+ new_reports = [new_reports]
+
+ for report in new_reports:
+ report[u"metadata"] = {
+ u"content_type": isomorphic_decode(request.headers[b"Content-Type"]),
+ }
+
+ with request.server.stash.lock:
+ reports = request.server.stash.take(key=key)
+ if reports is None:
+ reports = []
+ reports.extend(new_reports)
+ request.server.stash.put(key=key, value=reports)
+
+ # Increment report submission count. This tracks the number of times this
+ # reporting endpoint was contacted, rather than the total number of reports
+ # submitted, which can be seen from the length of the report list.
+ with request.server.stash.lock:
+ count = request.server.stash.take(key=count_key)
+ if count is None:
+ count = 0
+ count += 1
+ request.server.stash.put(key=count_key, value=count)
+
+ # Return acknowledgement report.
+ return [(b"Content-Type", b"text/plain")], b"Recorded report " + request.body
diff --git a/testing/web-platform/tests/reporting/resources/same-origin-report.https.sub.html b/testing/web-platform/tests/reporting/resources/same-origin-report.https.sub.html
new file mode 100644
index 0000000000..326a0fd0ed
--- /dev/null
+++ b/testing/web-platform/tests/reporting/resources/same-origin-report.https.sub.html
@@ -0,0 +1,15 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Generates a CSP violation report and sends it to a same-origin endpoint</title>
+</head>
+<body>
+ <script>
+ const img = document.createElement('img');
+ img.src = "/reporting/resources/fail.png";
+ document.body.appendChild(img);
+ // Post back to the main frame that the report should have been queued.
+ top.postMessage("done", "*");
+ </script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/reporting/resources/same-origin-report.https.sub.html.sub.headers b/testing/web-platform/tests/reporting/resources/same-origin-report.https.sub.html.sub.headers
new file mode 100644
index 0000000000..8244fafb22
--- /dev/null
+++ b/testing/web-platform/tests/reporting/resources/same-origin-report.https.sub.html.sub.headers
@@ -0,0 +1,2 @@
+Reporting-Endpoints: csp-endpoint="/reporting/resources/report.py?reportID=d0d517bf-891b-457a-b970-8b2b2c81a0bf"
+Content-Security-Policy: script-src 'self' 'unsafe-inline'; img-src 'none'; report-to csp-endpoint
diff --git a/testing/web-platform/tests/reporting/resources/second-csp-report.https.sub.html b/testing/web-platform/tests/reporting/resources/second-csp-report.https.sub.html
new file mode 100644
index 0000000000..a34bc653f5
--- /dev/null
+++ b/testing/web-platform/tests/reporting/resources/second-csp-report.https.sub.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>Bug test page 2</title>
+</head>
+<body>
+<h1>Bug test page 2</h1>
+<script>
+var img = document.createElement('img');
+img.src = "missing3.png";
+// Appending this image will cause a third CSP violation. The report generated
+// here must not be batched with the reports from the previous page, regardless
+// of whether they have been sent or not.
+document.body.appendChild(img);
+// Give the report handler enough time to finish handling any reports from the
+// previous page (Reports there are delayed by 1 second because of the trickle
+// pipe in the headers in first-csp-report.https.sub.html.sub.headers) and then
+// inform the parent that reports may be checked.
+setTimeout(()=>{
+ parent.postMessage("ready", "*");
+},1250);
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/reporting/resources/second-csp-report.https.sub.html.sub.headers b/testing/web-platform/tests/reporting/resources/second-csp-report.https.sub.html.sub.headers
new file mode 100644
index 0000000000..b32c548fef
--- /dev/null
+++ b/testing/web-platform/tests/reporting/resources/second-csp-report.https.sub.html.sub.headers
@@ -0,0 +1,2 @@
+Reporting-Endpoints: csp="/reporting/resources/report.py?endpoint=csp2"
+Content-Security-Policy: img-src 'none'; report-to csp
diff --git a/testing/web-platform/tests/reporting/same-origin-cross-site-credentials.https.sub.html b/testing/web-platform/tests/reporting/same-origin-cross-site-credentials.https.sub.html
new file mode 100644
index 0000000000..258ab8e103
--- /dev/null
+++ b/testing/web-platform/tests/reporting/same-origin-cross-site-credentials.https.sub.html
@@ -0,0 +1,53 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test that credentials are sent properly in a same-origin but not same-site context</title>
+ <script src='/resources/testharness.js'></script>
+ <script src='/resources/testharnessreport.js'></script>
+ <script src='resources/report-helper.js'></script>
+</head>
+<body>
+ <script>
+ const base_url = `${location.protocol}//${location.host}`;
+ const endpoint = `${base_url}/reporting/resources/report.py`;
+ const id = 'd0d517bf-891b-457a-b970-8b2b2c81a0bf';
+
+ promise_test(async t => {
+ // If this is not run from the expected origin, then the A->B->A frame embedding will not be correct,
+ // and the cookies set in the top-level page will never be returned with the reports.
+ assert_true(location.href.startsWith("https://{{hosts[][]}}:{{ports[https][0]}}/"),
+ "Test running on unexpected origin; subsequent assertions will fail.");
+
+ // Set credentials, and set up test to clear them afterwards.
+ await fetch('/cookies/resources/setSameSite.py?reporting', {mode: 'no-cors', credentials: 'include', cache: 'no-store'});
+ t.add_cleanup(() => fetch("/cookies/resources/dropSameSite.py", {mode: 'no-cors', credentials: 'include', cache: 'no-store'}));
+
+ // Insert a cross-origin frame which will then frame this origin to
+ // trigger a CSP error.
+ const frame = document.createElement('iframe');
+ frame.src = "https://{{hosts[alt][]}}:{{ports[https][0]}}/reporting/resources/middle-frame.https.sub.html";
+ document.body.appendChild(frame);
+
+ // Wait for the inner frame to signal that the report has been generated.
+ await new Promise(resolve => {
+ window.addEventListener('message', ev => {
+ if (ev.data === "done")
+ resolve(ev.data);
+ });
+ document.body.appendChild(frame);
+ });
+
+ const reports = await pollReports(endpoint, id);
+ checkReportExists(reports, 'csp-violation', "https://{{hosts[][]}}:{{ports[https][0]}}/reporting/resources/same-origin-report.https.sub.html");
+
+ // Same-site: None cookies should be sent, but not Lax, Strict, or default cookies.
+ const cookies = await pollCookies(endpoint, id);
+ assert_equals(cookies.samesite_none, "[samesite_none=reporting]", "Credential value was correct");
+ assert_false("samesite_strict" in cookies, "Same-site: Strict cookies should not be sent");
+ assert_false("samesite_lax" in cookies, "Same-site: Lax cookies should not be sent");
+ assert_false("samesite_unspecified" in cookies, "Same-site unspecified cookies should not be sent");
+ assert_equals(Object.keys(cookies).length, 1, "No additional cookies were received");
+ }, "Reporting endpoints received credentials.");
+ </script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/reporting/same-origin-report-credentials.https.sub.html b/testing/web-platform/tests/reporting/same-origin-report-credentials.https.sub.html
new file mode 100644
index 0000000000..cd93bd601b
--- /dev/null
+++ b/testing/web-platform/tests/reporting/same-origin-report-credentials.https.sub.html
@@ -0,0 +1,39 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test that reports are sent with credentials to same-origin endpoints</title>
+ <script src='/resources/testharness.js'></script>
+ <script src='/resources/testharnessreport.js'></script>
+ <script src='resources/report-helper.js'></script>
+</head>
+<body>
+ <script>
+ const base_url = `${location.protocol}//${location.host}`;
+ const endpoint = `${base_url}/reporting/resources/report.py`;
+ const id = '320db941-960a-4529-8c4a-24aeb6739309';
+
+ promise_test(async t => {
+ // Set credentials, and set up test to clear them afterwards.
+ await fetch('/cookies/resources/set-cookie.py?name=report&path=%2F', {mode: 'no-cors', credentials: 'include', cache: 'no-store'});
+ t.add_cleanup(() => fetch("/cookies/resources/set.py?report=; path=%2F; expires=Thu, 01 Jan 1970 00:00:01 GMT"));
+
+ // Trigger a CSP error.
+ await new Promise(resolve => {
+ const img = document.createElement('img');
+ img.src = "/reporting/resources/fail.png";
+ img.addEventListener('error', resolve);
+ document.body.appendChild(img);
+ });
+
+ // Wait for report to be received.
+ const reports = await pollReports(endpoint, id);
+ checkReportExists(reports, 'csp-violation', location.href);
+
+ // Validate that credentials were sent to same-origin endpoint.
+ const cookies = await pollCookies(endpoint, id);
+ assert_true('report' in cookies, "Credentials were present in report");
+ assert_equals(cookies.report, "[report=1]", "Credential value was correct");
+ }, "Reporting endpoints received credentials.");
+ </script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/reporting/same-origin-report-credentials.https.sub.html.sub.headers b/testing/web-platform/tests/reporting/same-origin-report-credentials.https.sub.html.sub.headers
new file mode 100644
index 0000000000..a88efd0cf9
--- /dev/null
+++ b/testing/web-platform/tests/reporting/same-origin-report-credentials.https.sub.html.sub.headers
@@ -0,0 +1,2 @@
+Reporting-Endpoints: csp-endpoint="/reporting/resources/report.py?reportID=320db941-960a-4529-8c4a-24aeb6739309"
+Content-Security-Policy: script-src 'self' 'unsafe-inline'; img-src 'none'; report-to csp-endpoint
diff --git a/testing/web-platform/tests/reporting/same-origin-same-site-credentials.https.sub.html b/testing/web-platform/tests/reporting/same-origin-same-site-credentials.https.sub.html
new file mode 100644
index 0000000000..9b99edb26e
--- /dev/null
+++ b/testing/web-platform/tests/reporting/same-origin-same-site-credentials.https.sub.html
@@ -0,0 +1,53 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test that credentials are sent properly in a same-origin and also same-site nested context</title>
+ <script src='/resources/testharness.js'></script>
+ <script src='/resources/testharnessreport.js'></script>
+ <script src='resources/report-helper.js'></script>
+</head>
+<body>
+ <script>
+ const base_url = `${location.protocol}//${location.host}`;
+ const endpoint = `${base_url}/reporting/resources/report.py`;
+ const id = 'd0d517bf-891b-457a-b970-8b2b2c81a0bf';
+
+ promise_test(async t => {
+ // If this is not run from the expected origin, then the A->A->A frame embedding will not be correct,
+ // and the cookies set in the top-level page will never be returned with the reports.
+ assert_true(location.href.startsWith("https://{{hosts[][]}}:{{ports[https][0]}}/"),
+ "Test running on unexpected origin; subsequent assertions will fail.");
+
+ // Set credentials, and set up test to clear them afterwards.
+ await fetch('/cookies/resources/setSameSite.py?reporting', {mode: 'no-cors', credentials: 'include', cache: 'no-store'});
+ t.add_cleanup(() => fetch("/cookies/resources/dropSameSite.py", {mode: 'no-cors', credentials: 'include', cache: 'no-store'}));
+
+ // Insert a same-origin frame which will then frame this origin to
+ // trigger a CSP error.
+ const frame = document.createElement('iframe');
+ frame.src = "https://{{hosts[][]}}:{{ports[https][0]}}/reporting/resources/middle-frame.https.sub.html";
+ document.body.appendChild(frame);
+
+ // Wait for the inner frame to signal that the report has been generated.
+ await new Promise(resolve => {
+ window.addEventListener('message', ev => {
+ if (ev.data === "done")
+ resolve(ev.data);
+ });
+ document.body.appendChild(frame);
+ });
+
+ const reports = await pollReports(endpoint, id);
+ checkReportExists(reports, 'csp-violation', "https://{{hosts[][]}}:{{ports[https][0]}}/reporting/resources/same-origin-report.https.sub.html");
+
+ // All credentials set at the top-level should be received.
+ const cookies = await pollCookies(endpoint, id);
+ assert_equals(cookies.samesite_none, "[samesite_none=reporting]", "Credential value was correct");
+ assert_equals(cookies.samesite_unspecified, "[samesite_unspecified=reporting]", "Credential value was correct");
+ assert_equals(cookies.samesite_lax, "[samesite_lax=reporting]", "Credential value was correct");
+ assert_equals(cookies.samesite_strict, "[samesite_strict=reporting]", "Credential value was correct");
+ assert_equals(Object.keys(cookies).length, 4, "No additional cookies were received");
+ }, "Reporting endpoints received credentials.");
+ </script>
+</body>
+</html>