- Notifications
You must be signed in to change notification settings - Fork0
A proposal for an Event Timing specification.
License
tdresser/event-timing
Folders and files
Name | Name | Last commit message | Last commit date | |
---|---|---|---|---|
Repository files navigation
Monitoring event latency today requires an event listener. This precludes measuring event latency early in page load, and adds unnecessary performance overhead.
This document provides a proposal for giving developers insight into the latency of a subset of events triggered by user interaction.
This proposal explains the minimal API required to solve the following use cases:
- Observe the queueing delay of input events before event handlers are registered.
- Measure combined event handler duration, including browser event handling logic.
A polyfill approximately implementing this API can be foundhere.
Only knowing about slow events doesn't provide enough context to determine if a site is getting better or worse. If a site change results in more engaged users, and the fraction of slow events remains constant, we expect an increase in the number of slow events. We also need to enable computing the fraction of events which are slow to avoid conflating changes in event frequency with changes in event latency.
To accomplish these goals, we introduce:
interface PerformanceEventTiming :PerformanceEntry{// The type of event dispatched. E.g. "touchmove".// Doesn't require an event listener of this type to be registered.readonlyattributeDOMStringname;// "event".readonlyattributeDOMStringentryType;// The event timestamp.readonlyattributeDOMHighResTimeStampstartTime;// The time the first event handler started to execute.// |startTime| if no event handlers executed.readonlyattributeDOMHighResTimeStampprocessingStart;// The time the last event handler finished executing.// |startTime| if no event handlers executed.readonlyattributeDOMHighResTimeStampprocessingEnd;// The duration between |startTime| and the next execution of step 7.12 in the HTML event loop processing model.readonlyattributeDOMHighResTimeStampduration;// Whether or not the event was cancelable.readonlyattributebooleancancelable;};// Contains the number of events which have been dispatched, per event type.interfaceEventCounts{readonlyattributeunsignedlongclick; ...readonlyattributeunsignedlongtouchmove; ...};partialinterfacePerformance{// Contains the number of events which have been dispatched, per event type. Populated asynchronously.readonlyattributeEventCountseventCounts;};
Make the following modifications to the "to dispatch an event algorithm".
LetpendingEventEntries
be an initially empty list ofPerformanceEventTiming
objects, stored per document.
Before step one, run these steps:
- If
event
is none of: "MouseEvent", "PointerEvent", "TouchEvent", "KeyboardEvent", "WheelEvent", "InputEvent", "CompositionEvent" andevent.type
isn't "scroll" then return. - Let newEntry be a new
PerformanceEventTiming
object. - Set newEntry's name attribute to
event.type
. - Set newEntry's entryType attribute to "event".
- Set newEntry's startTime attribute to
event.timeStamp
. - If
event.type
is "pointermove", set newEntry's startTime toevent.getCoalescedEvents()[0].startTime
. - Set newEntry's processingStart attribute to the value returned by
performance.now()
. - Set newEntry's duration attribute to 0.
- Set newEntry's cancelable attribute to
event.cancelable
.
After step 13
- Set
newEntry.processingEnd
to the value returned byperformance.now()
. - Append
newEntry
topendingEventEntries
.
After step 7.12 of theevent loop processing model
- For each
newEntry
inpendingEventEntries
:- Set newEntry's duration attribute to the value returned by
Math.round((performance.now() - newEntry.startTime)/8) * 8
- This value is rounded to the nearest 8ms to avoid providing a high resolution timer.
- Increment
performance.eventsCounts[newEntry.name]
. - If
newEntry.duration > 50 && newEntry.processingStart != newEntry.processingEnd
, queuenewEntry
on the current document. - If
newEntry.duration > 50 && newEntry.processingStart == newEntry.processingEnd
, the user agent MAY queuenewEntry
on the current document.
- Set newEntry's duration attribute to the value returned by
In the case where event handlers took no time, a user agent may opt not to queue the entry. This provides browsers the flexibility to ignore input which never blocks on the main thread.
To avoid adding another high resolution timer to the platform,duration
is rounded to the nearest multiple of 8. Event handler duration inherits it's precision fromperformance.now()
, and could previously be measured by overriding addEventListener, as demonstrated in the polyfill.
constperformanceObserver=newPerformanceObserver((entries)=>{for(constentryofentries.getEntries()){console.log(entry);}});performanceObserver.observe({entryTypes:['event']});
The very first user interaction has a disproportionate impact on user experience, and is often disproportionately slow. In Chrome, the 99'th percentile of theentry.processingStart
-entry.startTime
for the following events is over 1 second:
- Key down
- Mouse down
- Pointer down which is followed by a pointer up
- Click
This is ~4x the 99'th percentile of these events overall. In the median, we see ~10ms for the first event, and ~2.5ms for subsequent events.
This list intentionally excludes scrolls, which are often not blocked on javascript execution.
In order to address capture user pain caused by slow initial interactions, we propose a small addition to the event timing API specific to this use-case.
LetpendingPointerDown
benull
.LethasDispatchedEvent
be set tofalse
on navigationStart.
When iterating through the entries inpendingEventEntries
, after dispatchingnewEntry
:
- If
hasDispatchedEvent
istrue
, return. - Let
newFirstInputDelayEntry
be a copy ofnewEntry
. - Set
newFirstInputDelayEntry.entryType
tofirstInput
. - If
newFirstInputDelayEntry.type
is "pointerdown"`:- Set
pendingPointerDown
to newFirstInputDelayEntry - return
- Set
- Set
hasDispatchedEvent
totrue
. - If
newFirstInputDelayEntry.type
is "pointerup":- Queue
pendingPointerDown
- Queue
- If
newFirstInputDelayEntry.type
is one of "click", "keydown" or "mousedown":- Queue
newFirstInputDelayEntry
- Queue
FirstInputDelay can be polyfilled today: seehere for an example. However, this requires registering analytics JS before any events are processed, which is often not possible. First Input Delay can also be polyfilled on top of the event timing API, but it isn't very ergonomic, and due to the asynchrony ofperformance.eventCounts
can sometimes incorrectly report an event as the first event when there was a prior event less than 50ms.
About
A proposal for an Event Timing specification.
Resources
License
Uh oh!
There was an error while loading.Please reload this page.