summaryrefslogtreecommitdiffstats
path: root/testing/web-platform/tests/resource-timing/resources/entry-invariants.js
diff options
context:
space:
mode:
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.js512
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}`);
+}