"; };
@@ -29067,6 +29071,7 @@
E0FEF371B27C53EAC1C1FBEE /* EventSource.cpp */,
E0FEF371B17C53EAC1C1FBEE /* EventSource.h */,
E0FEF371B07C53EAC1C1FBEE /* EventSource.idl */,
+ 48EA8D0F2E56212B00B1183B /* EventTimingInteractionID.h */,
14993BE30B2F2B1C0050497F /* FocusController.cpp */,
14993BE40B2F2B1C0050497F /* FocusController.h */,
062287830B4DB322000C34DF /* FocusDirection.h */,
@@ -29233,6 +29238,7 @@
4822FD222E2A8058004CAFF9 /* PerformanceEventTiming.cpp */,
4822FD212E2A8058004CAFF9 /* PerformanceEventTiming.h */,
4822FD252E2A8082004CAFF9 /* PerformanceEventTiming.idl */,
+ 4869472C2E54CE1800C708D8 /* PerformanceEventTimingCandidate.h */,
AD5A0C211DECA10100707054 /* PerformanceLogging.cpp */,
AD5A0C201DECA0B500707054 /* PerformanceLogging.h */,
0FF2E80C1EE0D430009EABD4 /* PerformanceLoggingClient.cpp */,
@@ -42219,6 +42225,7 @@
97AA3CA5145237CC003E1DA6 /* EventTargetHeaders.h in Headers */,
9BDF285C2DC0AC8B0096B893 /* EventTargetInlines.h in Headers */,
97AA3CA6145237CC003E1DA6 /* EventTargetInterfaces.h in Headers */,
+ 48EA8D102E56212B00B1183B /* EventTimingInteractionID.h in Headers */,
262EC41A1D078FB900BA78FC /* EventTrackingRegions.h in Headers */,
93D196331D6CAB8200FC7E47 /* Exception.h in Headers */,
935FBCF209BA143B00E230B1 /* ExceptionCode.h in Headers */,
@@ -44570,6 +44577,7 @@
8A844D0511D3C18E0014065C /* Performance.h in Headers */,
86BE340115058CB200CE0FD8 /* PerformanceEntry.h in Headers */,
4822FD232E2A8058004CAFF9 /* PerformanceEventTiming.h in Headers */,
+ 4869472D2E54CE1800C708D8 /* PerformanceEventTimingCandidate.h in Headers */,
AD5A0C251DECACCC00707054 /* PerformanceLogging.h in Headers */,
0F850FE31ED7C18300FB77A7 /* PerformanceLoggingClient.h in Headers */,
37C738E91EDBD2FA003F2B0B /* PerformanceMark.h in Headers */,
diff --git a/Source/WebCore/bindings/js/JSPerformanceEntryCustom.cpp b/Source/WebCore/bindings/js/JSPerformanceEntryCustom.cpp
index 9dfe6be398eee..fb36791f17cef 100644
--- a/Source/WebCore/bindings/js/JSPerformanceEntryCustom.cpp
+++ b/Source/WebCore/bindings/js/JSPerformanceEntryCustom.cpp
@@ -66,7 +66,6 @@ JSValue toJSNewlyCreated(JSGlobalObject*, JSDOMGlobalObject* globalObject, Ref(globalObject, WTFMove(entry));
- return createWrapper(globalObject, WTFMove(entry));
}
ASSERT_NOT_REACHED();
diff --git a/Source/WebCore/dom/EventDispatcher.cpp b/Source/WebCore/dom/EventDispatcher.cpp
index 925038740a804..8595d40eb9519 100644
--- a/Source/WebCore/dom/EventDispatcher.cpp
+++ b/Source/WebCore/dom/EventDispatcher.cpp
@@ -179,9 +179,9 @@ void EventDispatcher::dispatchEvent(Node& node, Event& event)
bool shouldDispatchEventToScripts = hasRelevantEventListener(document, event);
RefPtr window = document->window();
- std::optional pendingEventTiming;
+ std::optional pendingEventTiming;
if (typeInfo.isInCategory(EventCategory::EventTimingEligible) && window && document->settings().eventTimingEnabled() && event.isTrusted())
- pendingEventTiming = window->initializeEventTimingEntry(event, typeInfo);
+ pendingEventTiming = window->initializeEventTimingEntry(event, typeInfo.type());
auto finalizeEntry(WTF::makeScopeExit([&, event = Ref(event)] {
if (pendingEventTiming)
window->finalizeEventTimingEntry(*pendingEventTiming, event);
diff --git a/Source/WebCore/page/EventTimingInteractionID.h b/Source/WebCore/page/EventTimingInteractionID.h
new file mode 100644
index 0000000000000..d416eea36c70b
--- /dev/null
+++ b/Source/WebCore/page/EventTimingInteractionID.h
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2025 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include
+
+namespace WebCore {
+
+struct EventTimingInteractionID {
+ uint64_t value { 0 };
+};
+
+}
+
+namespace WTF {
+
+template<>
+struct MarkableTraits {
+ static bool isEmptyValue(WebCore::EventTimingInteractionID eventTimingInteractionID)
+ {
+ return !eventTimingInteractionID.value;
+ }
+
+ static constexpr WebCore::EventTimingInteractionID emptyValue()
+ {
+ return WebCore::EventTimingInteractionID();
+ }
+};
+
+}
diff --git a/Source/WebCore/page/LocalDOMWindow.cpp b/Source/WebCore/page/LocalDOMWindow.cpp
index 8aedaa3ccbca8..15b9b542034a6 100644
--- a/Source/WebCore/page/LocalDOMWindow.cpp
+++ b/Source/WebCore/page/LocalDOMWindow.cpp
@@ -75,6 +75,7 @@
#include "HTTPParsers.h"
#include "History.h"
#include "IdleRequestOptions.h"
+#include "InputEvent.h"
#include "InspectorInstrumentation.h"
#include "JSDOMExceptionHandling.h"
#include "JSDOMPromiseDeferred.h"
@@ -137,12 +138,14 @@
#include "WindowFocusAllowedIndicator.h"
#include "WindowPostMessageOptions.h"
#include "WindowProxy.h"
+#include "page/EventTimingInteractionID.h"
#include
#include
#include
#include
#include
#include
+#include
#include
#include
#include
@@ -2458,39 +2461,196 @@ void LocalDOMWindow::finishedLoading()
}
}
-PerformanceEventTiming::Candidate LocalDOMWindow::initializeEventTimingEntry(const Event& event, EventTypeInfo typeInfo)
+EventTimingInteractionID LocalDOMWindow::computeInteractionID(const Event& event, EventType type)
{
- // FIXME: implement InteractionId logic
+ auto finalizePendingPointerDown = [this]() {
+ auto interactionID = generateInteractionID();
+ m_pointerMap = interactionID;
+ m_pendingPointerDown->interactionID = interactionID;
+ m_performanceEventTimingCandidates.append(*m_pendingPointerDown);
+ m_pendingPointerDown.reset();
+ return interactionID;
+ };
+
+ switch (type) {
+ case EventType::keyup: {
+ ASSERT(event.isKeyboardEvent());
+ auto keyboardEvent = downcast(&event);
+ ASSERT(keyboardEvent);
+ if (keyboardEvent->isComposing())
+ return EventTimingInteractionID();
+
+ auto it = m_pendingKeyDowns.find(keyboardEvent->keyCode());
+ if (it == m_pendingKeyDowns.end())
+ return EventTimingInteractionID();
+
+ auto interactionID = generateInteractionID();
+ it->value.interactionID = interactionID;
+ m_performanceEventTimingCandidates.append(it->value);
+ m_pendingKeyDowns.remove(it);
+ return interactionID;
+ }
+ case EventType::compositionstart: {
+ for (auto& pendingEntry : m_pendingKeyDowns)
+ m_performanceEventTimingCandidates.append(pendingEntry.value);
+
+ m_pendingKeyDowns.clear();
+ return EventTimingInteractionID();
+ }
+ case EventType::input: {
+ if (!event.isInputEvent())
+ return EventTimingInteractionID();
+
+ ASSERT(event.isInputEvent());
+ auto inputEvent = downcast(&event);
+ ASSERT(inputEvent);
+ if (!inputEvent->isInputMethodComposing())
+ return EventTimingInteractionID();
+
+ return generateInteractionID();
+ }
+ case EventType::click: {
+ if (!m_pointerMap)
+ return EventTimingInteractionID();
+
+ auto interactionID = *m_pointerMap;
+ m_pointerMap.reset();
+ return interactionID;
+ }
+ case EventType::pointerup: {
+ if (!m_pendingPointerDown) {
+ if (!m_contextMenuTriggered)
+ return EventTimingInteractionID();
+
+ m_contextMenuTriggered = false;
+ return currentInteractionID();
+ }
+ return finalizePendingPointerDown();
+ }
+ case EventType::pointercancel: {
+ if (m_pendingPointerDown) {
+ // Cancelled pointerDowns are finalized without receiving an interactionID:
+ m_performanceEventTimingCandidates.append(*m_pendingPointerDown);
+ m_pendingPointerDown.reset();
+ }
+ return EventTimingInteractionID();
+ }
+ case EventType::contextmenu: {
+ if (!m_pendingPointerDown)
+ return currentInteractionID();
+
+ m_contextMenuTriggered = true;
+ return finalizePendingPointerDown();
+ }
+ default:
+ return EventTimingInteractionID();
+ }
+}
+
+EventTimingInteractionID LocalDOMWindow::currentInteractionID()
+{
+ return ensureUserInteractionValue();
+}
+
+EventTimingInteractionID& LocalDOMWindow::ensureUserInteractionValue()
+{
+ // Should be initialized with a random number from 100 to 10000:
+ if (!m_userInteractionValue) [[unlikely]]
+ m_userInteractionValue = EventTimingInteractionID { .value = 100 + WTF::cryptographicallyRandomNumber() % 9901 };
+ return *m_userInteractionValue;
+}
+
+EventTimingInteractionID LocalDOMWindow::generateInteractionID()
+{
+ ++m_interactionCount;
+ return generateInteractionIDWithoutIncreasingInteractionCount();
+}
+
+EventTimingInteractionID LocalDOMWindow::generateInteractionIDWithoutIncreasingInteractionCount()
+{
+ // User interaction value should be increased by a small integer to
+ // "discourage developers from considering it as a counter":
+ ensureUserInteractionValue().value += 7;
+ return ensureUserInteractionValue();
+}
+
+PerformanceEventTimingCandidate LocalDOMWindow::initializeEventTimingEntry(const Event& event, EventType type)
+{
+ auto startTime = performance().relativeTimeFromTimeOriginInReducedResolutionSeconds(event.timeStamp());
auto processingStart = performance().nowInReducedResolutionSeconds();
- LOG_WITH_STREAM(PerformanceTimeline, stream << "Initializing event timing entry (type=" << event.type() << ") at t=" << processingStart);
- return PerformanceEventTiming::Candidate {
- .typeInfo = typeInfo,
+ LOG_WITH_STREAM(PerformanceTimeline, stream << "Initializing event timing entry (type=" << event.type() << "; tstamp=" << startTime << ") at t=" << processingStart);
+ if (startTime > processingStart)
+ startTime = processingStart;
+
+ return PerformanceEventTimingCandidate {
+ .type = type,
.cancelable = event.cancelable(),
- // Account for event.timeStamp() unreliability (based on wall clock):
- .startTime = std::min(processingStart, performance().relativeTimeFromTimeOriginInReducedResolutionSeconds(event.timeStamp())),
- .processingStart = processingStart
+ .startTime = startTime,
+ .processingStart = processingStart,
+ .processingEnd = { },
+ .duration = { },
+ .target = { },
+ .interactionID = computeInteractionID(event, type)
};
}
-void LocalDOMWindow::finalizeEventTimingEntry(const PerformanceEventTiming::Candidate& entry, const Event& event)
+void LocalDOMWindow::finalizeEventTimingEntry(PerformanceEventTimingCandidate& entry, const Event& event)
{
+ auto processingEnd = performance().nowInReducedResolutionSeconds();
+ entry.processingEnd = processingEnd;
+ entry.target = event.target();
+ if (event.type() == eventNames().pointerdownEvent) [[unlikely]] {
+ if (m_pendingPointerDown) {
+ m_performanceEventTimingCandidates.append(*m_pendingPointerDown);
+ LOG_WITH_STREAM(PerformanceTimeline, stream << "Repeated pointerdown entries at t=" << processingEnd);
+ }
+
+ LOG_WITH_STREAM(PerformanceTimeline, stream << "Adding pending pointerdown at t=" << processingEnd);
+ m_pendingPointerDown = entry;
+ m_contextMenuTriggered = false;
+ return;
+ }
+ if (event.type() == eventNames().keydownEvent) [[unlikely]] {
+ ASSERT(event.isKeyboardEvent());
+ auto keyboardEvent = downcast(&event);
+ ASSERT(keyboardEvent);
+ if (keyboardEvent->isComposing()) {
+ m_performanceEventTimingCandidates.append(entry);
+ return;
+ }
+ auto code = keyboardEvent->keyCode();
+ auto it = m_pendingKeyDowns.find(code);
+ if (it == m_pendingKeyDowns.end()) {
+ m_pendingKeyDowns.set(code, entry);
+ return;
+ }
+ // Code 229 corresponds to IME keyboard events
+ // (https://www.w3.org/TR/event-timing/#sec-fin-event-timing)
+ if (code != 229)
+ it->value.interactionID = generateInteractionIDWithoutIncreasingInteractionCount();
+
+ m_performanceEventTimingCandidates.append(it->value);
+ it->value = entry;
+ return;
+ }
+
m_performanceEventTimingCandidates.append(entry);
- // FIXME: implement InteractionId logic
- m_performanceEventTimingCandidates.last().target = event.target();
- m_performanceEventTimingCandidates.last().processingEnd = performance().nowInReducedResolutionSeconds();
}
void LocalDOMWindow::dispatchPendingEventTimingEntries()
{
+ auto renderingTime = performance().nowInReducedResolutionSeconds();
+ if (m_pendingPointerDown && !m_pendingPointerDown->duration)
+ m_pendingPointerDown->duration = renderingTime - m_pendingPointerDown->startTime;
+
if (m_performanceEventTimingCandidates.isEmpty())
return;
- auto renderingTime = performance().nowInReducedResolutionSeconds();
LOG_WITH_STREAM(PerformanceTimeline, stream << "Dispatching " << m_performanceEventTimingCandidates.size() << " event timing entries at t=" << renderingTime);
for (auto& candidateEntry : m_performanceEventTimingCandidates) {
- performance().countEvent(candidateEntry.typeInfo.type());
- auto duration = renderingTime - candidateEntry.startTime;
- performance().processEventEntry(candidateEntry, duration);
+ performance().countEvent(candidateEntry.type);
+ candidateEntry.duration = renderingTime - candidateEntry.startTime;
+ performance().processEventEntry(candidateEntry);
}
m_performanceEventTimingCandidates.clear();
}
diff --git a/Source/WebCore/page/LocalDOMWindow.h b/Source/WebCore/page/LocalDOMWindow.h
index 4e6e50286f619..e05e0f998d66c 100644
--- a/Source/WebCore/page/LocalDOMWindow.h
+++ b/Source/WebCore/page/LocalDOMWindow.h
@@ -30,9 +30,8 @@
#include
#include
#include
-#include
#include
-#include
+#include
#include
#include
#include
@@ -282,9 +281,10 @@ class LocalDOMWindow final
void finishedLoading();
// EventTiming API
- PerformanceEventTiming::Candidate initializeEventTimingEntry(const Event&, EventTypeInfo);
- void finalizeEventTimingEntry(const PerformanceEventTiming::Candidate&, const Event&);
+ PerformanceEventTimingCandidate initializeEventTimingEntry(const Event&, EventType);
+ void finalizeEventTimingEntry(PerformanceEventTimingCandidate&, const Event&);
void dispatchPendingEventTimingEntries();
+ uint64_t interactionCount() { return m_interactionCount; }
// HTML 5 key/value storage
ExceptionOr sessionStorage();
@@ -397,6 +397,12 @@ class LocalDOMWindow final
void failedToRegisterDeviceMotionEventListener();
#endif
+ EventTimingInteractionID computeInteractionID(const Event&, EventType);
+ EventTimingInteractionID currentInteractionID();
+ EventTimingInteractionID& ensureUserInteractionValue();
+ EventTimingInteractionID generateInteractionID();
+ EventTimingInteractionID generateInteractionIDWithoutIncreasingInteractionCount();
+
bool isSameSecurityOriginAsMainFrame() const;
#if ENABLE(GAMEPAD)
@@ -439,7 +445,18 @@ class LocalDOMWindow final
mutable RefPtr m_closeWatcherManager;
// Equivalent to the list of PerformanceEventTiming objects mentioned in https://www.w3.org/TR/event-timing/#sec-modifications-HTML :
- Vector m_performanceEventTimingCandidates;
+ Vector m_performanceEventTimingCandidates;
+
+ // FIXME: support multiple pointers by replacing std::optional
+ // with map objects using PointerID as key:
+ Markable m_pointerMap;
+ std::optional m_pendingPointerDown;
+
+ bool m_contextMenuTriggered { false };
+
+ HashMap, WTF::SignedWithZeroKeyHashTraits> m_pendingKeyDowns;
+ std::optional m_userInteractionValue;
+ uint64_t m_interactionCount { 0 };
String m_status;
diff --git a/Source/WebCore/page/Performance.cpp b/Source/WebCore/page/Performance.cpp
index 8327ae9bea631..29e6d7b735cff 100644
--- a/Source/WebCore/page/Performance.cpp
+++ b/Source/WebCore/page/Performance.cpp
@@ -42,6 +42,7 @@
#include "EventNames.h"
#include "ExceptionOr.h"
#include "LocalFrame.h"
+#include "Logging.h"
#include "PerformanceEntry.h"
#include "PerformanceEventTiming.h"
#include "PerformanceMarkOptions.h"
@@ -163,6 +164,13 @@ EventCounts* Performance::eventCounts()
return m_eventCounts.get();
}
+uint64_t Performance::interactionCount()
+{
+ ASSERT(is(scriptExecutionContext()));
+ ASSERT(isMainThread());
+ return downcast(*scriptExecutionContext()).window()->interactionCount();
+}
+
PerformanceNavigation* Performance::navigation()
{
if (!is(scriptExecutionContext()))
@@ -202,6 +210,9 @@ Vector[> Performance::getEntries() const
if (m_firstContentfulPaint)
entries.append(*m_firstContentfulPaint);
+ if (m_firstInput)
+ entries.append(*m_firstInput);
+
std::ranges::sort(entries, PerformanceEntry::startTimeCompareLessThan);
return entries;
}
@@ -226,6 +237,9 @@ Vector][> Performance::getEntriesByType(const String& entryT
entries.appendVector(m_userTiming->getMeasures());
}
+ if (entryType == "first-input"_s && m_firstInput)
+ entries.append(*m_firstInput);
+
std::ranges::sort(entries, PerformanceEntry::startTimeCompareLessThan);
return entries;
}
@@ -254,6 +268,11 @@ Vector][> Performance::getEntriesByName(const String& name,
entries.appendVector(m_userTiming->getMeasures(name));
}
+ if (entryType.isNull() || entryType == "first-input"_s) {
+ if (m_firstInput && name == m_firstInput->name())
+ entries.append(*m_firstInput);
+ }
+
std::ranges::sort(entries, PerformanceEntry::startTimeCompareLessThan);
return entries;
}
@@ -293,25 +312,29 @@ void Performance::countEvent(EventType type)
eventCounts()->add(type);
}
-void Performance::processEventEntry(PerformanceEventTiming::Candidate& candidate, Seconds duration)
+void Performance::processEventEntry(const PerformanceEventTimingCandidate& candidate)
{
- // FIXME: handle firstInput based on InteractionId
- if (!m_firstInput) {
- m_firstInput = PerformanceEventTiming::create(candidate, duration, true);
+ // Constants to avoid the need to round to the appropriate resolution
+ // (PerformanceEventTiming::durationResolution) when filtering candidates:
+ static constexpr Seconds minDurationCutoffBeforeRounding = PerformanceEventTiming::minimumDurationThreshold - (PerformanceEventTiming::durationResolution / 2);
+ static constexpr Seconds defaultDurationCutoffBeforeRounding = PerformanceEventTiming::defaultDurationThreshold - (PerformanceEventTiming::durationResolution / 2);
+
+ if (!m_firstInput && candidate.interactionID.value) {
+ m_firstInput = PerformanceEventTiming::create(candidate, true);
queueEntry(*m_firstInput);
}
- if (duration < minDurationCutoffBeforeRounding)
+ if (candidate.duration < minDurationCutoffBeforeRounding)
return;
// FIXME: early return more often by keeping track of m_observers; we
// should keep track of the minimum defaultThreshold and whether any
// observers are interested in 'event' entries:
- if (duration <= defaultDurationCutoffBeforeRounding && !m_observers.size())
+ if (candidate.duration <= defaultDurationCutoffBeforeRounding && !m_observers.size())
return;
- auto entry = PerformanceEventTiming::create(candidate, duration);
- if (m_eventTimingBuffer.size() < m_eventTimingBufferSize && duration > defaultDurationCutoffBeforeRounding)
+ auto entry = PerformanceEventTiming::create(candidate);
+ if (m_eventTimingBuffer.size() < m_eventTimingBufferSize && candidate.duration > defaultDurationCutoffBeforeRounding)
m_eventTimingBuffer.append(entry);
queueEntry(entry);
@@ -536,6 +559,7 @@ void Performance::queueEntry(PerformanceEntry& entry)
if (!shouldScheduleTask)
return;
+ LOG_WITH_STREAM(PerformanceTimeline, stream << "PerformanceEntry of type " << entry.name() << " dispatched to interested observer at t=" << now());
scheduleTaskIfNeeded();
}
diff --git a/Source/WebCore/page/Performance.h b/Source/WebCore/page/Performance.h
index 1f2d385420ce6..e4b09eb830275 100644
--- a/Source/WebCore/page/Performance.h
+++ b/Source/WebCore/page/Performance.h
@@ -36,7 +36,6 @@
#include "DOMHighResTimeStamp.h"
#include "EventTarget.h"
#include "EventTargetInterfaces.h"
-#include "PerformanceEventTiming.h"
#include "ReducedResolutionSeconds.h"
#include "ScriptExecutionContext.h"
#include "Timer.h"
@@ -69,6 +68,7 @@ class ResourceResponse;
class ResourceTiming;
class ScriptExecutionContext;
enum class EventType : uint16_t;
+struct PerformanceEventTimingCandidate;
struct PerformanceMarkOptions;
struct PerformanceMeasureOptions;
template class ExceptionOr;
@@ -87,7 +87,7 @@ class Performance final : public RefCounted, public ContextDestruct
PerformanceTiming* timing();
EventCounts* eventCounts();
- unsigned interactionCount() { return 0; }
+ uint64_t interactionCount();
Vector][> getEntries() const;
Vector][> getEntriesByType(const String& entryType) const;
@@ -95,7 +95,7 @@ class Performance final : public RefCounted, public ContextDestruct
void appendBufferedEntriesByType(const String& entryType, Vector][>&, PerformanceObserver&) const;
void countEvent(EventType);
- void processEventEntry(PerformanceEventTiming::Candidate&, Seconds duration);
+ void processEventEntry(const PerformanceEventTimingCandidate&);
void clearResourceTimings();
void setResourceTimingBufferSize(unsigned);
@@ -160,18 +160,13 @@ class Performance final : public RefCounted, public ContextDestruct
Timer m_resourceTimingBufferFullTimer;
Vector][> m_backupResourceTimingBuffer;
- RefPtr m_firstInput;
- Vector][> m_eventTimingBuffer;
+ RefPtr m_firstInput;
+ Vector][> m_eventTimingBuffer;
// Sizes recommended by https://w3c.github.io/timing-entrytypes-registry/#registry:
unsigned m_eventTimingBufferSize { 150 };
unsigned m_resourceTimingBufferSize { 250 };
- // Constants to avoid the need to round to PerformanceEventTiming::durationResolution
- // when filtering candidate entries:
- static constexpr Seconds minDurationCutoffBeforeRounding = PerformanceEventTiming::minimumDurationThreshold - (PerformanceEventTiming::durationResolution / 2);
- static constexpr Seconds defaultDurationCutoffBeforeRounding = PerformanceEventTiming::defaultDurationThreshold - (PerformanceEventTiming::durationResolution / 2);
-
// https://w3c.github.io/resource-timing/#dfn-resource-timing-buffer-full-flag
bool m_resourceTimingBufferFullFlag { false };
bool m_waitingForBackupBufferToBeProcessed { false };
diff --git a/Source/WebCore/page/PerformanceEventTiming.cpp b/Source/WebCore/page/PerformanceEventTiming.cpp
index 6d8864bcca464..842d226c8544e 100644
--- a/Source/WebCore/page/PerformanceEventTiming.cpp
+++ b/Source/WebCore/page/PerformanceEventTiming.cpp
@@ -28,23 +28,26 @@
#include "Document.h"
#include "DocumentInlines.h"
+#include "EventNames.h"
#include "EventTargetInlines.h"
#include "Node.h"
+#include "PerformanceEventTimingCandidate.h"
#include
namespace WebCore {
-Ref PerformanceEventTiming::create(const Candidate& candidate, Seconds duration, bool isFirst)
+Ref PerformanceEventTiming::create(const PerformanceEventTimingCandidate& candidate, bool isFirst)
{
- return adoptRef(*new PerformanceEventTiming(candidate, duration, isFirst));
+ return adoptRef(*new PerformanceEventTiming(candidate, isFirst));
}
-PerformanceEventTiming::PerformanceEventTiming(const Candidate& candidate, Seconds duration, bool isFirst)
- : PerformanceEntry(eventNames().eventNameFromEventType(candidate.typeInfo.type()), candidate.startTime.milliseconds(), candidate.startTime.milliseconds() + durationResolutionInMilliseconds*std::round(duration.milliseconds() / durationResolutionInMilliseconds))
+PerformanceEventTiming::PerformanceEventTiming(const PerformanceEventTimingCandidate& candidate, bool isFirst)
+ : PerformanceEntry(eventNames().eventNameFromEventType(candidate.type), candidate.startTime.milliseconds(), candidate.startTime.milliseconds() + durationResolutionInMilliseconds*std::round(candidate.duration.milliseconds() / durationResolutionInMilliseconds))
, m_isFirst(isFirst)
, m_cancelable(candidate.cancelable)
, m_processingStart(candidate.processingStart)
, m_processingEnd(candidate.processingEnd)
+ , m_interactionID(candidate.interactionID)
, m_target(candidate.target)
{ }
@@ -76,9 +79,9 @@ ASCIILiteral PerformanceEventTiming::entryType() const
return m_isFirst ? "first-input"_s : "event"_s;
}
-unsigned PerformanceEventTiming::interactionId() const
+uint64_t PerformanceEventTiming::interactionId() const
{
- return 0;
+ return m_interactionID.value;
}
} // namespace WebCore
diff --git a/Source/WebCore/page/PerformanceEventTiming.h b/Source/WebCore/page/PerformanceEventTiming.h
index d09960d392f04..53da240347c6a 100644
--- a/Source/WebCore/page/PerformanceEventTiming.h
+++ b/Source/WebCore/page/PerformanceEventTiming.h
@@ -26,35 +26,27 @@
#pragma once
-#include
-#include
-#include
-#include
+#include "DOMHighResTimeStamp.h"
+#include "EventTarget.h"
+#include "EventTimingInteractionID.h"
+#include "PerformanceEntry.h"
#include
namespace WebCore {
-class EventTarget;
class Node;
+struct PerformanceEventTimingCandidate;
class PerformanceEventTiming final : public PerformanceEntry {
public:
- struct Candidate {
- EventTypeInfo typeInfo { };
- bool cancelable { false };
- Seconds startTime { 0 };
- Seconds processingStart { 0 };
- Seconds processingEnd { 0 };
- WeakPtr target { nullptr };
- };
- static Ref create(const Candidate&, Seconds duration, bool isFirst = false);
+ static Ref create(const PerformanceEventTimingCandidate&, bool isFirst = false);
~PerformanceEventTiming();
DOMHighResTimeStamp processingStart() const { return m_processingStart.milliseconds(); }
DOMHighResTimeStamp processingEnd() const { return m_processingEnd.milliseconds(); }
bool cancelable() const { return m_cancelable; }
Node* target() const;
- unsigned interactionId() const;
+ uint64_t interactionId() const;
Type performanceEntryType() const final;
ASCIILiteral entryType() const final;
@@ -65,11 +57,13 @@ class PerformanceEventTiming final : public PerformanceEntry {
static constexpr Seconds defaultDurationThreshold = 104_ms;
private:
- PerformanceEventTiming(const Candidate&, Seconds duration, bool isFirst);
+ PerformanceEventTiming(const PerformanceEventTimingCandidate&, bool isFirst);
+
bool m_isFirst;
bool m_cancelable;
Seconds m_processingStart;
Seconds m_processingEnd;
+ EventTimingInteractionID m_interactionID;
WeakPtr m_target;
};
diff --git a/Source/WebCore/page/PerformanceEventTimingCandidate.h b/Source/WebCore/page/PerformanceEventTimingCandidate.h
new file mode 100644
index 0000000000000..21f4ac237875e
--- /dev/null
+++ b/Source/WebCore/page/PerformanceEventTimingCandidate.h
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2025 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include "EventTarget.h"
+#include "EventTimingInteractionID.h"
+#include
+
+namespace WebCore {
+
+enum class EventType : uint16_t;
+
+struct PerformanceEventTimingCandidate {
+ EventType type { };
+ bool cancelable { false };
+ Seconds startTime;
+ Seconds processingStart;
+ Seconds processingEnd;
+ Seconds duration;
+ WeakPtr target;
+ EventTimingInteractionID interactionID;
+};
+
+}
diff --git a/Source/WebCore/page/PerformanceObserver.cpp b/Source/WebCore/page/PerformanceObserver.cpp
index 76ef48f34ef39..cf4228d92a7e3 100644
--- a/Source/WebCore/page/PerformanceObserver.cpp
+++ b/Source/WebCore/page/PerformanceObserver.cpp
@@ -30,6 +30,7 @@
#include "InspectorInstrumentation.h"
#include "LocalDOMWindow.h"
#include "Performance.h"
+#include "PerformanceEventTiming.h"
#include "PerformanceObserverEntryList.h"
#include "WorkerGlobalScope.h"
@@ -37,6 +38,7 @@ namespace WebCore {
PerformanceObserver::PerformanceObserver(ScriptExecutionContext& scriptExecutionContext, Ref&& callback)
: m_callback(WTFMove(callback))
+ , m_durationThreshold(PerformanceEventTiming::defaultDurationThreshold)
{
if (RefPtr document = dynamicDowncast(scriptExecutionContext)) {
if (auto* window = document->window())
diff --git a/Source/WebCore/page/PerformanceObserver.h b/Source/WebCore/page/PerformanceObserver.h
index 6db6759565cfe..4e3baa45ab337 100644
--- a/Source/WebCore/page/PerformanceObserver.h
+++ b/Source/WebCore/page/PerformanceObserver.h
@@ -83,10 +83,10 @@ class PerformanceObserver : public RefCounted {
Vector][> m_entriesToDeliver;
const Ref m_callback;
OptionSet m_typeFilter;
+ Seconds m_durationThreshold;
bool m_registered { false };
bool m_isTypeObserver { false };
bool m_hasNavigationTiming { false };
- Seconds m_durationThreshold = PerformanceEventTiming::defaultDurationThreshold;
};
} // namespace WebCore
]