forked from w3c/event-timing
-
Notifications
You must be signed in to change notification settings - Fork 0
/
index.bs
709 lines (593 loc) · 43.8 KB
/
index.bs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
<pre class=metadata>
Title: Event Timing API
Status: ED
Shortname: event-timing
Group: webperf
Level: none
Editor: Nicolás Peña Moreno, Google https://google.com, [email protected], w3cid 103755
Tim Dresser, Google https://google.com, [email protected], w3cid 68214
URL: https://w3c.github.io/event-timing/
TR: https://www.w3.org/TR/event-timing/
Repository: https://github.com/WICG/event-timing
Test Suite: https://github.com/web-platform-tests/wpt/tree/master/event-timing
Abstract: This document defines an API that provides web page authors with insights into the latency of certain events triggered by user interactions.
Default Highlight: js
Complain About: accidental-2119 yes
</pre>
<pre class=anchors>
urlPrefix: https://w3c.github.io/performance-timeline/; spec: PERFORMANCE-TIMELINE-2;
type: interface; url: #the-performanceentry-interface; text: PerformanceEntry;
type: attribute; for: PerformanceEntry;
text: name; url: #dom-performanceentry-name;
text: entryType; url: #dom-performanceentry-entrytype;
text: startTime; url: #dom-performanceentry-starttime;
text: duration; url: #dom-performanceentry-duration;
type: dfn; url: #dfn-register-a-performance-entry-type; text: register a performance entry type;
type: dfn; url: #dfn-queue-a-performanceentry; text: queue the entry;
type: attribute; for: PerformanceObserver;
text: supportedEntryTypes; url: #supportedentrytypes-attribute;
type: method; for: PerformanceObserver;
text: observe(); url: #observe-method;
type: dictionary; url: #performanceobserverinit-dictionary; text: PerformanceObserverInit;
type: attribute; for: PerformanceObserverInit;
text: entryTypes; url: #dom-performanceobserverinit-entrytypes;
text: type; url: #dom-performanceobserverinit-type;
type: dfn; url: #dfn-performance-entry-buffer; text: performance entry buffer;
type: interface; url: #the-performanceobserver-interface; text: PerformanceObserver;
type: dfn; for: PerformanceObserver; text: observer buffer; url: #dfn-observer-buffer;
type: dfn; for: registered performance observer;
text: options list; url: #dfn-options-list;
text: observer; url: #dfn-observer;
urlPrefix: https://w3c.github.io/hr-time/; spec: HR-TIME-2;
type: typedef; url: #idl-def-domhighrestimestamp; text: DOMHighResTimeStamp;
type: interface; url: #dom-performance; text: Performance;
type: method; for:Performance;
text: now(); url: #dom-performance-now;
type: dfn; text: current high resolution time; url: #dfn-current-high-resolution-time;
type: attribute; for: WindowOrWorkerGlobalScope;
text: performance; url: #dom-windoworworkerglobalscope-performance;
urlPrefix: https://tc39.github.io/ecma262/; spec: ECMASCRIPT;
type: dfn; url: #sec-math.round; text: Math.round;
urlPrefix: https://dom.spec.whatwg.org/; spec: DOM;
type: attribute; for: Event;
text: type; url: #dom-event-type;
text: timeStamp; url: #dom-event-timestamp;
text: cancelable; url: #dom-event-cancelable;
text: isTrusted; url: #dom-event-istrusted;
type: dfn; url: #concept-event-dispatch; text: event dispatch algorithm
urlPrefix: https://w3c.github.io/pointerevents/; spec: POINTEREVENTS;
type: event; url: #the-pointerover-event; text: pointerover;
type: event; url: #the-pointerenter-event; text: pointerenter;
type: event; url: #the-pointerdown-event; text: pointerdown;
type: event; url: #the-pointermove-event; text: pointermove;
type: event; url: #the-pointerrawupdate-event; text: pointerrawupdate;
type: event; url: #the-pointerup-event; text: pointerup;
type: event; url: #the-pointercancel-event; text: pointercancel;
type: event; url: #the-pointerout-event; text: pointerout;
type: event; url: #the-pointerleave-event; text: pointerleave;
type: event; url: #the-gotpointercapture-event; text: gotpointercapture;
type: event; url: #the-lostpointercapture-event; text: lostpointercapture;
urlPrefix: https://w3c.github.io/touch-events/; spec: TOUCH-EVENTS;
type: interface; url: #touchevent-interface; text: TouchEvent;
type: event; url: #the-touchstart-event; text: touchstart;
type: event; url: #the-touchend-event; text: touchend;
type: event; url: #the-touchmove-event; text: touchmove;
type: event; url: #the-touchcancel-event; text: touchcancel;
urlPrefix: https://w3c.github.io/paint-timing/; spec: PAINT-TIMING;
type: dfn; url: #mark-paint-timing; text: mark paint timing;
urlPrefix: https://w3c.github.io/uievents/; spec: UIEVENTS;
type: event; url: #event-type-auxclick; text: auxclick;
type: event; url: #event-type-click; text: click;
type: event; url: #event-type-contextmenu; text: contextmenu;
type: event; url: #event-type-dblclick; text: dblclick;
type: event; url: #event-type-mousedown; text: mousedown;
type: event; url: #event-type-mouseenter; text: mouseenter;
type: event; url: #event-type-mouseleave; text: mouseleave;
type: event; url: #event-type-mousemove; text: mousemove;
type: event; url: #event-type-mouseout; text: mouseout;
type: event; url: #event-type-mouseover; text: mouseover;
type: event; url: #event-type-mouseup; text: mouseup;
type: event; url: #event-type-keydown; text: keydown;
type: event; url: #event-type-keypress; text: keypress;
type: event; url: #event-type-keyup; text: keyup;
type: event; url: #event-type-wheel; text: wheel;
type: event; url: #event-type-beforeinput; text: beforeinput;
type: event; url: #event-type-input; text: input;
type: event; url: #event-type-compositionstart; text: compositionstart;
type: event; url: #event-type-compositionupdate; text: compositionupdate;
type: event; url: #event-type-compositionend; text: compositionend;
urlPrefix: https://html.spec.whatwg.org/multipage/; spec: HTML;
type: event; url: #event-dnd-drag; text: drag;
type: event; url: #event-dnd-dragstart; text: dragstart;
type: event; url: #event-dnd-dragenter; text: dragenter;
type: event; url: #event-dnd-dragleave; text: dragleave;
type: event; url: #event-dnd-dragover; text: dragover;
type: event; url: #event-dnd-drop; text: drop;
type: event; url: #event-dnd-dragend; text: dragend;
urlPrefix: https://wicg.github.io/element-timing/; spec: ELEMENT-TIMING;
type: dfn; url: #get-an-element; text: get an element;
</pre>
Introduction {#sec-intro}
=====================
<div class="non-normative">
<em>This section is non-normative.</em>
When a user engages with a website, they expect their actions to cause changes to the website quickly.
In fact, <a href=https://www.nngroup.com/articles/response-times-3-important-limits/>research</a> 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 {{Event/timeStamp}}.
In addition, {{Performance/now()|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 <b>input delay</b>: 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 <b>first input delay</b>, the delay in processing for the first "discrete" input event.
</div>
Events exposed {#sec-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.
<div algorithm="considered for Event Timing">
Given an <var>event</var>, to determine if it should be <dfn>considered for Event Timing</dfn>, run the following steps:
1. If <var>event</var>'s {{Event/isTrusted}} attribute value is set to false, return false.
1. If <var>event</var>'s {{Event/type}} is one of the following:
<!-- MouseEvents -->
{{auxclick}}, {{click}}, {{contextmenu}}, {{dblclick}}, {{mousedown}}, {{mouseenter}}, {{mouseleave}}, {{mouseout}}, {{mouseover}}, {{mouseup}},
<!-- PointerEvents -->
{{pointerover}}, {{pointerenter}}, {{pointerdown}}, {{pointerup}}, {{pointercancel}}, {{pointerout}}, {{pointerleave}}, {{gotpointercapture}}, {{lostpointercapture}},
<!-- TouchEvents -->
{{touchstart}}, {{touchend}}, {{touchcancel}},
<!-- KeyboardEvents -->
{{keydown}}, {{keypress}}, {{keyup}},
<!-- WheelEvents excluded for now since they can be continuous. -->
<!-- InputEvents -->
{{beforeinput}}, {{input}},
<!-- CompositionEvents -->
{{compositionstart}}, {{compositionupdate}}, {{compositionend}},
<!-- Drag and Drop -->
{{dragstart}}, {{dragend}}, {{dragenter}}, {{dragleave}}, {{dragover}}, {{drop}},
return true.
1. Return false.
</div>
Note: {{mousemove}}, {{pointermove}}, {{pointerrawupdate}}, {{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.
<div class="non-normative">
<em>
The remainder of this section is non-normative.
It explains at a high level the information that is exposed in the [[#sec-processing-model]] section.
</em>
An {{Event}}'s <b>delay</b> is the difference between the time when the browser is about to run event handlers for the event and the {{Event}}'s {{Event/timeStamp}}.
The former point in time is exposed as the {{PerformanceEventTiming}}'s {{PerformanceEventTiming/processingStart}},
whereas the latter is exposed as {{PerformanceEventTiming}}'s {{PerformanceEntry/startTime}}.
Therefore, an {{Event}}'s delay can be computed as <code>{{PerformanceEventTiming/processingStart}} - {{PerformanceEntry/startTime}}</code>.
The Event Timing API exposes a {{PerformanceEntry/duration}} value, which is meant to be the time from when user interaction occurs
(estimated via the {{Event}}'s {{Event/timeStamp}}) to the next time the rendering of the {{Event}}'s [=relevant global object=]'s <a>associated Document</a>'s is updated.
This value is provided with 8 millisecond granularity.
By default, the Event Timing API buffers and exposes entries when the {{PerformanceEntry/duration}} is 104 or greater,
but a developer can set up a {{PerformanceObserver}} to observe future entries with a different threshold.
Note that this does not change the entries that are buffered and hence the
<a href="https://w3c.github.io/performance-timeline/#dom-performanceobserverinit-buffered">buffered</a> flag only enables receiving past entries with duration greater than or equal to the default threshold.
The Event Timing API also exposes timing information about the <b>first input</b> of a {{Window}} <var>window</var>,
defined as the first {{Event}} (with {{Event/isTrusted}} set) whose [=relevant global object=] is <var>window</var> and whose {{Event/type}} is one of the following:
* {{keydown}}
* {{mousedown}}
* {{pointerdown}} which is followed by {{pointerup}}
* {{click}}
This enables computing the <b>first input delay</b>, the <b>delay</b> of the <b>first input</b>.
Note that the Event Timing API creates entries for events regardless of whether they have any event listeners.
In particular, the first click or the first key might not be the user actually trying to interact with the page functionality;
many users do things like select text while they're reading or click in blank areas to control what has focus.
This is a design choice to capture problems with pages which register their event listeners too late and to capture performance
of inputs that are meaningful despite not having event listeners, such as hover effects.
Developers can choose to ignore such entries by ignoring those with essentially zero values of
<code>{{PerformanceEventTiming/processingEnd}} - {{PerformanceEventTiming/processingStart}}</code>,
as {{PerformanceEventTiming/processingEnd}} is the time when the <a>event dispatch algorithm</a> algorithm has concluded.
</div>
Usage example {#sec-example}
------------------------
<pre class="example highlight">
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;
// Obtain some information about the target of this event, such as the id.
const targetId = firstInput.target ? firstInput.target.id : 'unknown-target';
// 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});
}
</pre>
The following example computes a dictionary mapping interactionId to the maximum duration of any of its events.
This dictionary can later be aggregated and reported to analytics.
<pre class="example highlight">
let maxDurations = {};
new PerformanceObserver(list => {
list.getEntries().forEach(entry => {
if (entry.interactionId > 0) {
let id = entry.interactionId;
if (!maxDurations[id]) {
maxDurations[id] = entry.duration;
} else {
maxDurations[id] = Math.max(maxDurations[id], entry.duration);
}
}
})
}).observe({type: 'event', buffered: true, durationThreshold: 16});
</pre>
The following are sample use cases that could be achieved by using this API:
* Gather <b>first input delay</b> data 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).
Event Timing {#sec-event-timing}
=======================================
Event Timing adds the following interfaces:
{{PerformanceEventTiming}} interface {#sec-performance-event-timing}
------------------------------------------------------------------------
<pre class="idl">
[Exposed=Window]
interface PerformanceEventTiming : PerformanceEntry {
readonly attribute DOMHighResTimeStamp processingStart;
readonly attribute DOMHighResTimeStamp processingEnd;
readonly attribute boolean cancelable;
readonly attribute Node? target;
readonly attribute unsigned long long interactionId;
[Default] object toJSON();
};
</pre>
Each {{PerformanceEventTiming}} object has an associated <dfn for=PerformanceEventTiming>eventTarget</dfn>, which is initially set to null.
The <dfn export>target</dfn> attribute's getter returns the result of the <a link-for=''>get an element</a> algorithm, passing <a>this</a>'s <a>eventTarget</a> and null as inputs.
Note: A user agent implementing the Event Timing API would need to include "<code>first-input</code>" and "<code>event</code>" in {{PerformanceObserver/supportedEntryTypes}} for {{Window}} contexts.
This allows developers to detect support for event timing.
<div class="non-normative">
<em>
This remainder of this section is non-normative.
The values of the attributes of {{PerformanceEventTiming}} are set in the processing model in [[#sec-processing-model]].
This section provides an informative summary of how they will be set.
</em>
Each {{PerformanceEventTiming}} object reports timing information about an <dfn for=PerformanceEventTiming>associated {{Event}}</dfn>.
{{PerformanceEventTiming}} extends the following attributes of the {{PerformanceEntry}} interface:
<dl>
<dt>{{PerformanceEntry/name}}</dt>
<dd>The {{PerformanceEntry/name}} attribute's getter provides the <a>associated event</a>'s {{Event/type}}.</dd>
<dt>{{PerformanceEntry/entryType}}</dt>
<dd>The {{PerformanceEntry/entryType}} attribute's getter returns "<code>event</code>" (for long events) or "<code>first-input</code>" (for the first user interaction).</dd>
<dt>{{PerformanceEntry/startTime}}</dt>
<dd>The {{PerformanceEntry/startTime}} attribute's getter returns the <a>associated event</a>'s {{Event/timeStamp}}.</dd>
<dt>{{PerformanceEntry/duration}}</dt>
<dd>The {{PerformanceEntry/duration}} attribute's getter returns the difference between
the next time the <a>update the rendering</a> steps are completed for the <a>associated event</a>'s {{Document}} after the <a>associated event</a> has been dispatched,
and the {{PerformanceEntry/startTime}}, rounded to the nearest 8ms.</dd>
</dl>
{{PerformanceEventTiming}} has the following additional attributes:
<dl dfn-type=attribute dfn-for=PerformanceEventTiming link-for=PerformanceEventTiming>
<dt>{{processingStart}}</dt>
<dd>
The <dfn export>processingStart</dfn> attribute's getter returns a timestamp captured at the beginning of the <a link-for=''>event dispatch algorithm</a>. This is when event handlers are about to be executed.
</dd>
<dt>{{processingEnd}}</dt>
<dd>
The <dfn export>processingEnd</dfn> attribute's getter returns a timestamp captured at the end of the <a link-for=''>event dispatch algorithm</a>. This is when event handlers have finished executing. It's equal to {{processingStart}} when there are no such event handlers.
</dd>
<dt>{{cancelable}}</dt>
<dd>
The <dfn export>cancelable</dfn> attribute's getter returns the <a>associated event</a>'s {{Event/cancelable}} attribute value.
</dd>
<dt>{{target}}</dt>
<dd>
The {{target}} attribute's getter returns the <a>associated event</a>'s last {{Event/target}} when such {{/Node}} is not disconnected nor in the shadow DOM.
</dd>
<dt>{{interactionId}}</dt>
<dd link-for=>
The <dfn export>interactionId</dfn> attribute's getter returns the ID that uniquely identifies the user interaction which triggered the <a>associated event</a>. This attribute is 0 unless the <a>associated event</a>'s {{Event/type}} attribute value is one of:
* A {{pointerdown}}, {{pointerup}}, or {{click}} belonging to a user tap or drag. Note that {{pointerdown}} that ends in scroll is excluded.
* A {{keydown}} or {{keyup}} belonging to a user key press.
</dd>
</dl>
</div>
{{EventCounts}} interface {#sec-event-counts}
------------------------
<pre class="idl">
[Exposed=Window]
interface EventCounts {
readonly maplike<DOMString, unsigned long long>;
};
</pre>
The {{EventCounts}} object is a map where the keys are event <a href=Event/type>types</a> and the values are the number of events that have been dispatched that are of that {{Event/type}}.
Only events whose {{Event/type}} is supported by {{PerformanceEventTiming}} entries (see section [[#sec-events-exposed]]) are counted via this map.
Extensions to the {{Performance}} interface {#sec-extensions}
------------------------
<pre class="idl">
[Exposed=Window]
partial interface Performance {
[SameObject] readonly attribute EventCounts eventCounts;
readonly attribute unsigned long long interactionCount;
};
</pre>
The {{Performance/eventCounts}} attribute's getter returns <a>this</a>'s <a>relevant global object</a>'s <a for=Window>eventCounts</a>.
The {{Performance/interactionCount}} attribute's getter returns <a>this</a>'s <a>relevant global object</a>'s <a for=Window>interactionCount</a>.
Processing model {#sec-processing-model}
========================================
Modifications to the DOM specification {#sec-modifications-DOM}
--------------------------------------------------------
<em>This section will be removed once [[DOM]] has been modified.</em>
<div algorithm="additions to event dispatch">
We modify the <a>event dispatch algorithm</a> as follows.
Right after step 1, we add the following steps:
1. Let |interactionId| be the result of <a lt='compute interactionId'>computing interactionId</a> given |event|.
1. Let |timingEntry| be the result of <a lt='initialize event timing'>initializing event timing</a> given |event|, the <a>current high resolution time</a>, and |interactionId|.
Right before the returning step of that algorithm, add the following step:
1. <a>Finalize event timing</a> passing |timingEntry|, |event|, <em>target</em>, and the <a>current high resolution time</a> as inputs.
</div>
Note: If a user agent skips the <a>event dispatch algorithm</a>, it can still choose to include an entry for that {{Event}}.
In this case, it will estimate the value of {{PerformanceEventTiming/processingStart}} and set the {{PerformanceEventTiming/processingEnd}} to the same value.
Modifications to the HTML specification {#sec-modifications-HTML}
--------------------------------------------------------
<em>This section will be removed once [[HTML]] has been modified.</em>
Each {{Window}} has the following associated concepts:
* <dfn>entries to be queued</dfn>, a list that stores {{PerformanceEventTiming}} objects, which will initially be empty.
* <dfn>pending first pointer down</dfn>, a pointer to a {{PerformanceEventTiming}} entry which is initially null.
* <dfn>has dispatched input event</dfn>, a boolean which is initially set to false.
* <dfn>user interaction value</dfn>, an integer which is initially set to a random integer between 100 and 10000.
Note: the <a>user interaction value</a> is set to a random integer instead of 0 so that developers do not rely on it to count the number of interactions in the page.
By starting at a random value, developers are less likely to use it as the source of truth for the number of interactions that have occurred in the page.
* <dfn>pending key downs</dfn>, a <a>map</a> of integers to {{PerformanceEventTiming|PerformanceEventTimings}} which is initially empty.
* <dfn>pointer interaction value map</dfn>, a <a>map</a> of integers which is initially empty.
* <dfn>pointer is drag set</dfn>, a <a for=/>set</a> of integers which is initially empty.
* <dfn>pending pointer downs</dfn>, a <a>map</a> of integers to {{PerformanceEventTiming|PerformanceEventTimings}} which is initially empty.
* <dfn for=Window>eventCounts</dfn>, a map with entries of the form <var>type</var> → <var>numEvents</var>.
This means that there have been <var>numEvents</var> dispatched such that their {{Event/type}} attribute value is equal to <var>type</var>.
Upon construction of a {{Performance}} object whose [=relevant global object=] is a {{Window}}, its <a>eventCounts</a> must be initialized to a map containing 0s for all event types that the user agent supports from the list described in [[#sec-events-exposed]].
* <dfn for=Window>interactionCount</dfn>, an integer which counts the total number of distinct user interactions, for which there was a unique {{interactionId}} computed via <a lt='compute interactionId'>computing interactionId</a>.
<div algorithm="additions to update rendering">
In the <a>update the rendering</a> step of the <a>event loop processing model</a>, add a step right after the step that calls <a>mark paint timing</a>:
1. For each <a>fully active</a> {{Document}} in <em>docs</em>, invoke the algorithm to <a>dispatch pending Event Timing entries</a> for that {{Document}}.
</div>
Modifications to the Performance Timeline specification {#sec-modifications-perf-timeline}
--------------------------------------------------------
<em>This section will be removed once [[PERFORMANCE-TIMELINE-2]] had been modified.</em>
The {{PerformanceObserverInit}} dictionary is augmented:
<pre class="idl">
partial dictionary PerformanceObserverInit {
DOMHighResTimeStamp durationThreshold;
};
</pre>
Should add PerformanceEventTiming {#sec-should-add-performanceeventtiming}
--------------------------------------------------------
Note: The following algorithm is used in the [[PERFORMANCE-TIMELINE-2]] specification to determine
when a {{PerformanceEventTiming}} entry needs to be added to the buffer of a {{PerformanceObserver}}
or to the performance timeline, as described in the <a href=
https://w3c.github.io/timing-entrytypes-registry/#dfn-should-add-entry>registry</a>.
<div algorithm="should add PerformanceEventTiming"/>
Given a {{PerformanceEventTiming}} |entry| and a {{PerformanceObserverInit}} |options|, to
determine if we <dfn export>should add PerformanceEventTiming</dfn>, with |entry| and
optionally |options| as inputs, run the following steps:
1. If |entry|'s {{PerformanceEntry/entryType}} attribute value equals to "<code>first-input</code>", return true.
1. Assert that |entry|'s {{PerformanceEntry/entryType}} attribute value equals "<code>event</code>".
1. Let |minDuration| be computed as follows:
1. If |options| is not present or if |options|'s {{PerformanceObserverInit/durationThreshold}} is not present, let |minDuration| be 104.
1. Otherwise, let |minDuration| be the maximum between 16 and |options|'s {{PerformanceObserverInit/durationThreshold}} value.
1. If |entry|'s {{PerformanceEntry/duration}} attribute value is greater than or equal to |minDuration|, return true.
1. Otherwise, return false.
</div>
Increasing interaction count {#sec-increasing-interaction-count}
--------------------------------------------------------
<div algorithm="increase interaction count">
When asked to <dfn>increase interaction count</dfn> given a {{Window}} |window| object, perform the following steps:
1. Increase |window|'s <a>user interaction value</a> value by a small number chosen by the user agent.
1. Let |interactionCount| be |window|'s <a>interactionCount</a>.
1. Set |interactionCount| to |interactionCount| + 1.
</div>
Computing interactionId {#sec-computing-interactionid}
--------------------------------------------------------
<div algorithm="computing interactionId">
When asked to <dfn export>compute interactionId</dfn> with |event| as input, run the following steps:
1. If |event|'s {{Event/isTrusted}} attribute value is false, return 0.
1. Let |type| be |event|'s {{Event/type}} attribute value.
1. If |type| is not one among {{keyup}}, {{compositionstart}}, {{input}}, {{pointercancel}}, {{pointermove}}, {{pointerup}}, or {{click}}, return 0.
Note: {{keydown}} and {{pointerdown}} are handled in <a>finalize event timing</a>.
1. Let |window| be |event|'s <a>relevant global object</a>.
1. Let |pendingKeyDowns| be |window|'s <a>pending key downs</a>.
1. Let |pointerMap| be |window|'s <a>pointer interaction value map</a>.
1. Let |pointerIsDragSet| be |window|'s <a>pointer is drag set</a>.
1. Let |pendingPointerDowns| be |window|'s <a>pending pointer downs</a>.
1. If |type| is {{keyup}}:
1. If |event|'s {{KeyboardEvent/isComposing}} attribute value is true, return 0.
1. Let |code| be |event|'s {{KeyboardEvent/keyCode}} attribute value.
1. If |pendingKeyDowns|[|code|] does not exist, return 0.
1. Let |entry| be |pendingKeyDowns|[|code|].
1. <a>Increase interaction count</a> on |window|.
1. Let |interactionId| be |window|'s <a>user interaction value</a> value.
1. Set |entry|'s {{PerformanceEventTiming/interactionId}} to |interactionId|.
1. Add |entry| to |window|'s <a>entries to be queued</a>.
1. <a for=map>Remove</a> |pendingKeyDowns|[|code|].
1. Return |interactionId|.
1. If |type| is {{compositionstart}}:
1. For each |entry| in the <a for=map>values</a> of |pendingKeyDowns|:
1. Append |entry| to |window|'s <a>entries to be queued</a>.
1. <a for=map>Clear</a> |pendingKeyDowns|.
1. Return 0.
1. If |type| is {{input}}:
1. If |event| is not an instance of {{InputEvent}}, return 0.
Note: this check is done to exclude {{Event|Events}} for which the {{Event/type}} is {{input}} but that are not about modified text content.
1. If |event|'s {{InputEvent/isComposing}} attribute value is false, return 0.
1. <a>Increase interaction count</a> on |window|.
1. Return |window|'s <a>user interaction value</a>.
1. Otherwise (|type| is {{pointercancel}}, {{pointermove}}, {{pointerup}}, or {{click}}):
1. Let |pointerId| be |event|'s {{PointerEvent/pointerId}} attribute value.
1. If |type| is {{click}}:
1. If |pointerMap|[|pointerId|] does not exist, return 0.
1. Let |value| be |pointerMap|[|pointerId|].
1. Remove |pointerMap|[|pointerId|].
1. Remove [|pointerId|] from |pointerIsDragSet|.
1. Return |value|.
1. If |type| is {{pointermove}}:
1. Add |pointerId| to |pointerIsDragSet|.
1. Return 0.
1. Assert that |type| is {{pointerup}} or {{pointercancel}}.
1. If |pendingPointerDowns|[|pointerId|] does not exist, return 0.
1. Let |pointerDownEntry| be |pendingPointerDowns|[|pointerId|].
1. Assert that |pointerDownEntry| is a {{PerformanceEventTiming}} entry.
1. If |type| is {{pointerup}}:
1. Let |interactionType| be <code>"tap"</code>.
1. If |pointerIsDragSet| contains [|pointerId|] exists, set |interactionType| to <code>"drag"</code>.
1. <a>Increase interaction count</a> on |window|.
1. Set |pointerMap|[|pointerId|] to |window|'s <a>user interaction value</a>.
1. Set |pointerDownEntry|'s {{PerformanceEventTiming/interactionId}} to |pointerMap|[|pointerId|].
1. Append |pointerDownEntry| to |window|’s <a>entries to be queued</a>.
1. Remove |pendingPointerDowns|[|pointerId|].
1. If |type| is {{pointercancel}}, return 0.
1. Return |pointerMap|[|pointerId|].
</div>
Note: the algorithm attempts to assign events to the corresponding interactiond IDs.
For keyboard events, a {{keydown}} triggers a new interaction ID, whereas a {{keyup}} has to match its ID with a previous {{keydown}}.
For pointer events, when we get a {{pointerdown}} we have to wait until {{pointercancel}} or {{pointerup}} occur to know its {{PerformanceEventTiming/interactionId}}.
We try to match {{click}} with a previous interaction ID from a {{pointerdown}}.
If {{pointercancel}} or {{pointerup}} happens, we'll be ready to set the {{PerformanceEventTiming/interactionId}} for the stored entry corresponding to {{pointerdown}}.
If it is {{pointercancel}}, this means we do not want to assign a new interaction ID to the {{pointerdown}}.
If it is {{pointerup}}, we compute a new interaction ID and set it on both the {{pointerdown}} and the {{pointerup}} (and later, the {{click}} if it occurs).
The <a>user interaction value</a> is increased by a small number chosen by the user agent instead of 1 to discourage developers from considering it as a counter of the number of user interactions that have occurred in the web application.
A user agent may choose to increase it by a small random integer every time.
A user agent must not pick a global random integer and increase the <a>user interaction value</a>s of all {{Window|Windows}} by that amount because this could introduce cross origin leaks.
Initialize event timing {#sec-init-event-timing}
--------------------------------------------------------
<div algorithm="initialize event timing">
When asked to <dfn export>initialize event timing</dfn>, with <var>event</var>, <var>processingStart</var>, and |interactionId| as inputs, run the following steps:
1. If the algorithm to determine if <var>event</var> should be <a>considered for Event Timing</a> returns false, then return null.
1. Let <var>timingEntry</var> be a new {{PerformanceEventTiming}} object with |event|'s [=relevant realm=].
1. Set <var>timingEntry</var>'s {{PerformanceEntry/name}} to <var>event</var>'s {{Event/type}} attribute value.
1. Set <var>timingEntry</var>'s {{PerformanceEntry/entryType}} to "<code>event</code>".
1. Set <var>timingEntry</var>'s {{PerformanceEntry/startTime}} to <var>event</var>'s {{Event/timeStamp}} attribute value.
1. Set <var>timingEntry</var>'s {{processingStart}} to <var>processingStart</var>.
1. Set <var>timingEntry</var>'s {{cancelable}} to <var>event</var>'s {{Event/cancelable}} attribute value.
1. Set <var>timingEntry</var>'s {{interactionId}} to |interactionId|.
1. Return <var>timingEntry</var>.
</div>
Finalize event timing {#sec-fin-event-timing}
--------------------------------------------------------
<div algorithm="finalize event timing">
When asked to to <dfn export>finalize event timing</dfn>, with |timingEntry|, |event|, |target|, and |processingEnd| as inputs, run the following steps:
1. If <var>timingEntry</var> is null, return.
1. Let <var>relevantGlobal</var> be <var>target</var>'s <a>relevant global object</a>.
1. If <var>relevantGlobal</var> does not <a>implement</a> {{Window}}, return.
1. Set <var>timingEntry</var>'s {{processingEnd}} to <var>processingEnd</var>.
1. Assert that <var>target</var> <a>implements</a> {{Node}}.
Note: this assertion holds due to the types of events supported by the Event Timing API.
1. Set <var>timingEntry</var>'s <a for=PerformanceEventTiming>eventTarget</a> to the result of calling the <a>get an element</a> algorithm with <var>target</var> and <var>relevantGlobal</var>'s <a>associated document</a> as inputs.
Note: This will set <a for=PerformanceEventTiming>eventTarget</a> to the last event target. So if <a>retargeting</a> occurs, the last target, closest to the <a for=tree>root</a>, will be used.
Issue: Change the linking of the "get an element" algorithm once it is moved to the DOM spec.
1. If |event|'s {{Event/type}} attribute value is not {{keydown}} nor {{pointerdown}}, append |timingEntry| to |relevantGlobal|’s <a>entries to be queued</a>.
1. If |event|'s {{Event/type}} attribute value is {{pointerdown}}:
1. Let |pendingPointerDowns| be |relevantGlobal|'s <a>pending pointer downs</a>.
1. Let |pointerId| be |event|'s {{PointerEvent/pointerId}}.
1. If |pendingPointerDowns|[|pointerId|] exists, append |pendingPointerDowns|[|pointerId|] to |relevantGlobal|'s <a>entries to be queued</a>.
1. Set |pendingPointerDowns|[|pointerId|] to |timingEntry|.
1. Otherwise (|event|'s {{Event/type}} attribute value is {{keydown}}):
1. If |event|'s {{KeyboardEvent/isComposing}} attribute value is <code>true</code>:
1. Append |timingEntry| to |relevantGlobal|’s <a>entries to be queued</a>.
1. Return.
1. Let |pendingKeyDowns| be |relevantGlobal|'s <a>pending key downs</a>.
1. Let |code| be |event|'s {{KeyboardEvent/keyCode}} attribute value.
1. If |pendingKeyDowns|[|code|] exists:
1. Let |entry| be |pendingKeyDowns|[|code|].
1. If |code| is not 229:
Note: 229 is a special case since it corresponds to IME keyboard events. Sometimes multiple of these are sent by the user agent, and they do not correspond holding a key down repeatedly.
1. Increase |window|'s <a>user interaction value</a> value by a small number chosen by the user agent.
1. Set |entry|'s {{PerformanceEventTiming/interactionId}} to |window|'s <a>user interaction value</a>.
1. Add |entry| to |window|'s <a>entries to be queued</a>.
1. <a for=map>Remove</a> |pendingKeyDowns|[|code|].
1. Set |pendingKeyDowns|[|code|] to |timingEntry|.
</div>
Dispatch pending Event Timing entries {#sec-dispatch-pending}
--------------------------------------------------------
<div algorithm="dispatch pending Event Timing entries">
When asked to <dfn export>dispatch pending Event Timing entries</dfn> for a {{Document}} <var>doc</var>, run the following steps:
1. Let <var>window</var> be <var>doc</var>'s <a>relevant global object</a>.
1. Let <var>renderingTimestamp</var> be the <a>current high resolution time</a>.
1. For each <var>timingEntry</var> in <var>window</var>'s <a>entries to be queued</a>:
1. <a>Set event timing entry duration</a> passing |timingEntry|, |window|, and |renderingTimestamp|.
1. If |timingEntry|'s {{PerformanceEntry/duration}} attribute value is greater than or equal to 16, then <a lt='queue the entry'>queue</a> |timingEntry|.
1. Clear |window|'s <a>entries to be queued</a>.
1. For each |pendingDown| in the <a lt="get the values">values</a> from |window|'s <a>pending pointer downs</a>:
1. <a>Set event timing entry duration</a> passing |pendingDown|, |window|, and |renderingTimestamp|.
</div>
<div algorithm="set event timing entry duration">
When asked to <dfn>set event timing entry duration</dfn> given a {{PerformanceEventTiming}} |timingEntry|, a {{Window}} |window|, and a {{DOMHighResTimeStamp}} |renderingTimestamp|, perform the following steps:
1. If |timingEntry|'s {{PerformanceEntry/duration}} attribute value is nonzero, return.
1. Let <var>start</var> be <var>timingEntry</var>'s {{PerformanceEntry/startTime}} attribute value.
1. Set <var>timingEntry</var>'s {{PerformanceEntry/duration}} to a {{DOMHighResTimeStamp}} resulting from <code><var>renderingTimestamp</var> - <var>start</var></code>, with granularity of 8ms or less.
1. Let <var>name</var> be <var>timingEntry</var>'s {{PerformanceEntry/name}} attribute value.
1. Perform the following steps to update the event counts:
1. Let |eventCounts| be |window|'s <a>eventCounts</a>.
1. Assert that |eventCounts| <a for=map>contains</a> |name|.
1. <a for=map>Set</a> |eventCounts|[|name|] to |eventCounts|[|name|] + 1.
1. If <var>window</var>'s <a>has dispatched input event</a> is false, run the following steps:
1. If <var>name</var> is "<code>pointerdown</code>", run the following steps:
1. Set <var>window</var>'s <a>pending first pointer down</a> to a copy of <var>timingEntry</var>.
1. Set the {{PerformanceEntry/entryType}} of <var>window</var>'s <a>pending first pointer down</a> to "<code>first-input</code>".
1. Otherwise, run the following steps:
1. If <var>name</var> is "<code>pointerup</code>" AND if <var>window</var>'s <a>pending first pointer down</a> is not null, then:
1. Set <var>window</var>'s <a>has dispatched input event</a> to true.
1. <a lt='queue the entry'>Queue</a> <var>window</var>'s <a>pending first pointer down</a>.
1. Otherwise, if <var>name</var> is one of "<code>click</code>", "<code>keydown</code>" or "<code>mousedown</code>", then:
1. Set <var>window</var>'s <a>has dispatched input event</a> to true.
1. Let <var>newFirstInputDelayEntry</var> be a copy of <var>timingEntry</var>.
1. Set <var>newFirstInputDelayEntry</var>'s {{PerformanceEntry/entryType}} to "<code>first-input</code>".
1. <a>Queue the entry</a> <var>newFirstInputDelayEntry</var>.
</div>
Security & privacy considerations {#priv-sec}
===============================================
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()|performance.now()}}.
Since {{processingStart}} and {{processingEnd}} could be computed without using this API,
exposing these attributes does not produce new attack surfaces.
Thus, {{PerformanceEntry/duration}} is the only one which requires further consideration.
The {{PerformanceEntry/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 default cutoff value for the {{PerformanceEntry/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 within 100ms and will likely negatively impact user experience.
The choice of 16ms as the minimum value allowed for {{PerformanceObserverInit/durationThreshold}} is because it enables the typical use-case of
making sure that the response is smooth. In 120Hz displays, a response that skips more than a single frame will be at least 16ms, so the entry
corresponding to this user input will be surfaced in the API under the minimum value.