diff options
Diffstat (limited to 'testing/web-platform/tests/resource-timing/resources/entry-invariants.js')
-rw-r--r-- | testing/web-platform/tests/resource-timing/resources/entry-invariants.js | 512 |
1 files changed, 512 insertions, 0 deletions
diff --git a/testing/web-platform/tests/resource-timing/resources/entry-invariants.js b/testing/web-platform/tests/resource-timing/resources/entry-invariants.js new file mode 100644 index 0000000000..78d43d1b01 --- /dev/null +++ b/testing/web-platform/tests/resource-timing/resources/entry-invariants.js @@ -0,0 +1,512 @@ +const await_with_timeout = async (delay, message, promise, cleanup = ()=>{}) => { + let timeout_id; + const timeout = new Promise((_, reject) => { + timeout_id = step_timeout(() => + reject(new DOMException(message, "TimeoutError")), delay) + }); + let result = null; + try { + result = await Promise.race([promise, timeout]); + clearTimeout(timeout_id); + } finally { + cleanup(); + } + return result; +}; + +// Asserts that the given attributes are present in 'entry' and hold equal +// values. +const assert_all_equal_ = (entry, attributes) => { + let first = attributes[0]; + attributes.slice(1).forEach(other => { + assert_equals(entry[first], entry[other], + `${first} should be equal to ${other}`); + }); +} + +// Asserts that the given attributes are present in 'entry' and hold values +// that are sorted in the same order as given in 'attributes'. +const assert_ordered_ = (entry, attributes) => { + let before = attributes[0]; + attributes.slice(1).forEach(after => { + assert_greater_than_equal(entry[after], entry[before], + `${after} should be greater than ${before}`); + before = after; + }); +} + +// Asserts that the given attributes are present in 'entry' and hold a value of +// 0. +const assert_zeroed_ = (entry, attributes) => { + attributes.forEach(attribute => { + assert_equals(entry[attribute], 0, `${attribute} should be 0`); + }); +} + +// Asserts that the given attributes are present in 'entry' and hold a value of +// 0 or more. +const assert_not_negative_ = (entry, attributes) => { + attributes.forEach(attribute => { + assert_greater_than_equal(entry[attribute], 0, + `${attribute} should be greater than or equal to 0`); + }); +} + +// Asserts that the given attributes are present in 'entry' and hold a value +// greater than 0. +const assert_positive_ = (entry, attributes) => { + attributes.forEach(attribute => { + assert_greater_than(entry[attribute], 0, + `${attribute} should be greater than 0`); + }); +} + +const invariants = { + // Asserts that attributes of the given PerformanceResourceTiming entry match + // what the spec dictates for any resource fetched over HTTP without + // redirects but passing the Timing-Allow-Origin checks. + assert_tao_pass_no_redirect_http: entry => { + assert_ordered_(entry, [ + "fetchStart", + "domainLookupStart", + "domainLookupEnd", + "connectStart", + "connectEnd", + "requestStart", + "responseStart", + "responseEnd", + ]); + + assert_zeroed_(entry, [ + "workerStart", + "secureConnectionStart", + "redirectStart", + "redirectEnd", + ]); + + assert_not_negative_(entry, [ + "duration", + ]); + + assert_positive_(entry, [ + "fetchStart", + "transferSize", + ]); + }, + + // Like assert_tao_pass_no_redirect_http but for empty response bodies. + assert_tao_pass_no_redirect_http_empty: entry => { + assert_ordered_(entry, [ + "fetchStart", + "domainLookupStart", + "domainLookupEnd", + "connectStart", + "connectEnd", + "requestStart", + "responseStart", + "responseEnd", + ]); + + assert_zeroed_(entry, [ + "workerStart", + "secureConnectionStart", + "redirectStart", + "redirectEnd", + ]); + + assert_not_negative_(entry, [ + "duration", + ]); + + assert_positive_(entry, [ + "fetchStart", + "transferSize", + ]); + }, + + // Like assert_tao_pass_no_redirect_http but for resources fetched over HTTPS + assert_tao_pass_no_redirect_https: entry => { + assert_ordered_(entry, [ + "fetchStart", + "domainLookupStart", + "domainLookupEnd", + "secureConnectionStart", + "connectStart", + "connectEnd", + "requestStart", + "responseStart", + "responseEnd", + ]); + + assert_zeroed_(entry, [ + "workerStart", + "redirectStart", + "redirectEnd", + ]); + + assert_not_negative_(entry, [ + "duration", + ]); + + assert_positive_(entry, [ + "fetchStart", + "transferSize", + ]); + }, + + // Like assert_tao_pass_no_redirect_https but for resources that did encounter + // at least one HTTP redirect. + assert_tao_pass_with_redirect_https: entry => { + assert_ordered_(entry, [ + "fetchStart", + "redirectStart", + "redirectEnd", + "domainLookupStart", + "domainLookupEnd", + "secureConnectionStart", + "connectStart", + "connectEnd", + "requestStart", + "responseStart", + "responseEnd", + ]); + + assert_zeroed_(entry, [ + "workerStart", + ]); + + assert_not_negative_(entry, [ + "duration", + ]); + + assert_positive_(entry, [ + "fetchStart", + "transferSize", + ]); + }, + + // Like assert_tao_pass_no_redirect_http but, since the resource's bytes + // won't be retransmitted, the encoded and decoded sizes must be zero. + assert_tao_pass_304_not_modified_http: entry => { + assert_ordered_(entry, [ + "fetchStart", + "domainLookupStart", + "domainLookupEnd", + "connectStart", + "connectEnd", + "requestStart", + "responseStart", + "responseEnd", + ]); + + assert_zeroed_(entry, [ + "workerStart", + "secureConnectionStart", + "redirectStart", + "redirectEnd", + ]); + + assert_not_negative_(entry, [ + "duration", + ]); + + assert_positive_(entry, [ + "fetchStart", + "transferSize", + ]); + }, + + // Like assert_tao_pass_304_not_modified_http but for resources fetched over + // HTTPS. + assert_tao_pass_304_not_modified_https: entry => { + assert_ordered_(entry, [ + "fetchStart", + "domainLookupStart", + "domainLookupEnd", + "secureConnectionStart", + "connectStart", + "connectEnd", + "requestStart", + "responseStart", + "responseEnd", + ]); + + assert_zeroed_(entry, [ + "workerStart", + "redirectStart", + "redirectEnd", + ]); + + assert_not_negative_(entry, [ + "duration", + ]); + + assert_positive_(entry, [ + "fetchStart", + "transferSize", + ]); + }, + + // Asserts that attributes of the given PerformanceResourceTiming entry match + // what the spec dictates for any resource subsequently fetched over a + // persistent connection. When this happens, we expect that certain + // attributes describing transport layer behaviour will be equal. + assert_connection_reused: entry => { + assert_all_equal_(entry, [ + "fetchStart", + "connectStart", + "connectEnd", + "domainLookupStart", + "domainLookupEnd", + ]); + }, + + // Asserts that attributes of the given PerformanceResourceTiming entry match + // what the spec dictates for any resource fetched over HTTP through an HTTP + // redirect. + assert_same_origin_redirected_resource: entry => { + assert_positive_(entry, [ + "redirectStart", + ]); + + assert_equals(entry.redirectStart, entry.startTime, + "redirectStart should be equal to startTime"); + + assert_ordered_(entry, [ + "redirectStart", + "redirectEnd", + "fetchStart", + "domainLookupStart", + "domainLookupEnd", + "connectStart", + ]); + }, + + // Asserts that attributes of the given PerformanceResourceTiming entry match + // what the spec dictates for any resource fetched over HTTPS through a + // cross-origin redirect. + // (e.g. GET http://remote.com/foo => 302 Location: https://remote.com/foo) + assert_cross_origin_redirected_resource: entry => { + assert_zeroed_(entry, [ + "redirectStart", + "redirectEnd", + "domainLookupStart", + "domainLookupEnd", + "connectStart", + "connectEnd", + "secureConnectionStart", + "requestStart", + "responseStart", + ]); + + assert_positive_(entry, [ + "fetchStart", + "responseEnd", + ]); + + assert_ordered_(entry, [ + "fetchStart", + "responseEnd", + ]); + }, + + // Asserts that attributes of the given PerformanceResourceTiming entry match + // what the spec dictates when + // 1. An HTTP request is made for a same-origin resource. + // 2. The response to 1 is an HTTP redirect (like a 302). + // 3. The location from 2 is a cross-origin HTTPS URL. + // 4. The response to fetching the URL from 3 does not set a matching TAO header. + assert_http_to_cross_origin_redirected_resource: entry => { + assert_zeroed_(entry, [ + "redirectStart", + "redirectEnd", + "domainLookupStart", + "domainLookupEnd", + "connectStart", + "connectEnd", + "secureConnectionStart", + "requestStart", + "responseStart", + ]); + + assert_positive_(entry, [ + "fetchStart", + "responseEnd", + ]); + + assert_ordered_(entry, [ + "fetchStart", + "responseEnd", + ]); + }, + + // Asserts that attributes of the given PerformanceResourceTiming entry match + // what the spec dictates when + // 1. An HTTPS request is made for a same-origin resource. + // 2. The response to 1 is an HTTP redirect (like a 302). + // 3. The location from 2 is a cross-origin HTTPS URL. + // 4. The response to fetching the URL from 3 sets a matching TAO header. + assert_tao_enabled_cross_origin_redirected_resource: entry => { + assert_positive_(entry, [ + "redirectStart", + ]); + assert_ordered_(entry, [ + "redirectStart", + "redirectEnd", + "fetchStart", + "domainLookupStart", + "domainLookupEnd", + "connectStart", + "secureConnectionStart", + "connectEnd", + "requestStart", + "responseStart", + "responseEnd", + ]); + }, + + // Asserts that attributes of the given PerformanceResourceTiming entry match + // what the spec dictates when + // 1. An HTTP request is made for a same-origin resource + // 2. The response to 1 is an HTTP redirect (like a 302). + // 3. The location from 2 is a cross-origin HTTPS URL. + // 4. The response to fetching the URL from 3 sets a matching TAO header. + assert_http_to_tao_enabled_cross_origin_https_redirected_resource: entry => { + assert_zeroed_(entry, [ + // Note that, according to the spec, the secureConnectionStart attribute + // should describe the connection for the first resource request when + // there are redirects. Since the initial request is over HTTP, + // secureConnectionStart must be 0. + "secureConnectionStart", + ]); + assert_positive_(entry, [ + "redirectStart", + ]); + assert_ordered_(entry, [ + "redirectStart", + "redirectEnd", + "fetchStart", + "domainLookupStart", + "domainLookupEnd", + "connectStart", + "connectEnd", + "requestStart", + "responseStart", + "responseEnd", + ]); + }, + + assert_same_origin_redirected_from_cross_origin_resource: entry => { + assert_zeroed_(entry, [ + "workerStart", + "redirectStart", + "redirectEnd", + "domainLookupStart", + "domainLookupEnd", + "connectStart", + "connectEnd", + "secureConnectionStart", + "requestStart", + "responseStart", + "transferSize", + ]); + + assert_ordered_(entry, [ + "fetchStart", + "responseEnd", + ]); + + assert_equals(entry.fetchStart, entry.startTime, + "fetchStart must equal startTime"); + }, + + assert_tao_failure_resource: entry => { + assert_equals(entry.entryType, "resource", "entryType must always be 'resource'"); + + assert_positive_(entry, [ + "startTime", + ]); + + assert_not_negative_(entry, [ + "duration", + ]); + + assert_zeroed_(entry, [ + "redirectStart", + "redirectEnd", + "domainLookupStart", + "domainLookupEnd", + "connectStart", + "connectEnd", + "secureConnectionStart", + "requestStart", + "responseStart", + "transferSize", + ]); + } + +}; + +const attribute_test_internal = (loader, path, validator, run_test, test_label) => { + promise_test( + async () => { + let loaded_entry = new Promise((resolve, reject) => { + new PerformanceObserver((entry_list, self) => { + try { + const name_matches = entry_list.getEntries().forEach(entry => { + if (entry.name.includes(path)) { + resolve(entry); + } + }); + } catch(e) { + // By surfacing exceptions through the Promise interface, tests can + // fail fast with a useful message instead of timing out. + reject(e); + } + }).observe({"type": "resource"}); + }); + + await loader(path, validator); + const entry = await await_with_timeout(2000, + "Timeout was reached before entry fired", + loaded_entry); + assert_not_equals(entry, null, 'No entry was received'); + run_test(entry); + }, test_label); +}; + +// Given a resource-loader, a path (a relative path or absolute URL), and a +// PerformanceResourceTiming test, applies the loader to the resource path +// and tests the resulting PerformanceResourceTiming entry. +const attribute_test = (loader, path, run_test, test_label) => { + attribute_test_internal(loader, path, () => {}, run_test, test_label); +}; + +// Similar to attribute test, but on top of that, validates the added element, +// to ensure the test does what it intends to do. +const attribute_test_with_validator = (loader, path, validator, run_test, test_label) => { + attribute_test_internal(loader, path, validator, run_test, test_label); +}; + +const network_error_entry_test = (originalURL, args, label, loader) => { + const url = new URL(originalURL, location.href); + const search = new URLSearchParams(url.search.substr(1)); + const timeBefore = performance.now(); + + // Load using `fetch()`, unless we're given a specific loader for this test. + loader ??= () => new Promise(resolve => fetch(url, args).catch(resolve)); + + attribute_test( + loader, url, + () => { + const timeAfter = performance.now(); + const names = performance.getEntriesByType('resource').filter(e => e.initiatorType === 'fetch').map(e => e.name); + const entries = performance.getEntriesByName(url.toString()); + assert_equals(entries.length, 1, 'resource timing entry for network error'); + const entry = entries[0] + assert_equals(entry.startTime, entry.fetchStart, 'startTime and fetchStart should be equal'); + assert_greater_than_equal(entry.startTime, timeBefore, 'startTime and fetchStart should be greater than the time before fetching'); + assert_greater_than_equal(timeAfter, entry.responseEnd, 'endTime should be less than the time right after returning from the fetch'); + invariants.assert_tao_failure_resource(entry); + }, `A ResourceTiming entry should be created for network error of type ${label}`); +} |