512 lines
14 KiB
JavaScript
512 lines
14 KiB
JavaScript
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}`);
|
|
}
|