mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-04-20 11:36:10 +00:00
LibWeb: Report performance based timestamps relative to ESO time origin
This commit is contained in:
parent
8963e62a5e
commit
7488136a51
Notes:
github-actions[bot]
2025-01-27 13:54:36 +00:00
Author: https://github.com/tcl3 Commit: https://github.com/LadybirdBrowser/ladybird/commit/7488136a51a Pull-request: https://github.com/LadybirdBrowser/ladybird/pull/3341
12 changed files with 248 additions and 28 deletions
|
@ -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
|
||||
|
|
|
@ -51,8 +51,6 @@ private:
|
|||
|
||||
GC::Ptr<NavigationTiming::PerformanceNavigation> m_navigation;
|
||||
GC::Ptr<NavigationTiming::PerformanceTiming> m_timing;
|
||||
|
||||
Core::ElapsedTimer m_timer;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
{
|
||||
|
|
|
@ -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();
|
||||
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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.
|
|
@ -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]
|
|
@ -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.
|
|
@ -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;
|
||||
}
|
||||
|
|
45
Tests/LibWeb/Text/input/wpt-import/hr-time/timeOrigin.html
Normal file
45
Tests/LibWeb/Text/input/wpt-import/hr-time/timeOrigin.html
Normal 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>
|
15
Tests/LibWeb/Text/input/wpt-import/user-timing/mark.any.html
Normal file
15
Tests/LibWeb/Text/input/wpt-import/user-timing/mark.any.html
Normal 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>
|
118
Tests/LibWeb/Text/input/wpt-import/user-timing/mark.any.js
Normal file
118
Tests/LibWeb/Text/input/wpt-import/user-timing/mark.any.js
Normal 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);
|
||||
}
|
Loading…
Add table
Reference in a new issue