Event Timing API

Editor’s Draft,

This version:
https://wicg.github.io/event-timing
Test Suite:
https://github.com/web-platform-tests/wpt/tree/master/event-timing
Issue Tracking:
GitHub
Editors:
(Google)
(Google)

Abstract

This document defines an API that provides web page authors with insights into the latency of certain events triggered by user interactions.

Status of this document

This specification was published by the Web Platform Incubator Community Group. It is not a W3C Standard nor is it on the W3C Standards Track. Please note that under the W3C Community Contributor License Agreement (CLA) there is a limited opt-out and other conditions apply. Learn more about W3C Community and Business Groups.

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.

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. By requesting the user agent to compute 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 should be considered a key responsiveness 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.

1.1. Events exposed

The event timing API exposes timing information for any of the following events, in cases where the time difference between user input and paint operations that follow input processing exceeds a certain threshold:

The Event Timing API also exposes timing information about the first user interaction among the following:

1.2. Usage Example

    const observer = new PerformanceObserver(function(list) {
        const perfEntries = list.getEntries();
        for (let i = 0; i < perfEntries.length; i++) {
            // Process event and report to analytics and monitoring...
            const entry = perfEntries[i];
            if (entry.processingStart !== 0) {
                const inputDelay = entry.processingStart - entry.startTime;
                // Report the input delay when the processing start was provided.
            }
        }
    });
    // Register observer for event.
    observer.observe({entryTypes: ["event"]});
    ...
    // Later on, we can also directly query the first input information.
    const firstArray = performance.getEntriesByType('firstInput');
    if (firstArray.length !== 0) {
        const firstInput = firstArray[0];
        // Process the first input event and report back...
    }
}

The following are sample use cases that could be achieved by using this API:

2. Event Timing

Event Timing involves the following new interfaces:

2.1. PerformanceEventTiming interface

[Exposed=Window]
interface PerformanceEventTiming : PerformanceEntry {
    readonly attribute DOMHighResTimeStamp processingStart;
    readonly attribute DOMHighResTimeStamp processingEnd;
    readonly attribute boolean cancelable;
};

A user agent implementing the Event Timing API must perform the following steps:

  1. Run the register a performance entry type algorithm with "firstInput" as input.

  2. Run the register a performance entry type algorithm with "event" as input.

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’s type.
entryType
The entryType attribute’s getter returns "event" (for long events) or "firstInput" (for the first user interaction).
startTime
The startTime attribute’s getter returns the associated event’s timeStamp.
duration
The duration attribute’s getter must return the difference between the time of the first update the rendering occurring after associated event has been dispatched and the startTime, rounded up to the nearest 8 ms.

PerformanceEventTiming has the following additional attributes:

processingStart
The processingStart attribute’s getter returns the time when event handlers start to execute, or startTime if there are no event handlers.
processingEnd
The processingEnd attribute’s getter returns the time when event handlers have finished executing, or startTime if there are no event handlers.
cancelable
The cancelable attribute’s getter must return the associated event’s cancelable.

2.2. EventCounts interface

[Exposed=Window]
interface EventCounts {
    readonly maplike<DOMString, unsigned 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.

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, num-events>. This implies that there have been num-events 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.

Each Document has pendingEventEntries, a list that stores PerformanceEventTiming objects, which will initially be empty. Each Document also has pendingPointerDown, a pointer to a PerformanceEventTiming entry which is initially null. Finally, each Document has hasDispatchedEvent, a boolean which is initially set to false.

We modify the event dispatch algorithm as follows.

Right after step 1, we add the following step:

Right before the returning step of that algorithm, add the following steps:

3.2. Modifications to the HTML specification

This section will be removed once the HTML specification has been modified.

In the update the rendering step of the event loop processing model, add a step right after the step that calls mark paint timing:

  1. For each fully active Document in docs, invoke the algorithm to dispatch pending entries for that Document.

3.3. Initialize event timing

When asked to initialize event timing, with event and processing start as inputs, run the following steps:

  1. Let timingEntry be null.

  2. If event is a MouseEvent, PointerEvent TouchEvent, KeyboardEvent, WheelEvent, InputEvent, or CompositionEvent, AND if event’s isTrusted attribute value is set to true, then:

    1. Let timingEntry be a new PerformanceEventTiming object.

    2. Set timingEntry’s name to event’s type attribute value.

    3. Set timingEntry’s entryType to "event".

    4. Set timingEntry’s startTime as follows:

      1. If event’s type attribute value is equal to "pointermove", set timingEntry’s startTime to event.getCoalescedEvents()[0].startTime.

      2. Otherwise, set timingEntry’s startTime to event.timeStamp.

    5. Set timingEntry’s processingStart to processing start.

    6. Set timingEntry’s duration to 0.

    7. Set timingEntry’s cancelable to event’s cancelable attribute value.

  3. Return timingEntry.

3.4. Finalize event timing

When asked to to finalize event timing, with timingEntry, target, and processing end as inputs, run the following steps:

  1. If timingEntry is null, abort these steps.

  2. Set timingEntry’s processingEnd to processing end.

  3. Append timingEntry to target’s pendingEventEntries.

3.5. Dispatch pending entries

When asked to dispatch pending entries for a Document doc, run the following steps:

  1. Let window be doc’s associated Window.

  2. Let rendering-timestamp be the current high resolution time.

  3. For each timingEntry in doc’s pendingEventEntries:

    1. Set timingEntry’s duration by running the following steps:

      1. Let difference be rendering-timestamp - timingEntry.startTime.

      2. Set timingEntry’s duration to Math.ceil(difference/8)*8.

    2. Perform the following steps to update the event counts:

      1. If window.performance.eventCounts does not contain a key timingEntry.name, then set window.performance.eventCounts[timingEntry.name] to 1.

      2. Otherwise, increase window.performance.eventCounts[timingEntry.name] by 1.

    3. If timingEntry’s duration attribute value is greater than 56 AND if timingEntry’s processingStart attribute value is not equal to timingEntry’s processingEnd attribute value, then queue the entry timingEntry on window.

    4. If doc’s hasDispatchedEvent is false, run the following steps:

      1. Let newFirstInputDelayEntry be a copy of timingEntry.

      2. Set newFirstInputDelayEntry’s entryType to "firstInput".

      3. If newFirstInputDelayEntry’s name attribute value is "pointerdown", set doc’s pendingPointerDown to newFirstInputDelayEntry.

      4. Otherwise, run the following substeps:

        1. If newFirstInputDelayEntry’s name attribute value is "pointerup" AND if doc’s pendingPointerDown is not null, then:

          1. Set doc’s hasDispatchedEvent to true.

          2. Queue the entry pendingPointerDown.

        2. Otherwise, if newFirstInputDelayEntry’s name attribute value is one of "click", "keydown" or "mousedown", then:

          1. Set doc’s hasDispatchedEvent to true.

          2. Queue the entry newFirstInputDelayEntry.

3.6. Queueing other entries

Some user agents skip some steps of the event dispatch algorithm for some events. In fact, this is theoretically possible for any event which does not have associated event handlers or cause user agent defined behavior. If the input is the first input, then the user agent MUST always queue the corresponding "firstInput" entry and it MUST do so via the event dispatch algorithm. Otherwise, the user agent MAY opt not to queue the entry when there are no event handlers associated to the event. This provides user agents with the flexibility to ignore input which never blocks on the main thread and for which event dispatch algorithm is skipped.

To create an event timing entry outside of the event dispatch algorithm, the user agent must run the following steps:

  1. Let event be the event being measured and let target be the target Document.

  2. Run the initialize event timing algorithm, passing in event and 0 as inputs.

  3. Let timingEntry be the output from the algorithm.

  4. Run the finalize event timing algorithm, passing in timingEntry, target, and 0 as inputs.

Note: processing the first input entry via the event dispatch algorithm ensures that the first input’s duration is calculated accurately. If skipping first inputs without event handlers was allowed, or if they were processed outside of event dispatch algorithm, then statistical biases could be introduced. For example, consider a website which initially only has a few event handlers. This website would only receive first input entries for events targetting those event handlers, so the first input duration would be large. If the website then added a trivial event handler for the page, the first input duration would decrease because it would now receive many first inputs from events that do not trigger the non-trivial event handlers. But, in practice, the website having the trivial event handler will handle input more slowly.

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, 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 56 as the cutoff value for the duration is just the first multiple of 8 greater than 50.

Conformance

Conformance requirements are expressed with a combination of descriptive assertions and RFC 2119 terminology. The key words “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY”, and “OPTIONAL” in the normative parts of this document are to be interpreted as described in RFC 2119. However, for readability, these words do not appear in all uppercase letters in this specification.

All of the text of this specification is normative except sections explicitly marked as non-normative, examples, and notes. [RFC2119]

Examples in this specification are introduced with the words “for example” or are set apart from the normative text with class="example", like this:

This is an example of an informative example.

Informative notes begin with the word “Note” and are set apart from the normative text with class="note", like this:

Note, this is an informative note.

Index

Terms defined by this specification

Terms defined by reference

References

Normative References

[DOM]
Anne van Kesteren. DOM Standard. Living Standard. URL: https://dom.spec.whatwg.org/
[ECMASCRIPT]
ECMAScript Language Specification. URL: https://tc39.github.io/ecma262/
[HR-TIME-2]
Ilya Grigorik; James Simonsen; Jatinder Mann. High Resolution Time Level 2. 1 March 2018. CR. URL: https://www.w3.org/TR/hr-time-2/
[HTML]
Anne van Kesteren; et al. HTML Standard. Living Standard. URL: https://html.spec.whatwg.org/multipage/
[PAINT-TIMING]
Shubhie Panicker. Paint Timing 1. 7 September 2017. WD. URL: https://www.w3.org/TR/paint-timing/
[PERFORMANCE-TIMELINE-2]
Ilya Grigorik; Jatinder Mann; Zhiheng Wang. Performance Timeline Level 2. 8 December 2016. CR. URL: https://www.w3.org/TR/performance-timeline-2/
[POINTEREVENTS]
Jacob Rossi; Matt Brubeck. Pointer Events. 24 February 2015. REC. URL: https://www.w3.org/TR/pointerevents/
[RFC2119]
S. Bradner. Key words for use in RFCs to Indicate Requirement Levels. March 1997. Best Current Practice. URL: https://tools.ietf.org/html/rfc2119
[TOUCH-EVENTS]
Doug Schepers; et al. Touch Events. 10 October 2013. REC. URL: https://www.w3.org/TR/touch-events/
[UIEVENTS]
Gary Kacmarcik; Travis Leithead. UI Events. 4 August 2016. WD. URL: https://www.w3.org/TR/uievents/
[WebIDL]
Cameron McCormack; Boris Zbarsky; Tobie Langel. Web IDL. 15 December 2016. ED. URL: https://heycam.github.io/webidl/

IDL Index

[Exposed=Window]
interface PerformanceEventTiming : PerformanceEntry {
    readonly attribute DOMHighResTimeStamp processingStart;
    readonly attribute DOMHighResTimeStamp processingEnd;
    readonly attribute boolean cancelable;
};

[Exposed=Window]
interface EventCounts {
    readonly maplike<DOMString, unsigned long>;
};

[Exposed=Window]
partial interface Performance {
    [SameObject] readonly attribute EventCounts eventCounts;
};