summaryrefslogtreecommitdiffstats
path: root/testing/web-platform/tests/network-error-logging
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
commit26a029d407be480d791972afb5975cf62c9360a6 (patch)
treef435a8308119effd964b339f76abb83a57c29483 /testing/web-platform/tests/network-error-logging
parentInitial commit. (diff)
downloadfirefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz
firefox-26a029d407be480d791972afb5975cf62c9360a6.zip
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'testing/web-platform/tests/network-error-logging')
-rw-r--r--testing/web-platform/tests/network-error-logging/META.yml3
-rw-r--r--testing/web-platform/tests/network-error-logging/README.md74
-rw-r--r--testing/web-platform/tests/network-error-logging/no-report-on-failed-cors-preflight.https.html26
-rw-r--r--testing/web-platform/tests/network-error-logging/no-report-on-subdomain-404.https.html30
-rw-r--r--testing/web-platform/tests/network-error-logging/no-report-on-subdomain-success.https.html30
-rw-r--r--testing/web-platform/tests/network-error-logging/no-report-on-unexpired-cached-response.https.html33
-rw-r--r--testing/web-platform/tests/network-error-logging/reports-are-not-observable.https.html29
-rw-r--r--testing/web-platform/tests/network-error-logging/sends-report-on-404.https.html42
-rw-r--r--testing/web-platform/tests/network-error-logging/sends-report-on-cache-validation.https.html42
-rw-r--r--testing/web-platform/tests/network-error-logging/sends-report-on-redirect.https.html55
-rw-r--r--testing/web-platform/tests/network-error-logging/sends-report-on-subdomain-dns-failure.https.html46
-rw-r--r--testing/web-platform/tests/network-error-logging/sends-report-on-success-with-subdomain-policy.https.html40
-rw-r--r--testing/web-platform/tests/network-error-logging/sends-report-on-success.https.html37
-rw-r--r--testing/web-platform/tests/network-error-logging/support/cached-for-one-minute.pngbin0 -> 1689 bytes
-rw-r--r--testing/web-platform/tests/network-error-logging/support/cached-for-one-minute.png.sub.headers1
-rw-r--r--testing/web-platform/tests/network-error-logging/support/cached-with-validation.py17
-rw-r--r--testing/web-platform/tests/network-error-logging/support/clear-policy-pass.pngbin0 -> 1689 bytes
-rw-r--r--testing/web-platform/tests/network-error-logging/support/clear-policy-pass.png.sub.headers6
-rw-r--r--testing/web-platform/tests/network-error-logging/support/lock.py48
-rw-r--r--testing/web-platform/tests/network-error-logging/support/nel.sub.js293
-rw-r--r--testing/web-platform/tests/network-error-logging/support/no-policy-pass.pngbin0 -> 1689 bytes
-rw-r--r--testing/web-platform/tests/network-error-logging/support/pass.pngbin0 -> 1689 bytes
-rw-r--r--testing/web-platform/tests/network-error-logging/support/pass.png.sub.headers6
-rw-r--r--testing/web-platform/tests/network-error-logging/support/redirect.py3
-rw-r--r--testing/web-platform/tests/network-error-logging/support/subdomains-pass.pngbin0 -> 1689 bytes
-rw-r--r--testing/web-platform/tests/network-error-logging/support/subdomains-pass.png.sub.headers6
26 files changed, 867 insertions, 0 deletions
diff --git a/testing/web-platform/tests/network-error-logging/META.yml b/testing/web-platform/tests/network-error-logging/META.yml
new file mode 100644
index 0000000000..6c1f88eafc
--- /dev/null
+++ b/testing/web-platform/tests/network-error-logging/META.yml
@@ -0,0 +1,3 @@
+spec: https://w3c.github.io/network-error-logging/
+suggested_reviewers:
+ - dcreager
diff --git a/testing/web-platform/tests/network-error-logging/README.md b/testing/web-platform/tests/network-error-logging/README.md
new file mode 100644
index 0000000000..7cf2c6fdce
--- /dev/null
+++ b/testing/web-platform/tests/network-error-logging/README.md
@@ -0,0 +1,74 @@
+# Network Error Logging
+
+The tests in this directory exercise the user agent's implementation of [Network
+Error Logging](https://w3c.github.io/network-error-logging/) and
+[Reporting](https://w3c.github.io/reporting/).
+
+## Collector
+
+Each test case generates a unique `reportID` that is used to distinguish the NEL
+reports generated by that test case.
+
+The [support/report.py][] file is a [Python file handler][] that can be used as
+a Reporting collector. Its default operation is to save any reports that it
+receives into the [stash][]. If you pass in the optional `op` URL parameter,
+with a value of `retrieve_report`, it will instead return a list of all of the
+reports received for a particular `reportID`.
+
+[Python file handler]: https://wptserve.readthedocs.io/en/latest/handlers.html#python-file-handlers
+[stash]: https://wptserve.readthedocs.io/en/latest/stash.html
+[support/report.py]: support/report.py
+
+## Installing NEL policies
+
+NEL reports are only generated if the user agent has received a NEL policy for
+the origin of the request. The current request counts; if its response contains
+a policy, that policy is used for the current request and all future requests,
+modulo the policy's `max_age` field.
+
+Most of the test cases will therefore make a request or two to install NEL
+policies, and then make another request that should or should not be covered by
+those policies. It will then assert that NEL reports were or were not created,
+as required by the spec.
+
+The [support][] directory contains several images, each of which defines a
+particular "kind" of NEL policy (e.g., `include_subdomains` set vs unset, no
+policy at all, etc.). The [support/nel.sub.js][] file contains helper
+JavaScript methods for requesting those images, so that the test cases
+themselves are more descriptive.
+
+[support]: support
+[support/nel.sub.js]: support/nel.sub.js
+
+## Avoiding spurious reports
+
+NEL policies apply to **all** future requests to the origin. We therefore serve
+all of the test case's "infrastructure" (the test case itself,
+[support/report.py][] and [support/nel.sub.js][]) on a different origin than
+the requests that exercise the NEL implementation. That ensures that we don't
+have to wade through NEL reports about the infrastructure when verifying the NEL
+reports about the requests that we care about.
+
+## Browser configuration
+
+You must configure your browser's Reporting implementation to upload reports for
+a request immediately. The test cases do not currently have any timeouts; they
+assume that as soon as the Fetch API promise resolves, any NEL reports for the
+request have already been uploaded.
+
+## Test parallelism
+
+Because NEL policies are stored in a global cache in the user agent, we need to
+run the tests in this directory serially instead of in parallel. We implement a
+simple spin-lock in [support/lock.py][] to ensure that only one test is allowed
+to perform any NEL-related requests at a time.
+
+[support/lock.py]: support/lock.py
+
+## CORS preflights
+
+Reporting uploads are subject to CORS preflights. We want to test normal
+operation (when preflight requests succeed) as well as failures of the CORS
+preflight logic in the user agent. To support this, our test collector is
+configured to always reject the CORS preflight for a single domain (www2), and
+to always grant the CORS preflight for all other test subdomains.
diff --git a/testing/web-platform/tests/network-error-logging/no-report-on-failed-cors-preflight.https.html b/testing/web-platform/tests/network-error-logging/no-report-on-failed-cors-preflight.https.html
new file mode 100644
index 0000000000..3a35651b4e
--- /dev/null
+++ b/testing/web-platform/tests/network-error-logging/no-report-on-failed-cors-preflight.https.html
@@ -0,0 +1,26 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>
+ Test that NEL reports are not sent if the CORS preflight fails
+ </title>
+ <script src='/resources/testharness.js'></script>
+ <script src='/resources/testharnessreport.js'></script>
+ <script src='./support/nel.sub.js'></script>
+</head>
+<body>
+ <script>
+ nel_test(async t => {
+ // Make a request to a resource whose response headers include a NEL
+ // policy, but where the report uploader will reject the CORS preflight.
+ await fetchResourceWithBasicPolicy('www2');
+ // Because the CORS preflight is rejected, we should never receive a
+ // report about the request.
+ assert_false(await reportExists({
+ url: getURLForResourceWithBasicPolicy('www2'),
+ type: "network-error",
+ }));
+ });
+ </script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/network-error-logging/no-report-on-subdomain-404.https.html b/testing/web-platform/tests/network-error-logging/no-report-on-subdomain-404.https.html
new file mode 100644
index 0000000000..462f99e842
--- /dev/null
+++ b/testing/web-platform/tests/network-error-logging/no-report-on-subdomain-404.https.html
@@ -0,0 +1,30 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>
+ Test that include_subdomains policies do NOT report HTTP errors
+ </title>
+ <script src='/resources/testharness.js'></script>
+ <script src='/resources/testharnessreport.js'></script>
+ <script src='./support/nel.sub.js'></script>
+</head>
+<body>
+ <script>
+ nel_test(async t => {
+ // Make a request to a resource whose response headers include an
+ // include_subdomains NEL policy.
+ await fetchResourceWithIncludeSubdomainsPolicy();
+ // Make a request to another resource on a subdomain of the above. This
+ // resource doesn't exist, so the server should return a 404.
+ await fetchMissingResource('www');
+ // The include_subdomains policy that we just received should NOT cover
+ // the second request, since include_subdomains policies can only report
+ // on DNS errors.
+ assert_false(await reportExists({
+ url: getURLForMissingResource('www'),
+ type: "network-error",
+ }));
+ });
+ </script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/network-error-logging/no-report-on-subdomain-success.https.html b/testing/web-platform/tests/network-error-logging/no-report-on-subdomain-success.https.html
new file mode 100644
index 0000000000..5fd6d4fb41
--- /dev/null
+++ b/testing/web-platform/tests/network-error-logging/no-report-on-subdomain-success.https.html
@@ -0,0 +1,30 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>
+ Test that include_subdomains policies do NOT report successful requests
+ </title>
+ <script src='/resources/testharness.js'></script>
+ <script src='/resources/testharnessreport.js'></script>
+ <script src='./support/nel.sub.js'></script>
+</head>
+<body>
+ <script>
+ nel_test(async t => {
+ // Make a request to a resource whose response headers include an
+ // include_subdomains NEL policy.
+ await fetchResourceWithIncludeSubdomainsPolicy();
+ // Make a request to another resource on a subdomain of the above, which
+ // does not define its own NEL policy.
+ await fetchResourceWithNoPolicy('www');
+ // The include_subdomains policy that we just received should NOT cover
+ // the second request, since include_subdomains policies can only report
+ // on DNS errors.
+ assert_false(await reportExists({
+ url: getURLForResourceWithNoPolicy('www'),
+ type: "network-error",
+ }));
+ });
+ </script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/network-error-logging/no-report-on-unexpired-cached-response.https.html b/testing/web-platform/tests/network-error-logging/no-report-on-unexpired-cached-response.https.html
new file mode 100644
index 0000000000..8248aa52ec
--- /dev/null
+++ b/testing/web-platform/tests/network-error-logging/no-report-on-unexpired-cached-response.https.html
@@ -0,0 +1,33 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>
+ Test that NEL reports are not sent for cached responses that don't hit the
+ network
+ </title>
+ <script src='/resources/testharness.js'></script>
+ <script src='/resources/testharnessreport.js'></script>
+ <script src='./support/nel.sub.js'></script>
+</head>
+<body>
+ <script>
+ nel_test(async t => {
+ // Fetch a resource that can be cached without validation. Do this
+ // *before* fetching the NEL policy for this origin, to ensure that we
+ // don't generate any report about this request.
+ await fetchCachedResource();
+ // Fetch the NEL policy for this origin.
+ await fetchResourceWithBasicPolicy();
+ // Fetch the now-cached resource again. This should not generate a new
+ // network request.
+ await fetchCachedResource();
+ // Because the cached request did not generate a network request, we
+ // should never receive a report about the request.
+ assert_false(await reportExists({
+ url: getURLForCachedResource(),
+ type: "network-error",
+ }));
+ });
+ </script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/network-error-logging/reports-are-not-observable.https.html b/testing/web-platform/tests/network-error-logging/reports-are-not-observable.https.html
new file mode 100644
index 0000000000..35ab4f3c23
--- /dev/null
+++ b/testing/web-platform/tests/network-error-logging/reports-are-not-observable.https.html
@@ -0,0 +1,29 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>
+ Test that NEL reports are not observable from JavaScript
+ </title>
+ <script src='/resources/testharness.js'></script>
+ <script src='/resources/testharnessreport.js'></script>
+ <script src='./support/nel.sub.js'></script>
+</head>
+<body>
+ <script>
+ nel_test(async t => {
+ // Register an observer for NEL reports.
+ var observer = new ReportingObserver((reports, _) => {
+ assert_unreached("NEL reports should not be observable");
+ }, {"types": ["network-error"]});
+ observer.observe();
+ // Make a request to a resource whose response headers include a NEL
+ // policy. If NEL reports are observable, this will queue a task that
+ // calls the observer function above (which we don't want).
+ await fetchResourceWithBasicPolicy();
+ // Wait for one second to give any observer callback task a chance to
+ // fire.
+ await new Promise(resolve => t.step_timeout(resolve, 1000 /* msec */));
+ });
+ </script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/network-error-logging/sends-report-on-404.https.html b/testing/web-platform/tests/network-error-logging/sends-report-on-404.https.html
new file mode 100644
index 0000000000..38bdc01450
--- /dev/null
+++ b/testing/web-platform/tests/network-error-logging/sends-report-on-404.https.html
@@ -0,0 +1,42 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>
+ Test that NEL reports are sent for HTTP errors
+ </title>
+ <script src='/resources/testharness.js'></script>
+ <script src='/resources/testharnessreport.js'></script>
+ <script src='./support/nel.sub.js'></script>
+</head>
+<body>
+ <script>
+ nel_test(async t => {
+ // Make a request to a resource whose response headers include a NEL
+ // policy.
+ await fetchResourceWithBasicPolicy();
+ // Make a request to another resource on the same domain. This resource
+ // doesn't exist, so the server should return a 404.
+ await fetchMissingResource();
+ // The 404 won't contain its own NEL policy, but the policy we received in
+ // the first request should cover the second request, too, since they're
+ // at the same origin, so the collector should have received a report
+ // about it.
+ assert_true(await reportExists({
+ url: getURLForMissingResource(),
+ user_agent: navigator.userAgent,
+ type: "network-error",
+ body: {
+ method: "GET",
+ sampling_fraction: 1.0,
+ status_code: 404,
+ phase: "application",
+ type: "http.error",
+ },
+ metadata: {
+ content_type: "application/reports+json",
+ },
+ }));
+ });
+ </script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/network-error-logging/sends-report-on-cache-validation.https.html b/testing/web-platform/tests/network-error-logging/sends-report-on-cache-validation.https.html
new file mode 100644
index 0000000000..b87053cb7f
--- /dev/null
+++ b/testing/web-platform/tests/network-error-logging/sends-report-on-cache-validation.https.html
@@ -0,0 +1,42 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>
+ Test that NEL reports are sent for cache validation requests
+ </title>
+ <script src='/resources/testharness.js'></script>
+ <script src='/resources/testharnessreport.js'></script>
+ <script src='./support/nel.sub.js'></script>
+</head>
+<body>
+ <script>
+ nel_test(async t => {
+ // Fetch a resource that can be cached, but whose response requires
+ // validation. Do this *before* fetching the NEL policy for this origin,
+ // to ensure that we don't generate any report about this request.
+ await fetchValidatedCachedResource();
+ // Fetch the NEL policy for this origin.
+ await fetchResourceWithBasicPolicy();
+ // Fetch the now-cached resource again. Because the response requires
+ // validation, this will result in a network request.
+ await fetchValidatedCachedResource();
+ // We should receive a report about the cache validation request.
+ assert_true(await reportExists({
+ url: getURLForValidatedCachedResource(),
+ user_agent: navigator.userAgent,
+ type: "network-error",
+ body: {
+ method: "GET",
+ sampling_fraction: 1.0,
+ status_code: 304,
+ phase: "application",
+ type: "ok",
+ },
+ metadata: {
+ content_type: "application/reports+json",
+ },
+ }));
+ });
+ </script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/network-error-logging/sends-report-on-redirect.https.html b/testing/web-platform/tests/network-error-logging/sends-report-on-redirect.https.html
new file mode 100644
index 0000000000..da6ae72c5a
--- /dev/null
+++ b/testing/web-platform/tests/network-error-logging/sends-report-on-redirect.https.html
@@ -0,0 +1,55 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>
+ Test that NEL reports are sent for redirects
+ </title>
+ <script src='/resources/testharness.js'></script>
+ <script src='/resources/testharnessreport.js'></script>
+ <script src='./support/nel.sub.js'></script>
+</head>
+<body>
+ <script>
+ nel_test(async t => {
+ // Make a request to a resource whose response headers include a NEL
+ // policy.
+ await fetchResourceWithBasicPolicy();
+ // Then make a request that results in a redirect.
+ await fetchRedirectedResource();
+ // We should receive a report about the redirect itself, and also about
+ // the resource that we were redirected to.
+ assert_true(await reportExists({
+ url: getURLForRedirectedResource(),
+ user_agent: navigator.userAgent,
+ type: "network-error",
+ body: {
+ method: "GET",
+ sampling_fraction: 1.0,
+ status_code: 302,
+ phase: "application",
+ type: "ok",
+ },
+ metadata: {
+ content_type: "application/reports+json",
+ },
+ }, true /* retain */), "receive report about redirected resource");
+ assert_true(await reportExists({
+ // This happens to be where we're redirected to.
+ url: getURLForResourceWithNoPolicy(),
+ user_agent: navigator.userAgent,
+ type: "network-error",
+ body: {
+ method: "GET",
+ sampling_fraction: 1.0,
+ status_code: 200,
+ phase: "application",
+ type: "ok",
+ },
+ metadata: {
+ content_type: "application/reports+json",
+ },
+ }), "receive report about redirect target");
+ });
+ </script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/network-error-logging/sends-report-on-subdomain-dns-failure.https.html b/testing/web-platform/tests/network-error-logging/sends-report-on-subdomain-dns-failure.https.html
new file mode 100644
index 0000000000..8913857af8
--- /dev/null
+++ b/testing/web-platform/tests/network-error-logging/sends-report-on-subdomain-dns-failure.https.html
@@ -0,0 +1,46 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>
+ Test that include_subdomains policies report DNS failures for subdomains
+ </title>
+ <script src='/resources/testharness.js'></script>
+ <script src='/resources/testharnessreport.js'></script>
+ <script src='./support/nel.sub.js'></script>
+</head>
+<body>
+ <script>
+ nel_test(async t => {
+ // Make a request to a resource whose response headers include an
+ // include_subdomains NEL policy.
+ await fetchResourceWithIncludeSubdomainsPolicy();
+ // Make a request to another resource on a nonexistent subdomain of the
+ // above. Since the subdomain doesn't exist, the request should fail with
+ // a DNS error.
+ await fetchResourceWithNoPolicy('nonexistent').then((response) => {
+ assert_unreached("Request to nonexistent domain should fail");
+ }, (err) => {
+ // Silence the error, since it's expected.
+ });
+ // The include_subdomains policy that we just received should cover the
+ // second request, since include_subdomains policies can report on DNS
+ // errors, so the collector should have received a report about it.
+ assert_true(await reportExists({
+ url: getURLForResourceWithNoPolicy('nonexistent'),
+ user_agent: navigator.userAgent,
+ type: "network-error",
+ body: {
+ method: "GET",
+ sampling_fraction: 1.0,
+ status_code: 0,
+ phase: "dns",
+ type: "dns.name_not_resolved",
+ },
+ metadata: {
+ content_type: "application/reports+json",
+ },
+ }));
+ });
+ </script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/network-error-logging/sends-report-on-success-with-subdomain-policy.https.html b/testing/web-platform/tests/network-error-logging/sends-report-on-success-with-subdomain-policy.https.html
new file mode 100644
index 0000000000..fce12cd3e9
--- /dev/null
+++ b/testing/web-platform/tests/network-error-logging/sends-report-on-success-with-subdomain-policy.https.html
@@ -0,0 +1,40 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>
+ Test that NEL reports are sent for successful requests
+ </title>
+ <script src='/resources/testharness.js'></script>
+ <script src='/resources/testharnessreport.js'></script>
+ <script src='./support/nel.sub.js'></script>
+</head>
+<body>
+ <script>
+ nel_test(async t => {
+ // Make a request to a resource whose response headers include an
+ // include_subdomains NEL policy.
+ await fetchResourceWithIncludeSubdomainsPolicy();
+ // That policy should apply to the request that delivered it. Even though
+ // the policy has include_subdomains set, it SHOULD generate a full,
+ // non-downgraded report about the request, since the request has the
+ // same origin as the policy. (I.e., since the origins are the same, the
+ // include_subdomains setting is irrelevant.)
+ assert_true(await reportExists({
+ url: getURLForResourceWithIncludeSubdomainsPolicy(),
+ user_agent: navigator.userAgent,
+ type: "network-error",
+ body: {
+ method: "GET",
+ sampling_fraction: 1.0,
+ status_code: 200,
+ phase: "application",
+ type: "ok",
+ },
+ metadata: {
+ content_type: "application/reports+json",
+ },
+ }));
+ });
+ </script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/network-error-logging/sends-report-on-success.https.html b/testing/web-platform/tests/network-error-logging/sends-report-on-success.https.html
new file mode 100644
index 0000000000..68fddaa0c7
--- /dev/null
+++ b/testing/web-platform/tests/network-error-logging/sends-report-on-success.https.html
@@ -0,0 +1,37 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>
+ Test that NEL reports are sent for successful requests
+ </title>
+ <script src='/resources/testharness.js'></script>
+ <script src='/resources/testharnessreport.js'></script>
+ <script src='./support/nel.sub.js'></script>
+</head>
+<body>
+ <script>
+ nel_test(async t => {
+ // Make a request to a resource whose response headers include a NEL
+ // policy.
+ await fetchResourceWithBasicPolicy();
+ // That policy should apply to the request that delivered it, so the
+ // collector should have received a report about the request.
+ assert_true(await reportExists({
+ url: getURLForResourceWithBasicPolicy(),
+ user_agent: navigator.userAgent,
+ type: "network-error",
+ body: {
+ method: "GET",
+ sampling_fraction: 1.0,
+ status_code: 200,
+ phase: "application",
+ type: "ok",
+ },
+ metadata: {
+ content_type: "application/reports+json",
+ },
+ }));
+ });
+ </script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/network-error-logging/support/cached-for-one-minute.png b/testing/web-platform/tests/network-error-logging/support/cached-for-one-minute.png
new file mode 100644
index 0000000000..2fa1e0ac06
--- /dev/null
+++ b/testing/web-platform/tests/network-error-logging/support/cached-for-one-minute.png
Binary files differ
diff --git a/testing/web-platform/tests/network-error-logging/support/cached-for-one-minute.png.sub.headers b/testing/web-platform/tests/network-error-logging/support/cached-for-one-minute.png.sub.headers
new file mode 100644
index 0000000000..63d4c4de03
--- /dev/null
+++ b/testing/web-platform/tests/network-error-logging/support/cached-for-one-minute.png.sub.headers
@@ -0,0 +1 @@
+Cache-Control: public, max-age=60, must-revalidate
diff --git a/testing/web-platform/tests/network-error-logging/support/cached-with-validation.py b/testing/web-platform/tests/network-error-logging/support/cached-with-validation.py
new file mode 100644
index 0000000000..e5090e0392
--- /dev/null
+++ b/testing/web-platform/tests/network-error-logging/support/cached-with-validation.py
@@ -0,0 +1,17 @@
+ETAG = b'"123abc"'
+CONTENT_TYPE = b"text/plain"
+CONTENT = u"lorem ipsum dolor sit amet"
+
+def main(request, response):
+ # let caching kick in if possible (conditional GET)
+ etag = request.headers.get(b"If-None-Match", None)
+ if etag == ETAG:
+ response.headers.set(b"X-HTTP-STATUS", 304)
+ response.status = (304, b"Not Modified")
+ return u""
+
+ # cache miss, so respond with the actual content
+ response.status = (200, b"OK")
+ response.headers.set(b"ETag", ETAG)
+ response.headers.set(b"Content-Type", CONTENT_TYPE)
+ return CONTENT
diff --git a/testing/web-platform/tests/network-error-logging/support/clear-policy-pass.png b/testing/web-platform/tests/network-error-logging/support/clear-policy-pass.png
new file mode 100644
index 0000000000..2fa1e0ac06
--- /dev/null
+++ b/testing/web-platform/tests/network-error-logging/support/clear-policy-pass.png
Binary files differ
diff --git a/testing/web-platform/tests/network-error-logging/support/clear-policy-pass.png.sub.headers b/testing/web-platform/tests/network-error-logging/support/clear-policy-pass.png.sub.headers
new file mode 100644
index 0000000000..1085b8a987
--- /dev/null
+++ b/testing/web-platform/tests/network-error-logging/support/clear-policy-pass.png.sub.headers
@@ -0,0 +1,6 @@
+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
+Report-To: { "group": "nel-group", "max_age": 0, "endpoints": [] }
+NEL: {"max_age": 0}
diff --git a/testing/web-platform/tests/network-error-logging/support/lock.py b/testing/web-platform/tests/network-error-logging/support/lock.py
new file mode 100644
index 0000000000..0c5ae6b6f5
--- /dev/null
+++ b/testing/web-platform/tests/network-error-logging/support/lock.py
@@ -0,0 +1,48 @@
+# This file implements a shared lock that lets us ensure that the test cases in
+# this directory run serially. Each test case obtains this lock as its first
+# step, and releases it as its last. (The nel_test helper function in
+# nel.sub.js automates this process.) Because the lock needs to be shared
+# across all of the test cases, we use a hard-coded stash key. This hard-coded
+# key is a random UUID, which should not conflict with any other auto-generated
+# stash keys.
+
+import time
+
+_LOCK_KEY = b"67966d2e-a847-41d8-b7c3-5f6aee3375ba"
+_TIMEOUT = 5 # seconds
+
+def wait_for_lock(request):
+ t0 = time.time()
+ while time.time() - t0 < _TIMEOUT:
+ time.sleep(0.5)
+ value = request.server.stash.take(key=_LOCK_KEY)
+ if value is None:
+ return True
+ return False
+
+def lock(request, report_id):
+ with request.server.stash.lock:
+ # Loop until the lock is free
+ if not wait_for_lock(request):
+ return (503, [], b"Cannot obtain lock")
+ request.server.stash.put(key=_LOCK_KEY, value=report_id)
+ return b"Obtained lock for %s" % report_id
+
+def unlock(request, report_id):
+ with request.server.stash.lock:
+ lock_holder = request.server.stash.take(key=_LOCK_KEY)
+ if lock_holder != report_id:
+ # Return the lock holder to the stash
+ request.server.stash.put(key=_LOCK_KEY, value=lock_holder)
+ return (503, [], b"Cannot release lock held by %s" % lock_holder)
+ return b"Released lock for %s" % report_id
+
+def main(request, response):
+ op = request.GET.first(b"op")
+ report_id = request.GET.first(b"reportID")
+ if op == b"lock":
+ return lock(request, report_id)
+ elif op == b"unlock":
+ return unlock(request, report_id)
+ else:
+ return (400, [], b"Invalid op")
diff --git a/testing/web-platform/tests/network-error-logging/support/nel.sub.js b/testing/web-platform/tests/network-error-logging/support/nel.sub.js
new file mode 100644
index 0000000000..856af10cf7
--- /dev/null
+++ b/testing/web-platform/tests/network-error-logging/support/nel.sub.js
@@ -0,0 +1,293 @@
+const reportID = "{{$id:uuid()}}";
+
+/*
+ * NEL tests have to run serially, since the user agent maintains a global cache
+ * of Reporting and NEL policies, and we don't want the policies for multiple
+ * tests to interfere with each other. These functions (along with a Python
+ * handler in lock.py) implement a simple spin lock.
+ */
+
+function obtainNELLock() {
+ return fetch("/network-error-logging/support/lock.py?op=lock&reportID=" + reportID);
+}
+
+function releaseNELLock() {
+ return fetch("/network-error-logging/support/lock.py?op=unlock&reportID=" + reportID);
+}
+
+function nel_test(callback, name, properties) {
+ promise_test(async t => {
+ await obtainNELLock();
+ await clearReportingAndNELConfigurations();
+ await callback(t);
+ await releaseNELLock();
+ }, name, properties);
+}
+
+function nel_iframe_test(callback, name, properties) {
+ promise_test(async t => {
+ await obtainNELLock();
+ await clearReportingAndNELConfigurationsInIframe();
+ await callback(t);
+ await releaseNELLock();
+ }, name, properties);
+}
+
+/*
+ * Helper functions for constructing domain names that contain NEL policies.
+ */
+function _monitoredDomain(subdomain) {
+ if (subdomain == "www") {
+ return "{{hosts[alt][www]}}"
+ } else if (subdomain == "www1") {
+ return "{{hosts[alt][www1]}}"
+ } else if (subdomain == "www2") {
+ return "{{hosts[alt][www2]}}"
+ } else if (subdomain == "nonexistent") {
+ return "{{hosts[alt][nonexistent]}}"
+ } else {
+ return "{{hosts[alt][]}}"
+ }
+}
+
+function _getNELResourceURL(subdomain, suffix) {
+ return "https://" + _monitoredDomain(subdomain) +
+ ":{{ports[https][0]}}/network-error-logging/support/" + suffix;
+}
+
+/*
+ * Fetches a resource whose headers define a basic NEL policy (i.e., with no
+ * include_subdomains flag). We ensure that we request the resource from a
+ * different origin than is used for the main test case HTML file or for report
+ * uploads. This minimizes the number of reports that are generated for this
+ * policy.
+ */
+
+function getURLForResourceWithBasicPolicy(subdomain) {
+ return _getNELResourceURL(subdomain, "pass.png?id="+reportID+"&success_fraction=1.0");
+}
+
+function fetchResourceWithBasicPolicy(subdomain) {
+ const url = getURLForResourceWithBasicPolicy(subdomain);
+ return fetch(url, {mode: "no-cors"});
+}
+
+function fetchResourceWithZeroSuccessFractionPolicy(subdomain) {
+ const url = _getNELResourceURL(subdomain, "pass.png?id="+reportID+"&success_fraction=0.0");
+ return fetch(url, {mode: "no-cors"});
+}
+
+/*
+ * Similar to the above methods, but fetch resources in an iframe. Allows matching
+ * full context of reports sent from an iframe that's same-site relative to the domains
+ * a policy set.
+ */
+
+ function loadResourceWithBasicPolicyInIframe(subdomain) {
+ return loadResourceWithPolicyInIframe(
+ getURLForResourceWithBasicPolicy(subdomain));
+}
+
+function loadResourceWithZeroSuccessFractionPolicyInIframe(subdomain) {
+ return loadResourceWithPolicyInIframe(
+ _getNELResourceURL(subdomain, "pass.png?id="+reportID+"&success_fraction=0.0"));
+}
+
+function clearResourceWithBasicPolicyInIframe(subdomain) {
+ return loadResourceWithPolicyInIframe(
+ getURLForClearingConfiguration(subdomain));
+}
+
+function loadResourceWithPolicyInIframe(url) {
+ return new Promise((resolve, reject) => {
+ const frame = document.createElement('iframe');
+ frame.src = url;
+ frame.onload = () => resolve(frame);
+ frame.onerror = () => reject('failed to load ' + url);
+ document.body.appendChild(frame);
+ });
+}
+
+/*
+ * Fetches a resource whose headers define an include_subdomains NEL policy.
+ */
+
+function getURLForResourceWithIncludeSubdomainsPolicy(subdomain) {
+ return _getNELResourceURL(subdomain, "subdomains-pass.png?id="+reportID);
+}
+
+function fetchResourceWithIncludeSubdomainsPolicy(subdomain) {
+ const url = getURLForResourceWithIncludeSubdomainsPolicy(subdomain);
+ return fetch(url, {mode: "no-cors"});
+}
+
+/*
+ * Fetches a resource whose headers do NOT define a NEL policy. This may or may
+ * not generate a NEL report, depending on whether you've already successfully
+ * requested a resource from the same origin that included a NEL policy.
+ */
+
+function getURLForResourceWithNoPolicy(subdomain) {
+ return _getNELResourceURL(subdomain, "no-policy-pass.png");
+}
+
+function fetchResourceWithNoPolicy(subdomain) {
+ const url = getURLForResourceWithNoPolicy(subdomain);
+ return fetch(url, {mode: "no-cors"});
+}
+
+/*
+ * Fetches a resource that doesn't exist. This may or may not generate a NEL
+ * report, depending on whether you've already successfully requested a resource
+ * from the same origin that included a NEL policy.
+ */
+
+function getURLForMissingResource(subdomain) {
+ return _getNELResourceURL(subdomain, "nonexistent.png");
+}
+
+function fetchMissingResource(subdomain) {
+ const url = getURLForMissingResource(subdomain);
+ return fetch(url, {mode: "no-cors"});
+}
+
+/*
+ * Fetches a resource that can be cached without validation.
+ */
+
+function getURLForCachedResource(subdomain) {
+ return _getNELResourceURL(subdomain, "cached-for-one-minute.png");
+}
+
+function fetchCachedResource(subdomain) {
+ const url = getURLForCachedResource(subdomain);
+ return fetch(url, {mode: "no-cors"});
+}
+
+/*
+ * Fetches a resource that can be cached but requires validation.
+ */
+
+function getURLForValidatedCachedResource(subdomain) {
+ return _getNELResourceURL(subdomain, "cached-with-validation.py");
+}
+
+function fetchValidatedCachedResource(subdomain) {
+ const url = getURLForValidatedCachedResource(subdomain);
+ return fetch(url, {mode: "no-cors"});
+}
+
+/*
+ * Fetches a resource that redirects once before returning a successful
+ * response.
+ */
+
+function getURLForRedirectedResource(subdomain) {
+ return _getNELResourceURL(subdomain, "redirect.py?id="+reportID);
+}
+
+function fetchRedirectedResource(subdomain) {
+ const url = getURLForRedirectedResource(subdomain);
+ return fetch(url, {mode: "no-cors"});
+}
+
+/*
+ * Fetches resources that clear out any existing Reporting or NEL configurations
+ * for all origins that any test case might use.
+ */
+
+function getURLForClearingConfiguration(subdomain) {
+ return _getNELResourceURL(subdomain, "clear-policy-pass.png?id="+reportID);
+}
+
+async function clearReportingAndNELConfigurations(subdomain) {
+ await Promise.all([
+ fetch(getURLForClearingConfiguration(""), {mode: "no-cors"}),
+ fetch(getURLForClearingConfiguration("www"), {mode: "no-cors"}),
+ fetch(getURLForClearingConfiguration("www1"), {mode: "no-cors"}),
+ fetch(getURLForClearingConfiguration("www2"), {mode: "no-cors"}),
+ ]);
+ return;
+}
+
+async function clearReportingAndNELConfigurationsInIframe(subdomain) {
+ await Promise.all([
+ clearResourceWithBasicPolicyInIframe(""),
+ clearResourceWithBasicPolicyInIframe("www"),
+ clearResourceWithBasicPolicyInIframe("www1"),
+ clearResourceWithBasicPolicyInIframe("www2"),
+ ]);
+ return;
+}
+
+/*
+ * Returns whether all of the fields in obj1 also exist in obj2 with the same
+ * values. (Put another way, returns whether obj1 and obj2 are equal, ignoring
+ * any extra fields in obj2.)
+ */
+
+function _isSubsetOf(obj1, obj2) {
+ for (const prop in obj1) {
+ if (typeof obj1[prop] === 'object') {
+ if (typeof obj2[prop] !== 'object') {
+ return false;
+ }
+ if (!_isSubsetOf(obj1[prop], obj2[prop])) {
+ return false;
+ }
+ } else if (obj1[prop] != obj2[prop]) {
+ return false;
+ }
+ }
+ return true;
+}
+
+/*
+ * Verifies that a report was uploaded that contains all of the fields in
+ * expected.
+ */
+
+async function reportExists(expected, retain_reports) {
+ var timeout =
+ document.querySelector("meta[name=timeout][content=long]") ? 50 : 1;
+ var reportLocation =
+ "/reporting/resources/report.py?op=retrieve_report&timeout=" +
+ timeout + "&reportID=" + reportID;
+ if (retain_reports)
+ reportLocation += "&retain=1";
+ const response = await fetch(reportLocation);
+ const json = await response.json();
+ for (const report of json) {
+ if (_isSubsetOf(expected, report)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+/*
+ * Verifies that reports were uploaded that contains all of the fields in
+ * expected.
+ */
+
+async function reportsExist(expected_reports, retain_reports) {
+ const timeout = 10;
+ let reportLocation =
+ "/reporting/resources/report.py?op=retrieve_report&timeout=" +
+ timeout + "&reportID=" + reportID;
+ if (retain_reports)
+ reportLocation += "&retain";
+ // There must be the report of pass.png, so adding 1.
+ const min_count = expected_reports.length + 1;
+ reportLocation += "&min_count=" + min_count;
+ const response = await fetch(reportLocation);
+ const json = await response.json();
+ for (const expected of expected_reports) {
+ const found = json.some((report) => {
+ return _isSubsetOf(expected, report);
+ });
+ if (!found)
+ return false;
+ }
+ return true;
+}
diff --git a/testing/web-platform/tests/network-error-logging/support/no-policy-pass.png b/testing/web-platform/tests/network-error-logging/support/no-policy-pass.png
new file mode 100644
index 0000000000..2fa1e0ac06
--- /dev/null
+++ b/testing/web-platform/tests/network-error-logging/support/no-policy-pass.png
Binary files differ
diff --git a/testing/web-platform/tests/network-error-logging/support/pass.png b/testing/web-platform/tests/network-error-logging/support/pass.png
new file mode 100644
index 0000000000..2fa1e0ac06
--- /dev/null
+++ b/testing/web-platform/tests/network-error-logging/support/pass.png
Binary files differ
diff --git a/testing/web-platform/tests/network-error-logging/support/pass.png.sub.headers b/testing/web-platform/tests/network-error-logging/support/pass.png.sub.headers
new file mode 100644
index 0000000000..4cf91f1359
--- /dev/null
+++ b/testing/web-platform/tests/network-error-logging/support/pass.png.sub.headers
@@ -0,0 +1,6 @@
+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
+Report-To: { "group": "nel-group", "max_age": 10886400, "endpoints": [{ "url": "https://{{hosts[][www]}}:{{ports[https][0]}}/reporting/resources/report.py?op=put&reportID={{GET[id]}}" }] }
+NEL: {"report_to": "nel-group", "max_age": 10886400, "success_fraction": {{GET[success_fraction]}}}
diff --git a/testing/web-platform/tests/network-error-logging/support/redirect.py b/testing/web-platform/tests/network-error-logging/support/redirect.py
new file mode 100644
index 0000000000..7da4504764
--- /dev/null
+++ b/testing/web-platform/tests/network-error-logging/support/redirect.py
@@ -0,0 +1,3 @@
+# Always redirects to no-policy-pass.png.
+def main(request, response):
+ return 302, [(b"Location", b"no-policy-pass.png")], u""
diff --git a/testing/web-platform/tests/network-error-logging/support/subdomains-pass.png b/testing/web-platform/tests/network-error-logging/support/subdomains-pass.png
new file mode 100644
index 0000000000..2fa1e0ac06
--- /dev/null
+++ b/testing/web-platform/tests/network-error-logging/support/subdomains-pass.png
Binary files differ
diff --git a/testing/web-platform/tests/network-error-logging/support/subdomains-pass.png.sub.headers b/testing/web-platform/tests/network-error-logging/support/subdomains-pass.png.sub.headers
new file mode 100644
index 0000000000..ae6ce3f43c
--- /dev/null
+++ b/testing/web-platform/tests/network-error-logging/support/subdomains-pass.png.sub.headers
@@ -0,0 +1,6 @@
+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
+Report-To: { "group": "nel-group", "max_age": 10886400, "include_subdomains": true, "endpoints": [{ "url": "https://{{hosts[][www]}}:{{ports[https][0]}}/reporting/resources/report.py?op=put&reportID={{GET[id]}}" }] }
+NEL: {"report_to": "nel-group", "max_age": 10886400, "include_subdomains": true, "success_fraction": 1.0}