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
isTrustedattribute value is set to false, return false. -
If event’s
typeis one of the following:auxclick,click,dblclick,mousedown,mouseenter,mouseleave,mouseout,mouseover,mouseup,pointerover,pointerenter,pointerdown,pointerup,pointercancel,pointerout,pointerleave,gotpointercapture,lostpointercapturetouchstart,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:
-
pointerdownwhich 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
firstInputDelayon 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
nameattribute’s getter provides the associated event’stype. entryType- The
entryTypeattribute’s getter returns "event" (for long events) or "first" (for the first user interaction).- input startTime- The
startTimeattribute’s getter returns the associated event’stimeStamp. duration- The
durationattribute’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
processingStartattribute’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
processingEndattribute’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 toprocessingStartwhen there are no such event handlers. cancelable- The
cancelableattribute’s getter returns the associated event’scancelableattribute 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
Documentin 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
PerformanceEventTimingobject. -
Set timingEntry’s
entryTypeto "event". -
Set timingEntry’s
startTimeto event’stimeStampattribute value. -
Set timingEntry’s
processingStartto processingStart. -
Set timingEntry’s
cancelableto event’scancelableattribute value. -
Return timingEntry.
3.4. Finalize event timing
-
If timingEntry is null, return.
-
Let relevantGlobal be target’s relevant global object.
-
Set timingEntry’s
processingEndto 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
startTimeattribute value. -
Set timingEntry’s
durationby running the following steps:-
Let difference be
renderingTimestamp.- start -
Set timingEntry’s
durationto the result of rounding difference to the nearest multiple of 8.
-
-
Let name be timingEntry’s
nameattribute value. -
Perform the following steps to update the event counts:
-
Let performance be window’s
performanceattribute value. -
If performance’s
eventCountsattribute 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
eventCountsattribute value.
-
-
Otherwise, increase the map entry’s value by 1.
-
-
If timingEntry’s
durationattribute 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
entryTypeof 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
entryTypeto "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.