LibWeb: Use DocumentLoadTimingInfo values in PerformanceTiming interface

This commit is contained in:
Tim Ledbetter 2025-01-06 10:26:55 +00:00 committed by Andreas Kling
commit e03fec2a15
Notes: github-actions[bot] 2025-01-11 10:12:48 +00:00
7 changed files with 481 additions and 7 deletions

View file

@ -0,0 +1,56 @@
/*
author: W3C http://www.w3.org/
help: http://www.w3.org/TR/navigation-timing/#sec-window.performance-attribute
*/
var performanceNamespace = window.performance;
var namespace_check = false;
function wp_test(func, msg, properties)
{
// only run the namespace check once
if (!namespace_check)
{
namespace_check = true;
if (performanceNamespace === undefined || performanceNamespace == null)
{
// show a single error that window.performance is undefined
// The window.performance attribute provides a hosting area for performance related attributes.
test(function() { assert_true(performanceNamespace !== undefined && performanceNamespace != null, "window.performance is defined and not null"); }, "window.performance is defined and not null.");
}
}
test(func, msg, properties);
}
function test_true(value, msg, properties)
{
wp_test(function () { assert_true(value, msg); }, msg, properties);
}
function test_equals(value, equals, msg, properties)
{
wp_test(function () { assert_equals(value, equals, msg); }, msg, properties);
}
// assert for every entry in `expectedEntries`, there is a matching entry _somewhere_ in `actualEntries`
function test_entries(actualEntries, expectedEntries) {
test_equals(actualEntries.length, expectedEntries.length)
expectedEntries.forEach(function (expectedEntry) {
var foundEntry = actualEntries.find(function (actualEntry) {
return typeof Object.keys(expectedEntry).find(function (key) {
return actualEntry[key] !== expectedEntry[key]
}) === 'undefined'
})
test_true(!!foundEntry, `Entry ${JSON.stringify(expectedEntry)} could not be found.`)
if (foundEntry) {
assert_object_equals(foundEntry.toJSON(), expectedEntry)
}
})
}
function delayedLoadListener(callback) {
window.addEventListener('load', function() {
// TODO(cvazac) Remove this setTimeout when spec enforces sync entries.
step_timeout(callback, 0)
})
}

View file

@ -0,0 +1,66 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>functionality test of window.performance.measure</title>
<link rel="author" title="Intel" href="http://www.intel.com/" />
<link rel="help" href="http://www.w3.org/TR/user-timing/#extensions-performance-interface"/>
<script src="../resources/testharness.js"></script>
<script src="../resources/testharnessreport.js"></script>
<script src="../common/performance-timeline-utils.js"></script>
<script src="resources/webperftestharness.js"></script>
<script src="resources/webperftestharnessextension.js"></script>
<script>
setup({ explicit_done: true });
function onload_test()
{
const measures_for_timing_order = [
['nav2now', 'navigationStart'],
['loadTime', 'navigationStart', 'loadEventEnd'],
['loadEventEnd2a', 'loadEventEnd', 'abc'],
['nav2a', 'navigationStart', 'abc'],
['domComplete2a', 'domComplete', 'abc'],
['negativeValue', 1, 'navigationStart'],
];
const context = new PerformanceContext(window.performance);
mark_names.forEach(function(name) {
context.mark(name);
});
measures_for_timing_order.forEach(context.initialMeasures, context);
test_greater_than(context.getEntriesByName('nav2now', 'measure')[0].duration, 0, 'Measure of navigationStart to now should be positive value.');
test_greater_than(context.getEntriesByName('loadTime', 'measure')[0].duration, 0, 'Measure of navigationStart to loadEventEnd should be positive value.');
test_greater_than(0, context.getEntriesByName('negativeValue', 'measure')[0].duration, 'Measure of current mark to navigationStart should be negative value.');
test_equals(context.getEntriesByName('loadTime', 'measure')[0].duration + context.getEntriesByName('loadEventEnd2a', 'measure')[0].duration, context.getEntriesByName('nav2a', 'measure')[0].duration, 'loadTime plus loadEventEnd to a mark "a" should equal to navigationStart to "a".');
// We later assert that time has passed between setting one set of marks and another set.
// However, this assertion will fail if the test executes fast enough such that the marks occur
// at the same clock time. This is more likely in browsers such as Firefox that reduce the
// precision of the clock exposed through this API to mitigate timing attacks. To mitigate the
// test failure, we sleep. Firefox may round timestamps to the nearest millisecond in either
// direction - e.g. 10ms & 11.999ms may both round to 11ms - so we need to sleep at least 2ms to
// avoid test failures. To be safe, we sleep 3ms.
sleep_milliseconds(3);
// Following cases test for scenarios that measure names are tied twice.
mark_names.forEach(function(name) {
context.mark(name);
});
measures_for_timing_order.forEach(context.initialMeasures, context);
test_greater_than(context.getEntriesByName('nav2now', 'measure')[1].duration, context.getEntriesByName('nav2now', 'measure')[0].duration, 'Second measure of current mark to navigationStart should be negative value.');
test_equals(context.getEntriesByName('loadTime', 'measure')[0].duration, context.getEntriesByName('loadTime', 'measure')[1].duration, 'Measures of loadTime should have same duration.');
test_greater_than(context.getEntriesByName('domComplete2a', 'measure')[1].duration, context.getEntriesByName('domComplete2a', 'measure')[0].duration, 'Measure from domComplete event to most recent mark "a" should have longer duration.');
test_greater_than(context.getEntriesByName('negativeValue', 'measure')[0].duration, context.getEntriesByName('negativeValue', 'measure')[1].duration, 'Measure from most recent mark to navigationStart should have longer duration.');
done();
}
</script>
</head>
<body onload="setTimeout(onload_test,0)">
<h1>Description</h1>
<p>This test validates functionality of the interface window.performance.measure using keywords from the Navigation Timing spec.</p>
<div id="log"></div>
</body>
</html>

View file

@ -0,0 +1,124 @@
//
// Helper functions for User Timing tests
//
var timingAttributes = [
"navigationStart",
"unloadEventStart",
"unloadEventEnd",
"redirectStart",
"redirectEnd",
"fetchStart",
"domainLookupStart",
"domainLookupEnd",
"connectStart",
"connectEnd",
"secureConnectionStart",
"requestStart",
"responseStart",
"responseEnd",
"domLoading",
"domInteractive",
"domContentLoadedEventStart",
"domContentLoadedEventEnd",
"domComplete",
"loadEventStart",
"loadEventEnd"
];
function has_required_interfaces()
{
if (window.performance.mark == undefined ||
window.performance.clearMarks == undefined ||
window.performance.measure == undefined ||
window.performance.clearMeasures == undefined ||
window.performance.getEntriesByName == undefined ||
window.performance.getEntriesByType == undefined ||
window.performance.getEntries == undefined) {
return false;
}
return true;
}
function test_namespace(child_name, skip_root)
{
if (skip_root === undefined) {
var msg = 'window.performance is defined';
wp_test(function () { assert_not_equals(performanceNamespace, undefined, msg); }, msg);
}
if (child_name !== undefined) {
var msg2 = 'window.performance.' + child_name + ' is defined';
wp_test(function() { assert_not_equals(performanceNamespace[child_name], undefined, msg2); }, msg2);
}
}
function test_attribute_exists(parent_name, attribute_name, properties)
{
var msg = 'window.performance.' + parent_name + '.' + attribute_name + ' is defined.';
wp_test(function() { assert_not_equals(performanceNamespace[parent_name][attribute_name], undefined, msg); }, msg, properties);
}
function test_enum(parent_name, enum_name, value, properties)
{
var msg = 'window.performance.' + parent_name + '.' + enum_name + ' is defined.';
wp_test(function() { assert_not_equals(performanceNamespace[parent_name][enum_name], undefined, msg); }, msg, properties);
msg = 'window.performance.' + parent_name + '.' + enum_name + ' = ' + value;
wp_test(function() { assert_equals(performanceNamespace[parent_name][enum_name], value, msg); }, msg, properties);
}
function test_timing_order(attribute_name, greater_than_attribute, properties)
{
// ensure it's not 0 first
var msg = "window.performance.timing." + attribute_name + " > 0";
wp_test(function() { assert_true(performanceNamespace.timing[attribute_name] > 0, msg); }, msg, properties);
// ensure it's in the right order
msg = "window.performance.timing." + attribute_name + " >= window.performance.timing." + greater_than_attribute;
wp_test(function() { assert_true(performanceNamespace.timing[attribute_name] >= performanceNamespace.timing[greater_than_attribute], msg); }, msg, properties);
}
function test_timing_greater_than(attribute_name, greater_than, properties)
{
var msg = "window.performance.timing." + attribute_name + " > " + greater_than;
test_greater_than(performanceNamespace.timing[attribute_name], greater_than, msg, properties);
}
function test_timing_equals(attribute_name, equals, msg, properties)
{
var test_msg = msg || "window.performance.timing." + attribute_name + " == " + equals;
test_equals(performanceNamespace.timing[attribute_name], equals, test_msg, properties);
}
//
// Non-test related helper functions
//
function sleep_milliseconds(n)
{
var start = new Date().getTime();
while (true) {
if ((new Date().getTime() - start) >= n) break;
}
}
//
// Common helper functions
//
function test_greater_than(value, greater_than, msg, properties)
{
wp_test(function () { assert_greater_than(value, greater_than, msg); }, msg, properties);
}
function test_greater_or_equals(value, greater_than, msg, properties)
{
wp_test(function () { assert_greater_than_equal(value, greater_than, msg); }, msg, properties);
}
function test_not_equals(value, notequals, msg, properties)
{
wp_test(function() { assert_not_equals(value, notequals, msg); }, msg, properties);
}

View file

@ -0,0 +1,202 @@
//
// Helper functions for User Timing tests
//
var mark_names = [
'',
'1',
'abc',
];
var measures = [
[''],
['2', 1],
['aaa', 'navigationStart', ''],
];
function test_method_exists(method, method_name, properties)
{
var msg;
if (typeof method === 'function')
msg = 'performance.' + method.name + ' is supported!';
else
msg = 'performance.' + method_name + ' is supported!';
wp_test(function() { assert_equals(typeof method, 'function', msg); }, msg, properties);
}
function test_method_throw_exception(func_str, exception, msg)
{
let exception_name;
let test_func;
if (typeof exception == "function") {
exception_name = exception.name;
test_func = assert_throws_js;
} else {
exception_name = exception;
test_func = assert_throws_dom;
}
var msg = 'Invocation of ' + func_str + ' should throw ' + exception_name + ' Exception.';
wp_test(function() { test_func(exception, function() {eval(func_str)}, msg); }, msg);
}
function test_noless_than(value, greater_than, msg, properties)
{
wp_test(function () { assert_true(value >= greater_than, msg); }, msg, properties);
}
function test_fail(msg, properties)
{
wp_test(function() { assert_unreached(); }, msg, properties);
}
function test_resource_entries(entries, expected_entries)
{
// This is slightly convoluted so that we can sort the output.
var actual_entries = {};
var origin = window.location.protocol + "//" + window.location.host;
for (var i = 0; i < entries.length; ++i) {
var entry = entries[i];
var found = false;
for (var expected_entry in expected_entries) {
if (entry.name == origin + expected_entry) {
found = true;
if (expected_entry in actual_entries) {
test_fail(expected_entry + ' is not expected to have duplicate entries');
}
actual_entries[expected_entry] = entry;
break;
}
}
if (!found) {
test_fail(entries[i].name + ' is not expected to be in the Resource Timing buffer');
}
}
sorted_urls = [];
for (var i in actual_entries) {
sorted_urls.push(i);
}
sorted_urls.sort();
for (var i in sorted_urls) {
var url = sorted_urls[i];
test_equals(actual_entries[url].initiatorType,
expected_entries[url],
origin + url + ' is expected to have initiatorType ' + expected_entries[url]);
}
for (var j in expected_entries) {
if (!(j in actual_entries)) {
test_fail(origin + j + ' is expected to be in the Resource Timing buffer');
}
}
}
function performance_entrylist_checker(type)
{
const entryType = type;
function entry_check(entry, expectedNames, testDescription = '')
{
const msg = testDescription + 'Entry \"' + entry.name + '\" should be one that we have set.';
wp_test(function() { assert_in_array(entry.name, expectedNames, msg); }, msg);
test_equals(entry.entryType, entryType, testDescription + 'entryType should be \"' + entryType + '\".');
if (type === "measure") {
test_true(isFinite(entry.startTime), testDescription + 'startTime should be a number.');
test_true(isFinite(entry.duration), testDescription + 'duration should be a number.');
} else if (type === "mark") {
test_greater_than(entry.startTime, 0, testDescription + 'startTime should greater than 0.');
test_equals(entry.duration, 0, testDescription + 'duration of mark should be 0.');
}
}
function entrylist_order_check(entryList)
{
let inOrder = true;
for (let i = 0; i < entryList.length - 1; ++i)
{
if (entryList[i + 1].startTime < entryList[i].startTime) {
inOrder = false;
break;
}
}
return inOrder;
}
function entrylist_check(entryList, expectedLength, expectedNames, testDescription = '')
{
test_equals(entryList.length, expectedLength, testDescription + 'There should be ' + expectedLength + ' entries.');
test_true(entrylist_order_check(entryList), testDescription + 'Entries in entrylist should be in order.');
for (let i = 0; i < entryList.length; ++i)
{
entry_check(entryList[i], expectedNames, testDescription + 'Entry_list ' + i + '. ');
}
}
return{"entrylist_check":entrylist_check};
}
function PerformanceContext(context)
{
this.performanceContext = context;
}
PerformanceContext.prototype =
{
initialMeasures: function(item, index, array)
{
this.performanceContext.measure.apply(this.performanceContext, item);
},
mark: function()
{
this.performanceContext.mark.apply(this.performanceContext, arguments);
},
measure: function()
{
this.performanceContext.measure.apply(this.performanceContext, arguments);
},
clearMarks: function()
{
this.performanceContext.clearMarks.apply(this.performanceContext, arguments);
},
clearMeasures: function()
{
this.performanceContext.clearMeasures.apply(this.performanceContext, arguments);
},
getEntries: function()
{
return this.performanceContext.getEntries.apply(this.performanceContext, arguments);
},
getEntriesByType: function()
{
return this.performanceContext.getEntriesByType.apply(this.performanceContext, arguments);
},
getEntriesByName: function()
{
return this.performanceContext.getEntriesByName.apply(this.performanceContext, arguments);
},
setResourceTimingBufferSize: function()
{
return this.performanceContext.setResourceTimingBufferSize.apply(this.performanceContext, arguments);
},
registerResourceTimingBufferFullCallback: function(func)
{
this.performanceContext.onresourcetimingbufferfull = func;
},
clearResourceTimings: function()
{
this.performanceContext.clearResourceTimings.apply(this.performanceContext, arguments);
}
};