LibWeb: Report performance based timestamps relative to ESO time origin

This commit is contained in:
Tim Ledbetter 2025-01-07 10:08:14 +00:00 committed by Alexander Kalenik
parent 8963e62a5e
commit 7488136a51
Notes: github-actions[bot] 2025-01-27 13:54:36 +00:00
12 changed files with 248 additions and 28 deletions

View file

@ -24,9 +24,7 @@ GC_DEFINE_ALLOCATOR(Performance);
Performance::Performance(JS::Realm& realm)
: DOM::EventTarget(realm)
, m_timer(Core::TimerType::Precise)
{
m_timer.start();
}
Performance::~Performance() = default;
@ -68,8 +66,8 @@ GC::Ptr<NavigationTiming::PerformanceNavigation> Performance::navigation()
// https://w3c.github.io/hr-time/#timeorigin-attribute
double Performance::time_origin() const
{
// FIXME: The timeOrigin attribute MUST return the number of milliseconds in the duration returned by get time origin timestamp for the relevant global object of this.
return static_cast<double>(m_timer.origin_time().nanoseconds()) / 1e6;
// The timeOrigin attribute MUST return the number of milliseconds in the duration returned by get time origin timestamp for the relevant global object of this.
return get_time_origin_timestamp(HTML::relevant_principal_global_object(*this));
}
// https://w3c.github.io/hr-time/#now-method

View file

@ -51,8 +51,6 @@ private:
GC::Ptr<NavigationTiming::PerformanceNavigation> m_navigation;
GC::Ptr<NavigationTiming::PerformanceTiming> m_timing;
Core::ElapsedTimer m_timer;
};
}

View file

@ -11,23 +11,15 @@
namespace Web::HighResolutionTime {
// https://w3c.github.io/hr-time/#dfn-get-time-origin-timestamp
DOMHighResTimeStamp get_time_origin_timestamp(JS::Object const& global)
// https://w3c.github.io/hr-time/#dfn-estimated-monotonic-time-of-the-unix-epoch
DOMHighResTimeStamp estimated_monotonic_time_of_the_unix_epoch()
{
(void)global;
// To get time origin timestamp, given a global object global, run the following steps, which return a duration:
// FIXME: 1. Let timeOrigin be global's relevant settings object's time origin.
auto time_origin = 0;
// Each group of environment settings objects that could possibly communicate in any way
// has an estimated monotonic time of the Unix epoch, a moment on the monotonic clock,
// whose value is initialized by the following steps:
// !. Let wall time be the wall clock's unsafe current time.
struct timeval tv;
gettimeofday(&tv, nullptr);
auto wall_time = tv.tv_sec * 1000.0 + tv.tv_usec / 1000.0;
// 1. Let wall time be the wall clock's unsafe current time.
auto wall_time = wall_clock_unsafe_current_time();
// 2. Let monotonic time be the monotonic clock's unsafe current time.
auto monotonic_time = unsafe_shared_current_time();
@ -37,9 +29,18 @@ DOMHighResTimeStamp get_time_origin_timestamp(JS::Object const& global)
// 4. Initialize the estimated monotonic time of the Unix epoch to the result of calling coarsen time with epoch time
auto estimated_monotonic_time = coarsen_time(epoch_time);
return estimated_monotonic_time;
}
// https://w3c.github.io/hr-time/#dfn-get-time-origin-timestamp
DOMHighResTimeStamp get_time_origin_timestamp(JS::Object const& global)
{
// To get time origin timestamp, given a global object global, run the following steps, which return a duration:
// 1. Let timeOrigin be global's relevant settings object's time origin.
auto time_origin = HTML::relevant_principal_settings_object(global).time_origin();
// 2. Return the duration from the estimated monotonic time of the Unix epoch to timeOrigin.
return estimated_monotonic_time - time_origin;
return time_origin - estimated_monotonic_time_of_the_unix_epoch();
}
// https://w3c.github.io/hr-time/#dfn-coarsen-time
@ -75,8 +76,9 @@ DOMHighResTimeStamp relative_high_resolution_time(DOMHighResTimeStamp time, JS::
// https://w3c.github.io/hr-time/#dfn-relative-high-resolution-coarse-time
DOMHighResTimeStamp relative_high_resolution_coarsen_time(DOMHighResTimeStamp coarsen_time, JS::Object const& global)
{
// The relative high resolution coarse time given a DOMHighResTimeStamp coarseTime and a global object global, is the difference between coarseTime and the result of calling get time origin timestamp with global.
return coarsen_time - get_time_origin_timestamp(global);
// The relative high resolution coarse time given a moment from the monotonic clock coarseTime and a global object global, is the duration from global's relevant settings object's time origin to coarseTime.
auto time_origin = HTML::relevant_principal_settings_object(global).time_origin();
return coarsen_time - time_origin;
}
// https://w3c.github.io/hr-time/#dfn-coarsened-shared-current-time
@ -86,6 +88,12 @@ DOMHighResTimeStamp coarsened_shared_current_time(bool cross_origin_isolated_cap
return coarsen_time(unsafe_shared_current_time(), cross_origin_isolated_capability);
}
// https://w3c.github.io/hr-time/#wall-clock-unsafe-current-time
DOMHighResTimeStamp wall_clock_unsafe_current_time()
{
return UnixDateTime::now().nanoseconds_since_epoch() / 1.0e6;
}
// https://w3c.github.io/hr-time/#dfn-unsafe-shared-current-time
DOMHighResTimeStamp unsafe_shared_current_time()
{

View file

@ -12,12 +12,14 @@
namespace Web::HighResolutionTime {
DOMHighResTimeStamp estimated_monotonic_time_of_the_unix_epoch();
DOMHighResTimeStamp get_time_origin_timestamp(JS::Object const&);
DOMHighResTimeStamp coarsen_time(DOMHighResTimeStamp timestamp, bool cross_origin_isolated_capability = false);
DOMHighResTimeStamp current_high_resolution_time(JS::Object const&);
DOMHighResTimeStamp relative_high_resolution_time(DOMHighResTimeStamp, JS::Object const&);
DOMHighResTimeStamp relative_high_resolution_coarsen_time(DOMHighResTimeStamp, JS::Object const&);
DOMHighResTimeStamp coarsened_shared_current_time(bool cross_origin_isolated_capability = false);
DOMHighResTimeStamp wall_clock_unsafe_current_time();
DOMHighResTimeStamp unsafe_shared_current_time();
}

View file

@ -8,6 +8,7 @@
#include <LibWeb/Bindings/PerformanceMarkPrototype.h>
#include <LibWeb/HTML/StructuredSerialize.h>
#include <LibWeb/HTML/Window.h>
#include <LibWeb/HighResolutionTime/Performance.h>
#include <LibWeb/HighResolutionTime/TimeOrigin.h>
#include <LibWeb/NavigationTiming/EntryNames.h>
#include <LibWeb/PerformanceTimeline/EntryTypes.h>
@ -68,8 +69,7 @@ WebIDL::ExceptionOr<GC::Ref<PerformanceMark>> PerformanceMark::construct_impl(JS
}
// 2. Otherwise, set it to the value that would be returned by the Performance object's now() method.
else {
// FIXME: Performance#now doesn't currently use TimeOrigin's functions, update this and Performance#now to match Performance#now's specification.
start_time = HighResolutionTime::unsafe_shared_current_time();
start_time = HighResolutionTime::current_high_resolution_time(current_principal_global_object);
}
// 6. Set entry's duration attribute to 0.

View file

@ -0,0 +1,8 @@
Harness status: OK
Found 3 tests
3 Pass
Pass Window timeOrigin is close to Date.now() when there is no system clock adjustment.
Pass Window and worker timeOrigins are close when created one after another.
Pass Window and worker timeOrigins differ when worker is created after a delay.

View file

@ -0,0 +1,27 @@
Harness status: OK
Found 22 tests
22 Pass
Pass Entry 0 is properly created
Pass Entry 1 is properly created
Pass Entry 0 has the proper name
Pass Entry 0 startTime is approximately correct (up to 20ms difference allowed)
Pass Entry 0 has the proper entryType
Pass Entry 0 duration == 0
Pass getEntriesByName("mark", "mark")[0] returns an object containing a "mark" mark
Pass The mark returned by getEntriesByName("mark", "mark")[0] matches the mark returned by getEntriesByName("mark")[0]
Pass getEntries()[0] returns an object containing a "mark" mark
Pass The mark returned by getEntries()[0] matches the mark returned by getEntriesByName("mark")[0]
Pass getEntriesByType("mark")[0] returns an object containing a "mark" mark
Pass The mark returned by getEntriesByType("mark")[0] matches the mark returned by getEntriesByName("mark")[0]
Pass Entry 1 has the proper name
Pass Entry 1 startTime is approximately correct (up to 20ms difference allowed)
Pass Entry 1 has the proper entryType
Pass Entry 1 duration == 0
Pass getEntriesByName("mark", "mark")[1] returns an object containing a "mark" mark
Pass The mark returned by getEntriesByName("mark", "mark")[1] matches the mark returned by getEntriesByName("mark")[1]
Pass getEntries()[1] returns an object containing a "mark" mark
Pass The mark returned by getEntries()[1] matches the mark returned by getEntriesByName("mark")[1]
Pass getEntriesByType("mark")[1] returns an object containing a "mark" mark
Pass The mark returned by getEntriesByType("mark")[1] matches the mark returned by getEntriesByName("mark")[1]

View file

@ -2,13 +2,13 @@ Harness status: OK
Found 8 tests
7 Pass
1 Fail
6 Pass
2 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.
Fail 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.

View file

@ -11,11 +11,12 @@
return;
}
let timestamp = performance.now();
let monotonicTime = performance.now();
let date = Date.now();
let relativeDate = date - performance.timeOrigin;
let allowedDifference = 300;
if (timestamp <= date - allowedDifference || timestamp >= date + allowedDifference) {
if (Math.abs(monotonicTime - relativeDate) >= allowedDifference) {
println('performance.now() should be close to Date.now(), but was ' + (timestamp - date));
return;
}

View file

@ -0,0 +1,45 @@
<!doctype html>
<html>
<head>
<script src="../resources/testharness.js"></script>
<script src="../resources/testharnessreport.js"></script>
</head>
<body>
<script>
const windowOrigin = performance.timeOrigin;
test(() => {
// Use a 30ms cushion when comparing with Date() to account for inaccuracy.
const startTime = Date.now();
assert_greater_than_equal(startTime + 30, windowOrigin, 'Date.now() should be at least as large as the window timeOrigin.');
const startNow = performance.now();
assert_less_than_equal(startTime, windowOrigin + startNow + 30, 'Date.now() should be close to window timeOrigin.');
}, 'Window timeOrigin is close to Date.now() when there is no system clock adjustment.');
const workerScript = 'postMessage({timeOrigin: performance.timeOrigin})';
const blob = new Blob([workerScript]);
async_test(function(t) {
const beforeWorkerCreation = performance.now();
const worker = new Worker(URL.createObjectURL(blob));
worker.addEventListener('message', t.step_func_done(function(event) {
const workerOrigin = event.data.timeOrigin;
assert_greater_than_equal(workerOrigin, windowOrigin + beforeWorkerCreation, 'Worker timeOrigin should be greater than the window timeOrigin.');
const afterWorkerCreation = performance.now();
assert_less_than_equal(workerOrigin - windowOrigin, afterWorkerCreation, 'Window and worker timeOrigins should be close.');
}));
}, 'Window and worker timeOrigins are close when created one after another.');
async_test(function(t) {
this.step_timeout(function() {
const workerCreation = performance.now();
const worker = new Worker(URL.createObjectURL(blob));
worker.addEventListener('message', t.step_func_done(function(event) {
const workerOrigin = event.data.timeOrigin;
assert_greater_than_equal(workerOrigin - windowOrigin, 200, 'We waited 200ms to spawn the second worker, so its timeOrigin should be greater than that of the window.');
}));
}, 200);
}, 'Window and worker timeOrigins differ when worker is created after a delay.');
</script>
</body>
</html>

View file

@ -0,0 +1,15 @@
<!doctype html>
<meta charset=utf-8>
<script>
self.GLOBAL = {
isWindow: function() { return true; },
isWorker: function() { return false; },
isShadowRealm: function() { return false; },
};
</script>
<script src="../resources/testharness.js"></script>
<script src="../resources/testharnessreport.js"></script>
<div id=log></div>
<script src="../user-timing/mark.any.js"></script>

View file

@ -0,0 +1,118 @@
// test data
var testThreshold = 20;
var expectedTimes = new Array();
function match_entries(entries, index)
{
var entry = entries[index];
var match = self.performance.getEntriesByName("mark")[index];
assert_equals(entry.name, match.name, "entry.name");
assert_equals(entry.startTime, match.startTime, "entry.startTime");
assert_equals(entry.entryType, match.entryType, "entry.entryType");
assert_equals(entry.duration, match.duration, "entry.duration");
}
function filter_entries_by_type(entryList, entryType)
{
var testEntries = new Array();
// filter entryList
for (var i in entryList)
{
if (entryList[i].entryType == entryType)
{
testEntries.push(entryList[i]);
}
}
return testEntries;
}
test(function () {
// create first mark
self.performance.mark("mark");
expectedTimes[0] = self.performance.now();
const entries = self.performance.getEntriesByName("mark");
assert_equals(entries.length, 1);
}, "Entry 0 is properly created");
test(function () {
// create second, duplicate mark
self.performance.mark("mark");
expectedTimes[1] = self.performance.now();
const entries = self.performance.getEntriesByName("mark");
assert_equals(entries.length, 2);
}, "Entry 1 is properly created");
function test_mark(index) {
test(function () {
const entries = self.performance.getEntriesByName("mark");
assert_equals(entries[index].name, "mark", "Entry has the proper name");
}, "Entry " + index + " has the proper name");
test(function () {
const entries = self.performance.getEntriesByName("mark");
assert_approx_equals(entries[index].startTime, expectedTimes[index], testThreshold);
}, "Entry " + index + " startTime is approximately correct (up to " + testThreshold +
"ms difference allowed)");
test(function () {
const entries = self.performance.getEntriesByName("mark");
assert_equals(entries[index].entryType, "mark");
}, "Entry " + index + " has the proper entryType");
test(function () {
const entries = self.performance.getEntriesByName("mark");
assert_equals(entries[index].duration, 0);
}, "Entry " + index + " duration == 0");
test(function () {
const entries = self.performance.getEntriesByName("mark", "mark");
assert_equals(entries[index].name, "mark");
}, "getEntriesByName(\"mark\", \"mark\")[" + index + "] returns an " +
"object containing a \"mark\" mark");
test(function () {
const entries = self.performance.getEntriesByName("mark", "mark");
match_entries(entries, index);
}, "The mark returned by getEntriesByName(\"mark\", \"mark\")[" + index
+ "] matches the mark returned by " +
"getEntriesByName(\"mark\")[" + index + "]");
test(function () {
const entries = filter_entries_by_type(self.performance.getEntries(), "mark");
assert_equals(entries[index].name, "mark");
}, "getEntries()[" + index + "] returns an " +
"object containing a \"mark\" mark");
test(function () {
const entries = filter_entries_by_type(self.performance.getEntries(), "mark");
match_entries(entries, index);
}, "The mark returned by getEntries()[" + index
+ "] matches the mark returned by " +
"getEntriesByName(\"mark\")[" + index + "]");
test(function () {
const entries = self.performance.getEntriesByType("mark");
assert_equals(entries[index].name, "mark");
}, "getEntriesByType(\"mark\")[" + index + "] returns an " +
"object containing a \"mark\" mark");
test(function () {
const entries = self.performance.getEntriesByType("mark");
match_entries(entries, index);
}, "The mark returned by getEntriesByType(\"mark\")[" + index
+ "] matches the mark returned by " +
"getEntriesByName(\"mark\")[" + index + "]");
}
for (var i = 0; i < expectedTimes.length; i++) {
test_mark(i);
}