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;
}