From 7488136a51aaf6ce42ab65432434811be00487df Mon Sep 17 00:00:00 2001 From: Tim Ledbetter Date: Tue, 7 Jan 2025 10:08:14 +0000 Subject: [PATCH] LibWeb: Report performance based timestamps relative to ESO time origin --- .../LibWeb/HighResolutionTime/Performance.cpp | 6 +- .../LibWeb/HighResolutionTime/Performance.h | 2 - .../LibWeb/HighResolutionTime/TimeOrigin.cpp | 38 +++--- .../LibWeb/HighResolutionTime/TimeOrigin.h | 2 + .../LibWeb/UserTiming/PerformanceMark.cpp | 4 +- .../wpt-import/hr-time/timeOrigin.txt | 8 ++ .../wpt-import/user-timing/mark.any.txt | 27 ++++ ...sure_associated_with_navigation_timing.txt | 6 +- Tests/LibWeb/Text/input/performance-now.html | 5 +- .../input/wpt-import/hr-time/timeOrigin.html | 45 +++++++ .../wpt-import/user-timing/mark.any.html | 15 +++ .../input/wpt-import/user-timing/mark.any.js | 118 ++++++++++++++++++ 12 files changed, 248 insertions(+), 28 deletions(-) create mode 100644 Tests/LibWeb/Text/expected/wpt-import/hr-time/timeOrigin.txt create mode 100644 Tests/LibWeb/Text/expected/wpt-import/user-timing/mark.any.txt create mode 100644 Tests/LibWeb/Text/input/wpt-import/hr-time/timeOrigin.html create mode 100644 Tests/LibWeb/Text/input/wpt-import/user-timing/mark.any.html create mode 100644 Tests/LibWeb/Text/input/wpt-import/user-timing/mark.any.js diff --git a/Libraries/LibWeb/HighResolutionTime/Performance.cpp b/Libraries/LibWeb/HighResolutionTime/Performance.cpp index 5fbee3f104b..958bee41528 100644 --- a/Libraries/LibWeb/HighResolutionTime/Performance.cpp +++ b/Libraries/LibWeb/HighResolutionTime/Performance.cpp @@ -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 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(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 diff --git a/Libraries/LibWeb/HighResolutionTime/Performance.h b/Libraries/LibWeb/HighResolutionTime/Performance.h index ac180b899b3..799f61143f0 100644 --- a/Libraries/LibWeb/HighResolutionTime/Performance.h +++ b/Libraries/LibWeb/HighResolutionTime/Performance.h @@ -51,8 +51,6 @@ private: GC::Ptr m_navigation; GC::Ptr m_timing; - - Core::ElapsedTimer m_timer; }; } diff --git a/Libraries/LibWeb/HighResolutionTime/TimeOrigin.cpp b/Libraries/LibWeb/HighResolutionTime/TimeOrigin.cpp index e1ad35dcc85..c5ef9e9ca4a 100644 --- a/Libraries/LibWeb/HighResolutionTime/TimeOrigin.cpp +++ b/Libraries/LibWeb/HighResolutionTime/TimeOrigin.cpp @@ -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() { diff --git a/Libraries/LibWeb/HighResolutionTime/TimeOrigin.h b/Libraries/LibWeb/HighResolutionTime/TimeOrigin.h index 66e1bf49922..9ee9c61fb13 100644 --- a/Libraries/LibWeb/HighResolutionTime/TimeOrigin.h +++ b/Libraries/LibWeb/HighResolutionTime/TimeOrigin.h @@ -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(); } diff --git a/Libraries/LibWeb/UserTiming/PerformanceMark.cpp b/Libraries/LibWeb/UserTiming/PerformanceMark.cpp index 22683c14cd7..91d2487839d 100644 --- a/Libraries/LibWeb/UserTiming/PerformanceMark.cpp +++ b/Libraries/LibWeb/UserTiming/PerformanceMark.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -68,8 +69,7 @@ WebIDL::ExceptionOr> 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. diff --git a/Tests/LibWeb/Text/expected/wpt-import/hr-time/timeOrigin.txt b/Tests/LibWeb/Text/expected/wpt-import/hr-time/timeOrigin.txt new file mode 100644 index 00000000000..63437da0eb7 --- /dev/null +++ b/Tests/LibWeb/Text/expected/wpt-import/hr-time/timeOrigin.txt @@ -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. \ No newline at end of file diff --git a/Tests/LibWeb/Text/expected/wpt-import/user-timing/mark.any.txt b/Tests/LibWeb/Text/expected/wpt-import/user-timing/mark.any.txt new file mode 100644 index 00000000000..e568181a91b --- /dev/null +++ b/Tests/LibWeb/Text/expected/wpt-import/user-timing/mark.any.txt @@ -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] \ No newline at end of file diff --git a/Tests/LibWeb/Text/expected/wpt-import/user-timing/measure_associated_with_navigation_timing.txt b/Tests/LibWeb/Text/expected/wpt-import/user-timing/measure_associated_with_navigation_timing.txt index b936072438a..9a4f185a30e 100644 --- a/Tests/LibWeb/Text/expected/wpt-import/user-timing/measure_associated_with_navigation_timing.txt +++ b/Tests/LibWeb/Text/expected/wpt-import/user-timing/measure_associated_with_navigation_timing.txt @@ -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. \ No newline at end of file diff --git a/Tests/LibWeb/Text/input/performance-now.html b/Tests/LibWeb/Text/input/performance-now.html index 3cc8fefb246..d0ff3cdd905 100644 --- a/Tests/LibWeb/Text/input/performance-now.html +++ b/Tests/LibWeb/Text/input/performance-now.html @@ -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; } diff --git a/Tests/LibWeb/Text/input/wpt-import/hr-time/timeOrigin.html b/Tests/LibWeb/Text/input/wpt-import/hr-time/timeOrigin.html new file mode 100644 index 00000000000..98ef824670c --- /dev/null +++ b/Tests/LibWeb/Text/input/wpt-import/hr-time/timeOrigin.html @@ -0,0 +1,45 @@ + + + + + + + + + + \ No newline at end of file diff --git a/Tests/LibWeb/Text/input/wpt-import/user-timing/mark.any.html b/Tests/LibWeb/Text/input/wpt-import/user-timing/mark.any.html new file mode 100644 index 00000000000..1753d738e6f --- /dev/null +++ b/Tests/LibWeb/Text/input/wpt-import/user-timing/mark.any.html @@ -0,0 +1,15 @@ + + + + + + + +
+ diff --git a/Tests/LibWeb/Text/input/wpt-import/user-timing/mark.any.js b/Tests/LibWeb/Text/input/wpt-import/user-timing/mark.any.js new file mode 100644 index 00000000000..7e814d2074c --- /dev/null +++ b/Tests/LibWeb/Text/input/wpt-import/user-timing/mark.any.js @@ -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); +}