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