tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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:
Mdom/performance/PerformanceEventTiming.cpp | 3+--
Mdom/performance/PerformanceEventTiming.h | 8++++----
Mdom/performance/PerformanceMainThread.cpp | 4++--
Atesting/web-platform/tests/event-timing/pointerdown-pointerup-no-overlap.html | 72++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
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>