commit bc732e77b57e7512a01f80664eb18f553633982b
parent 0ddf25410102e1867445b6c6f0cbc20641f3a853
Author: Nazım Can Altınova <canaltinova@gmail.com>
Date: Wed, 19 Nov 2025 14:36:01 +0000
Bug 2000426 - Convert EventTiming mDuration to an optional value r=smaug
This patch converts the mDuration of PerformanceEventTiming from
DOMHighResTimeStamp to Maybe<DOMHighResTimeStamp>. This allows us to
distinguish between:
- Duration not yet set (mDuration.isNothing())
- Duration explicitly set to 0 (mDuration.isSome() && value == 0)
This distinction is necessary to fix bug 2000426, where we need to
determine if a pointerdown event's duration has been finalized before
processing subsequent pointerup/click events.
Differential Revision: https://phabricator.services.mozilla.com/D273025
Diffstat:
4 files changed, 79 insertions(+), 8 deletions(-)
diff --git a/dom/performance/PerformanceEventTiming.cpp b/dom/performance/PerformanceEventTiming.cpp
@@ -45,7 +45,6 @@ PerformanceEventTiming::PerformanceEventTiming(Performance* aPerformance,
mProcessingEnd(0),
mStartTime(
aPerformance->GetDOMTiming()->TimeStampToDOMHighRes(aStartTime)),
- mDuration(0),
mCancelable(aIsCancelable),
mMessage(aMessage) {}
@@ -149,7 +148,7 @@ bool PerformanceEventTiming::ShouldAddEntryToBuffer(double aDuration) const {
return true;
}
MOZ_ASSERT(GetEntryType() == nsGkAtoms::event);
- return RawDuration() >= aDuration;
+ return RawDuration().valueOr(0) >= aDuration;
}
bool PerformanceEventTiming::ShouldAddEntryToObserverBuffer(
diff --git a/dom/performance/PerformanceEventTiming.h b/dom/performance/PerformanceEventTiming.h
@@ -75,18 +75,18 @@ class PerformanceEventTiming final
void SetDuration(const DOMHighResTimeStamp aDuration) {
// Round the duration to the nearest 8ms.
// https://w3c.github.io/event-timing/#set-event-timing-entry-duration
- mDuration = std::round(aDuration / 8) * 8;
+ mDuration = Some(std::round(aDuration / 8) * 8);
}
// nsRFPService::ReduceTimePrecisionAsMSecs might causes
// some memory overhead, using the raw timestamp internally
// to avoid calling in unnecessarily.
- DOMHighResTimeStamp RawDuration() const { return mDuration; }
+ Maybe<DOMHighResTimeStamp> RawDuration() const { return mDuration; }
DOMHighResTimeStamp Duration() const override {
if (mCachedDuration.isNothing()) {
mCachedDuration.emplace(nsRFPService::ReduceTimePrecisionAsMSecs(
- mDuration, mPerformance->GetRandomTimelineSeed(),
+ mDuration.valueOr(0), mPerformance->GetRandomTimelineSeed(),
mPerformance->GetRTPCallerType()));
}
return mCachedDuration.value();
@@ -136,7 +136,7 @@ class PerformanceEventTiming final
DOMHighResTimeStamp mStartTime;
mutable Maybe<DOMHighResTimeStamp> mCachedStartTime;
- DOMHighResTimeStamp mDuration;
+ Maybe<DOMHighResTimeStamp> mDuration;
mutable Maybe<DOMHighResTimeStamp> mCachedDuration;
bool mCancelable;
diff --git a/dom/performance/PerformanceMainThread.cpp b/dom/performance/PerformanceMainThread.cpp
@@ -299,7 +299,7 @@ void PerformanceMainThread::DispatchPendingEventTimingEntries() {
it != mPendingEventTimingEntries.end(); ++it) {
// Set its duration if it's not set already.
PerformanceEventTiming* entry = *it;
- if (entry->RawDuration() == 0) {
+ if (entry->RawDuration().isNothing()) {
entry->SetDuration(renderingTime - entry->RawStartTime());
}
@@ -314,7 +314,7 @@ void PerformanceMainThread::DispatchPendingEventTimingEntries() {
while (mPendingEventTimingEntries.begin() != entriesToBeQueuedEnd) {
RefPtr<PerformanceEventTiming> entry =
mPendingEventTimingEntries.popFirst();
- if (entry->RawDuration() >= kDefaultEventTimingMinDuration) {
+ if (entry->RawDuration().valueOr(0) >= kDefaultEventTimingMinDuration) {
QueueEntry(entry);
}
diff --git a/testing/web-platform/tests/event-timing/pointerdown-pointerup-no-overlap.html b/testing/web-platform/tests/event-timing/pointerdown-pointerup-no-overlap.html
@@ -0,0 +1,72 @@
+<!DOCTYPE html>
+<html>
+<meta charset=utf-8 />
+<title>Event Timing: pointerdown and pointerup should not overlap when there is a paint in between</title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script src=/resources/testdriver.js></script>
+<script src=/resources/testdriver-vendor.js></script>
+<script src=/resources/testdriver-actions.js></script>
+<script src=resources/event-timing-test-utils.js></script>
+<button id='button'>Click me.</button>
+<script>
+ promise_test(async t => {
+ assert_implements(window.PerformanceEventTiming, 'Event Timing is not supported.');
+ const button = document.getElementById('button');
+ const observedEntries = [];
+ button.addEventListener('click', () => {
+ mainThreadBusy(150);
+ });
+
+ const observerPromise = new Promise(resolve => {
+ new PerformanceObserver(entryList => {
+ observedEntries.push(...entryList.getEntries().filter(entry =>
+ entry.name === 'pointerdown' || entry.name === 'pointerup' || entry.name === 'click'
+ ));
+
+ // We expect pointerdown, pointerup, and click.
+ if (observedEntries.length >= 3) {
+ resolve();
+ }
+ }).observe({entryTypes: ['event', 'first-input'], buffered: true});
+ });
+
+ const actions1 = new test_driver.Actions();
+ await actions1
+ .addPointer("testPointer", "mouse")
+ .pointerMove(0, 0, { origin: button })
+ .pointerDown({ button: actions1.ButtonType.LEFT })
+ .pause(50)
+ .send();
+
+ // Wait for a paint to occur
+ await afterNextPaint();
+
+ // Perform pointerup
+ const actions2 = new test_driver.Actions();
+ await actions2
+ .addPointer("testPointer", "mouse")
+ .pointerMove(0, 0, { origin: button })
+ .pointerUp({ button: actions2.ButtonType.LEFT })
+ .send();
+
+ await observerPromise;
+
+ const pointerdownEntry = observedEntries.find(e => e.name === 'pointerdown');
+ const pointerupEntry = observedEntries.find(e => e.name === 'pointerup');
+ const clickEntry = observedEntries.find(e => e.name === 'click');
+
+ assert_true(!!pointerdownEntry, 'Should have pointerdown entry');
+ assert_true(!!pointerupEntry, 'Should have pointerup entry');
+ assert_true(!!clickEntry, 'Should have click entry');
+
+ // Calculate end times: startTime + duration.
+ const pointerdownEndTime = pointerdownEntry.startTime + pointerdownEntry.duration;
+ const pointerupStartTime = pointerupEntry.startTime;
+
+ // pointerdown should complete BEFORE pointerup starts.
+ assert_less_than_equal(pointerdownEndTime, pointerupStartTime,
+ 'pointerdown should end before or when pointerup starts (no overlap)');
+ }, 'pointerdown and pointerup should not overlap when there is no pointerdown handler');
+</script>
+</html>