mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-04-22 04:25:13 +00:00
LibWeb: Use DocumentLoadTimingInfo values in PerformanceTiming interface
This commit is contained in:
parent
f8b8c9c4a4
commit
e03fec2a15
Notes:
github-actions[bot]
2025-01-11 10:12:48 +00:00
Author: https://github.com/tcl3 Commit: https://github.com/LadybirdBrowser/ladybird/commit/e03fec2a155 Pull-request: https://github.com/LadybirdBrowser/ladybird/pull/3156
7 changed files with 481 additions and 7 deletions
|
@ -24,4 +24,13 @@ void PerformanceTiming::initialize(JS::Realm& realm)
|
|||
WEB_SET_PROTOTYPE_FOR_INTERFACE(PerformanceTiming);
|
||||
}
|
||||
|
||||
DOM::DocumentLoadTimingInfo const& PerformanceTiming::document_load_timing_info() const
|
||||
{
|
||||
auto& global_object = HTML::relevant_global_object(*this);
|
||||
VERIFY(is<HTML::Window>(global_object));
|
||||
auto& window = static_cast<HTML::Window&>(global_object);
|
||||
auto document = window.document();
|
||||
return document->load_timing_info();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
/*
|
||||
* Copyright (c) 2021, Andreas Kling <andreas@ladybird.org>
|
||||
* Copyright (c) 2025, Tim Ledbetter <tim.ledbetter@ladybird.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
@ -19,7 +20,7 @@ public:
|
|||
|
||||
~PerformanceTiming();
|
||||
|
||||
u64 navigation_start() { return 0; }
|
||||
u64 navigation_start() { return document_load_timing_info().navigation_start_time; }
|
||||
u64 unload_event_start() { return 0; }
|
||||
u64 unload_event_end() { return 0; }
|
||||
u64 redirect_start() { return 0; }
|
||||
|
@ -34,16 +35,18 @@ public:
|
|||
u64 response_start() { return 0; }
|
||||
u64 response_end() { return 0; }
|
||||
u64 dom_loading() { return 0; }
|
||||
u64 dom_interactive() { return 0; }
|
||||
u64 dom_content_loaded_event_start() { return 0; }
|
||||
u64 dom_content_loaded_event_end() { return 0; }
|
||||
u64 dom_complete() { return 0; }
|
||||
u64 load_event_start() { return 0; }
|
||||
u64 load_event_end() { return 0; }
|
||||
u64 dom_interactive() { return document_load_timing_info().dom_interactive_time; }
|
||||
u64 dom_content_loaded_event_start() { return document_load_timing_info().dom_content_loaded_event_start_time; }
|
||||
u64 dom_content_loaded_event_end() { return document_load_timing_info().dom_content_loaded_event_end_time; }
|
||||
u64 dom_complete() { return document_load_timing_info().dom_complete_time; }
|
||||
u64 load_event_start() { return document_load_timing_info().load_event_start_time; }
|
||||
u64 load_event_end() { return document_load_timing_info().load_event_end_time; }
|
||||
|
||||
private:
|
||||
explicit PerformanceTiming(JS::Realm&);
|
||||
|
||||
DOM::DocumentLoadTimingInfo const& document_load_timing_info() const;
|
||||
|
||||
virtual void initialize(JS::Realm&) override;
|
||||
};
|
||||
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
Harness status: OK
|
||||
|
||||
Found 8 tests
|
||||
|
||||
7 Pass
|
||||
1 Fail
|
||||
Pass Measure of navigationStart to now should be positive value.
|
||||
Pass Measure of navigationStart to loadEventEnd should be positive value.
|
||||
Pass Measure of current mark to navigationStart should be negative value.
|
||||
Fail loadTime plus loadEventEnd to a mark "a" should equal to navigationStart to "a".
|
||||
Pass Second measure of current mark to navigationStart should be negative value.
|
||||
Pass Measures of loadTime should have same duration.
|
||||
Pass Measure from domComplete event to most recent mark "a" should have longer duration.
|
||||
Pass Measure from most recent mark to navigationStart should have longer duration.
|
|
@ -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)
|
||||
})
|
||||
}
|
|
@ -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>
|
|
@ -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);
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
};
|
Loading…
Add table
Reference in a new issue