1. Introduction
This section is non-normative.
When a user engages with a website, they expect their actions to cause changes to the website quickly. In fact, research suggests that any user input that is not handled within 100ms is considered slow. Therefore, it is important to surface input events that could not achieve those guidelines.
A common way to monitor event latency consists of registering an event listener.
The timestamp at which the event was created can be obtained via the event’s timeStamp
.
In addition, performance.now()
could be called both at the beginning and at the end of the event handler logic.
By subtracting the hardware timestamp from the timestamp obtained at the beginning of the event handler,
the developer can compute the input delay: the time it takes for an input to start being processed.
By subtracting the timestamp obtained at the beginning of the event handler from the timestamp obtained at the end of the event handler,
the developer can compute the amount of synchronous work performed in the event handler.
Finally, when inputs are handled synchronously, the duration from event hardware timestamp to the next paint after the event is handled is a useful user experience metric.
This approach has several fundamental flaws. First, requiring event listeners precludes measuring event latency very early in the page load because listeners will likely not be registered yet at that point in time. Second, developers that are only interested in the input delay might be forced to add new listeners to events that originally did not have one. This adds unnecessary performance overhead to the event latency calculation. And lastly, it would be very hard to measure asynchronous work caused by the event via this approach.
This specification provides an alternative to event latency monitoring that solves some of these problems. Since the user agent computes the timestamps, there is no need for event listeners in order to measure performance. This means that even events that occur very early in the page load can be captured. This also enables visibility into slow events without requiring analytics providers to attempt to patch and subscribe to every conceivable event. In addition to this, the website’s performance will not suffer from the overhead of unneeded event listeners. Finally, this specification allows developers to obtain detailed information about the timing of the rendering that occurs right after the event has been processed. This can be useful to measure the overhead of website modifications that are triggered by events.
The very first user interaction has a disproportionate impact on user experience, and is often disproportionately slow. It’s slow because it’s often blocked on JavaScript execution that is not properly split into chunks during page load. The latency of the website’s response to the first user interaction can be considered a key responsiveness and loading metric. To that effect, this API surfaces all the timing information about this interaction, even when this interaction is not handled slowly. This allows developers to measure percentiles and improvements without having to register event handlers. In particular, this API enables measuring the first input delay (FID), the delay in processing for the first "discrete" input event.
1.1. Events exposed
The Event Timing API exposes timing information for certain events. Certain types of events are considered, and timing information is exposed when the time difference between user input and paint operations that follow input processing exceeds a certain threshold.
-
If event’s
isTrusted
attribute value is set to false, return false. -
If event’s
type
is one of the following:auxclick
,click
,dblclick
,mousedown
,mouseenter
,mouseleave
,mouseout
,mouseover
,mouseup
,pointerover
,pointerenter
,pointerdown
,pointerup
,pointercancel
,pointerout
,pointerleave
,gotpointercapture
,lostpointercapture
touchstart
,touchend
,touchcancel
,keydown
,keyup
,beforeinput
,input
,compositionstart
,compositionupdate
,compositionend
,dragstart
,dragend
,dragenter
,dragexit
,dragleave
,dragover
,drop
, return true. -
Return false.
Note: mousemove
, pointermove
, touchmove
, wheel
, and drag
are excluded because these are "continuous" events.
The current API does not have enough guidance on how to count and aggregate these events to obtain meaningful performance metrics based on entries.
Therefore, these event types are not exposed.
The Event Timing API also exposes timing information about the first user interaction among the following:
-
pointerdown
which is followed bypointerup
1.2. Usage example
const observer= new PerformanceObserver( function ( list) { const perfEntries= list. getEntries(). forEach( entry=> { const inputDelay= entry. processingStart- entry. startTime; // Report the input delay when the processing start was provided. // Also report the full input duration via entry.duration. }); }); // Register observer for event. observer. observe({ entryTypes: [ "event" ]}); ... // We can also directly query the first input information. new PerformanceObserver( function ( list, obs) { const firstInput= list. getEntries()[ 0 ]; // Measure the delay to begin processing the first input event. const firstInputDelay= firstInput. processingStart- firstInput. startTime; // Measure the duration of processing the first input event. // Only use when the important event handling work is done synchronously in the handlers. const firstInputDuration= firstInput. duration; // Process the first input delay and perhaps its duration... // Disconnect this observer since callback is only triggered once. obs. disconnect(); }). observe({ type: 'first-input' , buffered: true }); }
The following are sample use cases that could be achieved by using this API:
-
Gather FID data (see
firstInputDelay
on the example above) on a website and track its performance over time. -
Clicking a button changes the sorting order on a table. Measure how long it takes from the click until we display reordered content.
-
A user drags a slider to control volume. Measure the latency to drag the slider.
-
Hovering a menu item triggers a flyout menu. Measure the latency for the flyout to appear.
-
Measure the 75’th percentile of the latency of the first user click (whenever click happens to be the first user interaction).
2. Event Timing
Event Timing adds the following interfaces:
2.1. PerformanceEventTiming
interface
[Exposed =Window ]interface :
PerformanceEventTiming PerformanceEntry {readonly attribute DOMHighResTimeStamp processingStart ;readonly attribute DOMHighResTimeStamp processingEnd ;readonly attribute boolean cancelable ; [Default ]object (); };
toJSON
Note: A user agent implementing the Event Timing API would need to include "first
" and "event
" in supportedEntryTypes
for Window
contexts.
This allows developers to detect support for event timing.
This remainder of this section is non-normative.
The values of the attributes of PerformanceEventTiming
are set in the processing model in § 3 Processing model.
This section provides an informative summary of how they will be set.
Each PerformanceEventTiming
object reports timing information about an associated Event
.
PerformanceEventTiming
extends the following attributes of the PerformanceEntry
interface:
name
- The
name
attribute’s getter provides the associated event’stype
. entryType
- The
entryType
attribute’s getter returns "event
" (for long events) or "first
" (for the first user interaction).- input startTime
- The
startTime
attribute’s getter returns the associated event’stimeStamp
. duration
- The
duration
attribute’s getter returns the difference between the time of the first update the rendering step occurring after associated event has been dispatched and thestartTime
, rounded to the nearest 8ms.
PerformanceEventTiming
has the following additional attributes:
processingStart
- The
processingStart
attribute’s getter returns a timestamp captured at the beginning of the event dispatch algorithm. This is when event handlers are about to be executed. processingEnd
- The
processingEnd
attribute’s getter returns a timestamp captured at the end of the event dispatch algorithm. This is when event handlers have finished executing. It’s equal toprocessingStart
when there are no such event handlers. cancelable
- The
cancelable
attribute’s getter returns the associated event’scancelable
attribute value.
2.2. EventCounts
interface
[Exposed =Window ]interface {
EventCounts readonly maplike <DOMString ,unsigned long long >; };
The EventCounts
object is a map where the keys are event types and the values are the number of events that have been dispatched that are of that type
.
Only events whose type
is supported by PerformanceEventTiming
entries (see section § 1.1 Events exposed) are counted via this map.
2.3. Extensions to the Performance
interface
[Exposed =Window ]partial interface Performance { [SameObject ]readonly attribute EventCounts ; };
eventCounts
The eventCounts
attribute’s getter returns a map with entries of the form type → numEvents.
This means that there have been numEvents dispatched such that their type
attribute value is equal to type.
3. Processing model
3.1. Modifications to the DOM specification
This section will be removed once the DOM specification has been modified.
Right after step 1, we add the following step:
-
Let timingEntry be the result of initializing event timing given event and the current high resolution time.
Right before the returning step of that algorithm, add the following step:
-
Finalize event timing passing timingEntry, target, and the current high resolution time as inputs.
Note: If a user agent skips the event dispatch algorithm, it can still choose to include an entry for that Event
.
In this case, it will estimate the value of processingStart
and set the processingEnd
to the same value.
3.2. Modifications to the HTML specification
This section will be removed once the HTML specification has been modified.
Each Window
has pending event entries, a list that stores PerformanceEventTiming
objects, which will initially be empty.
Each Window
also has pending pointer down, a pointer to a PerformanceEventTiming
entry which is initially null.
Finally, each Window
has has dispatched input event, a boolean which is initially set to false.
-
For each fully active
Document
in docs, invoke the algorithm to dispatch pending Event Timing entries for thatDocument
.
3.3. Initialize event timing
-
If the algorithm to determine if event should be considered for Event Timing returns false, then return null.
-
Let timingEntry be a new
PerformanceEventTiming
object. -
Set timingEntry’s
entryType
to "event
". -
Set timingEntry’s
startTime
to event’stimeStamp
attribute value. -
Set timingEntry’s
processingStart
to processingStart. -
Set timingEntry’s
cancelable
to event’scancelable
attribute value. -
Return timingEntry.
3.4. Finalize event timing
-
If timingEntry is null, return.
-
Let relevantGlobal be target’s relevant global object.
-
Set timingEntry’s
processingEnd
to processingEnd. -
Append timingEntry to relevantGlobal’s pending event entries.
3.5. Dispatch pending Event Timing entries
Document
doc, run the following steps:
-
Let window be doc’s relevant global object.
-
Let renderingTimestamp be the current high resolution time.
-
For each timingEntry in window’s pending event entries:
-
Let start be timingEntry’s
startTime
attribute value. -
Set timingEntry’s
duration
by running the following steps:-
Let difference be
renderingTimestamp
.- start -
Set timingEntry’s
duration
to the result of rounding difference to the nearest multiple of 8.
-
-
Let name be timingEntry’s
name
attribute value. -
Perform the following steps to update the event counts:
-
Let performance be window’s
performance
attribute value. -
If performance’s
eventCounts
attribute value does not contain a map entry whose key is name, then:-
Let mapEntry be a new map entry with key equal to name and value equal to 1.
-
Add mapEntry to performance’s
eventCounts
attribute value.
-
-
Otherwise, increase the map entry’s value by 1.
-
-
If timingEntry’s
duration
attribute value is greater than or equal to 104, then queue timingEntry. -
If window’s has dispatched input event is false, run the following steps:
-
If name is "
pointerdown
", run the following steps:-
Set window’s pending pointer down to a copy of timingEntry.
-
Set the
entryType
of window’s pending pointer down to "first
".- input
-
-
Otherwise, run the following steps:
-
If name is "
pointerup
" AND if window’s pending pointer down is not null, then:-
Set window’s has dispatched input event to true.
-
Queue window’s pending pointer down.
-
-
Otherwise, if name is one of "
click
", "keydown
" or "mousedown
", then:-
Set window’s has dispatched input event to true.
-
Let newFirstInputDelayEntry be a copy of timingEntry.
-
Set newFirstInputDelayEntry’s
entryType
to "first
".- input -
Queue the entry newFirstInputDelayEntry.
-
-
-
-
4. Security & privacy considerations
We would not like to introduce more high resolution timers to the web platform due to the security concerns entailed by such timers.
Event handler timestamps have the same accuracy as performance.now()
.
Since processingStart
and processingEnd
could be computed without using this API,
exposing these attributes does not produce new attack surfaces.
Thus, duration
is the only one which requires further consideration.
The duration
has an 8 millisecond granularity (it is computed as such by performing rounding).
Thus, a high resolution timer cannot be produced from this timestamps.
However, it does introduce new information that is not readily available to web developers: the time pixels draw after an event has been processed.
We do not find security or privacy concerns on exposing the timestamp, especially given its granularity.
In an effort to expose the minimal amount of new information that is useful, we decided to pick 8 milliseconds as the granularity.
This allows relatively precise timing even for 120Hz displays.
The choice of 104ms as the cutoff value for the duration
is just the first multiple of 8 greater than 100ms.
An event whose rounded duration is greater than or equal to 104ms will have its pre-rounded duration greater than or equal to 100ms.
Such events are not handled in accordance with the RAIL performance model, which suggests applications respond within 100ms to user input.