-
Notifications
You must be signed in to change notification settings - Fork 14
/
index.bs
310 lines (250 loc) · 17.9 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
<pre class=metadata>
Title: Element Timing API
Status: CG-DRAFT
Shortname: element-timing
Group: WICG
Level: 1
Editor: Ian Clelland, Google https://google.com, iclelland@chromium.org, w3cid 76841
Tim Dresser, Google https://google.com, tdresser@chromium.org
Former Editor: Nicolás Peña Moreno, Google https://google.com, npm@chromium.org
URL: https://wicg.github.io/element-timing
Repository: https://github.com/WICG/element-timing
Test Suite: https://github.com/web-platform-tests/wpt/tree/master/element-timing
Abstract: This document defines an API that enables monitoring when large or developer-specified image elements and text nodes are displayed on screen.
Default Highlight: js
</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-queue-a-performanceentry; text: queue the PerformanceEntry;
type: interface; url: #the-performanceobserver-interface; text: PerformanceObserver;
type: attribute; for: PerformanceObserver;
text: supportedEntryTypes; url: #supportedentrytypes-attribute;
urlPrefix: https://w3c.github.io/IntersectionObserver; spec: INTERSECTION-OBSERVER;
type: dfn; url: #calculate-intersection-rect-algo; text: intersection rect algorithm;
urlPrefix: https://html.spec.whatwg.org/multipage; spec: HTML;
type: dfn; url: //webappapis.html#event-loop-processing-model; text: event loop processing model;
urlPrefix: https://drafts.csswg.org/css-backgrounds-3; spec: CSS-BACKGROUNDS-3;
type: dfn; url: #propdef-background-image; text: background-image;
urlPrefix: https://wicg.github.io/largest-contentful-paint/; spec: LARGEST-CONTENTFUL-PAINT;
type: dfn; url:#potentially-add-a-largestcontentfulpaint-entry; text: potentially add a LargestContentfulPaint entry;
urlPrefix: https://fetch.spec.whatwg.org/; spec: FETCH;
type: dfn; url:#concept-tao-check; text: timing allow check;
urlPrefix: https://w3c.github.io/paint-timing/; spec: PAINT-TIMING;
type: dfn; url:#set-of-owned-text-nodes; text: set of owned text nodes;
type: dfn; url:#process-image-that-finished-loading; text: process image that finished loading;
type: dfn; url:#timing-eligible; text: timing-eligible
type: dfn; url:#the-paint-timing-steps; text: the paint timing steps
type: dfn; url:#exposed-for-paint-timing; text: exposed for paint timing
type: dfn; url:#pending-image-record; text: pending image record
type: dfn; for:pending image record; url:#pending-image-record-element; text: element
type: dfn; for:pending image record; url:#pending-image-record-loadtime; text: loadTime
type: dfn; for:pending image record; url:#pending-image-record-request; text: request
</pre>
<pre class=link-defaults>
spec:dom; type:dfn; text:descendant
</pre>
Introduction {#sec-intro}
=====================
<em>This section is non-normative.</em>
Knowing when critical elements are displayed on screen is key to understanding page load performance.
While fast rendering of the essential components is not sufficient for a satisfactory loading experience, it is necessary.
Therefore, monitoring these rendering timestamps is important to improve and prevent regressions in page loads.
This specification gives developers and analytics providers an API to measure rendering timestamps of critical elements.
There is currently no good way to measure these timestamps for real users.
Existing approaches would require either registering observers too early or significant DOM manipulation.
These approaches are discussed on the [[#sec-security]] section.
Web developers are the experts in critical user interactions for their sites, so they should be allowed to tell the user agent which are the elements they care about.
Thus, this API exposes rendering timing information about web-developer-annotated elements.
Elements exposed {#sec-elements-exposed}
------------------------
The Element Timing API supports timing information about <a>timing-eligible</a>
elements, as defined by [[PAINT-TIMING]].
Elements that have a "<code>elementtiming</code>" content attribute are reported in the <a>report image element timing</a> and the <a>report text element timing</a> algorithms.
Usage example {#sec-example}
------------------------
The following example shows an image that is registered for observation via its <code>elementtiming</code> attribute, and an observer gathering the timing information.
<xmp class="example highlight" highlight=html>
<img... elementtiming='foobar'/>
<p elementtiming='important-paragraph'>This is text I care about.</p>
...
<script>
const observer = new PerformanceObserver((list) => {
let perfEntries = list.getEntries();
// Process the entries by iterating over them.
});
observer.observe({type: 'element', buffered: true});
</script>
</xmp>
The following are sample elements whose rendering timestamps could be measured by using this API and which should be compared to page navigation:
* The images in the image carousel of a shopping site.
* The main photo in a story of a news site.
* The title of a blog post.
* The first paragraph in an entry of an encyclopedia site.
The API could have use cases outside of page load by comparing the rendering timestamps with input timestamps.
For example, developers could monitor the time it takes for a widget to show up after a click that triggers it.
Element Timing {#sec-element-timing}
=======================================
Element Timing involves the following new interfaces:
{{PerformanceElementTiming}} interface {#sec-performance-element-timing}
------------------------------------------------------------------------
<pre class="idl">
[Exposed=Window]
interface PerformanceElementTiming : PerformanceEntry {
readonly attribute DOMHighResTimeStamp renderTime;
readonly attribute DOMHighResTimeStamp loadTime;
readonly attribute DOMRectReadOnly intersectionRect;
readonly attribute DOMString identifier;
readonly attribute unsigned long naturalWidth;
readonly attribute unsigned long naturalHeight;
readonly attribute DOMString id;
readonly attribute Element? element;
readonly attribute DOMString url;
[Default] object toJSON();
};
</pre>
A {{PerformanceElementTiming}} object reports timing information about one associated element.
Each {{PerformanceElementTiming}} object has these associated concepts, all of which are initially set to <code>null</code>:
* A <dfn>request</dfn> containing the <a>image request</a> (if the entry is for image content).
* An <dfn>element</dfn> containing the associated {{Element}}.
The associated concepts and some attributes for {{PerformanceElementTiming}} are specified in the processing model in [[#sec-report-image-element]] and [[#sec-report-text]].
The {{PerformanceEntry/entryType}} attribute's getter must return the {{DOMString}} <code>"element"</code>.
The {{PerformanceEntry/name}} attribute's getter must return the value it was initialized to.
The {{PerformanceEntry/startTime}} attribute's getter must return the value of <a>this</a>'s {{renderTime}} if it is not 0, and the value of <a>this</a>'s {{loadTime}} otherwise.
The {{PerformanceEntry/duration}} attribute's getter must return 0.
The {{PerformanceElementTiming/renderTime}} attribute must return the value it was initialized to.
The {{PerformanceElementTiming/loadTime}} attribute's getter must return the the value it was initialized to.
The {{PerformanceElementTiming/intersectionRect}} attribute must return the value it was initialized to.
The {{PerformanceElementTiming/identifier}} attribute's getter must return the value it was initialized to.
The {{PerformanceElementTiming/naturalWidth}} attribute must return the value it was initialized to.
The {{PerformanceElementTiming/naturalHeight}} attribute must return the value it was initialized to.
The {{PerformanceElementTiming/id}} attribute's getter must return the value it was initialized to.
The {{PerformanceElementTiming/element}} attribute's getter must perform the following steps:
<div algorithm="PerformanceElementTiming element">
1. If <a>this</a>'s <a>element</a> is not [=exposed for paint timing=] given null, return null.
1. Return <a>this</a>'s <a>element</a>.
</div>
Note: This means that an element that is no longer <a>descendant</a> of the {{Document}} will no longer be returned by {{PerformanceElementTiming/element}}'s attribute getter.
The {{PerformanceElementTiming/url}} attribute's getter must perform the following steps:
<div algorithm="PerformanceElementTiming url">
1. If <a>this</a>'s <a>request</a> is null, return the empty string.
1. Let |urlString| be <a>this</a>'s <a>request</a>'s <a for="image request">current URL</a>.
1. Let |url| be the result of <a lt="URL parser">parsing</a> |urlString|.
1. If |url|'s <a spec=url>scheme</a> is "`data`", trim |urlString| to its first 100 characters.
1. Return |urlString|.
</div>
Note: The URL is trimmed for data URLs to avoid excessive memory in the entry.
Processing model {#sec-processing-model}
========================================
Note: A user agent implementing the Element Timing API would need to include <code>"element"</code> in {{PerformanceObserver/supportedEntryTypes}} for {{Window}} contexts.
This allows developers to detect support for element timing.
Modifications to the DOM specification {#sec-modifications-DOM}
--------------------------------------------------------
<em>This section will be removed once the [[DOM]] specification has been modified.</em>
We extend the {{Element}} interface as follows:
<pre class="idl">
partial interface Element {
[CEReactions] attribute DOMString elementTiming;
};
</pre>
The {{Element/elementTiming}} attribute must <a>reflect</a> the element's "<code>elementtiming</code>" content attribute.
Report Element Timing {#sec-report-element-timing}
--------------------------------------------------
<div export algorithm="report element timing">
When asked to <dfn>report element timing</dfn> given a {{Document}} |doc|, a timestamp |now|, an [=ordered set=] of [=pending image records=] |paintedImages|, and an [=ordered set=] of [=/elements=] |paintedTextNodes|, perform the following steps:
1. For each |record| in |paintedImages|:
1. Run the <a>report image element timing</a> algorithm passing in |record|, |now|, and |doc|.
1. For each {{Element}} |element| in |paintedTextNodes|:
1. Run the <a>report text element timing</a> given |element|, |now|, and |doc|.
</div>
Report Image Element Timing {#sec-report-image-element}
--------------------------------------------------------
<div algorithm="report image element timing">
When asked to <dfn>report image element timing</dfn> given a [=pending image record=] |record|, a DOMHighResTimestamp |renderTime| and a {{Document}} |document|, perform the following steps:
1. If |record|'s [=pending image record/element=]'s "<code>elementtiming</code>" content attribute is absent, then abort these steps.
1. Let |intersectionRect| be the value returned by the <a>intersection rect algorithm</a> using |record|'s [=pending image record/element=] as the target and viewport as the root.
1. Create and initialize a {{PerformanceElementTiming}} object |entry| with |document|'s [=relevant realm=].
1. Initialize |entry|'s <a>request</a> to |record|'s [=pending image record/request=].
1. Initialize |entry|'s <a>element</a> to |record|'s [=pending image record/element=].
1. Initialize |entry|'s {{PerformanceEntry/name}} to the {{DOMString}} "image-paint".
1. Initialize |entry|'s {{renderTime}} to |renderTime|.
1. Initialize |entry|'s {{loadTime}} to |record|'s [=pending image record/loadTime=].
1. Initialize |entry|'s {{intersectionRect}} to |intersectionRect|.
1. Initialize |entry|'s {{identifier}} to |record|'s [=pending image record/element=]'s "<code>elementtiming</code>" content attribute.
1. Initialize |entry|'s {{PerformanceElementTiming/naturalWidth}} and {{PerformanceElementTiming/naturalHeight}} by running the same steps for an <{img}>'s {{HTMLImageElement/naturalWidth}} and {{HTMLImageElement/naturalHeight}} attribute getters, but using |record|'s [=pending image record/request=] as the image.
1. Initialize |entry|'s {{id}} to |record|'s [=pending image record/element=]'s "<code>id</code>" content attribute.
1. <a>Queue the PerformanceEntry</a> |entry|.
</div>
Report Text Element Timing {#sec-report-text}
--------------------------------------------------------
<div algorithm="report text element timing">
When asked to <dfn>report text element timing</dfn> given an {{Element}} |element|, a DOMHighResTimestamp |renderTime| and a {{Document}} |document|, perform the following steps:
1. If |element|'s "<code>elementtiming</code>" content attribute is absent, then abort these steps.
1. Let |intersectionRect| be an empty rectangle.
1. For each {{Text}} <a>node</a> |text| in |element|'s <a>set of owned text nodes</a>:
1. Augment |intersectionRect| to be smallest rectangle containing the border box of |text| and |intersectionRect|.
1. Intersect |intersectionRect| with the visual viewport.
1. Create and initialize a {{PerformanceElementTiming}} object |entry| with |document|'s [=relevant realm=].
1. Initialize |entry|'s <a>element</a> to |element|.
1. Initialize |entry|'s {{PerformanceEntry/name}} to the {{DOMString}} "text-paint".
1. Initialize |entry|'s {{renderTime}} to |renderTime|.
1. Initialize |entry|'s {{loadTime}} to 0.
1. Initialize |entry|'s {{intersectionRect}} to |intersectionRect|.
1. Initialize |entry|'s {{identifier}} to |element|'s "<code>elementtiming</code>" content attribute.
1. Initialize |entry|'s {{PerformanceElementTiming/naturalWidth}} and {{PerformanceElementTiming/naturalHeight}} to 0.
1. Initialize |entry|'s {{id}} to |element|'s "<code>id</code>" content attribute.
1. <a>Queue the PerformanceEntry</a> |entry|.
</div>
Security & privacy considerations {#sec-security}
===============================================
This API exposes some information about cross-origin images.
In particular, images that do not pass the <a>timing allow check</a> still have their resource load time exposed, which could be a source of privacy concerns.
However, this is considered to not add new attacks to the web platform because the ResourceTiming API exposes a similar timestamp already.
In addition, the onload handler exposes load timing when it is available, and the resource load time is a close proxy to this.
The <a>current high resolution time</a> computed at the beginning of the onload handler would provide the image load time.
We choose to expose the {{loadTime}} because it is very easy to obtain even without an onload handler.
In addition, we believe any fix to remove the leak provided by image onload handlers or ResourceTiming could also fix the leak provided by this API.
The {{renderTime}} (display timestamp) can also be polyfilled via the PaintTiming API.
To do this, add an iframe that contains trivial content on the onload handler of the target image or text content.
Then, query the first paint of that iframe to obtain the rendering timestamp of the content.
This is quite inefficient and the polyfill itself might affect the timing obtained.
Due to the difficulty in obtaining this information today, we choose not to expose the display timestamp for images that fail the <a>timing allow check</a>.
For clarity, here is a code snippet using the PaintTiming API:
<xmp class="example highlight" highlight=html>
// In the attacker frame.
<iframe src=attack.html></iframe>
<script>
window.onmessage = e => {
let timestamp = e.data;
// Acquired the display timestamp for 'victim.jpg'!
}
</script>
// In the attack.html iframe.
<img src='victim.jpg'/>
<script>
// Wait until onload or some time when the PaintTiming entries will be visible.
onload() => {
let entry = performance.getEntriesByType('paint')[0];
top.postMessage(entry.startTime, '*');
}
</script>
</xmp>
The other nontrivial parameter being exposed here is the {{intersectionRect}}.
This can already be polyfilled, for example using {{IntersectionObserver}}.
The polyfill process would be similar: add an {{IntersectionObserver}} on the onload handler of the target image or text content.
This solution is inefficient because it requires registering the observer once the content has loaded, but it should still provide the same level of accuracy.
For images, we compute the {{intersectionRect}} once the image has loaded if it does not pass the <a>timing allow check</a>.
Computing it at this point in time allows exposing the entry at that time.
If we were to compute the rect only until the image is fully displayed, we'd only be able to expose the entry after that time.
If we do not want to expose the rendering timetamp of an image, it's preferable to dispatch the entry to the {{PerformanceObserver}} right away.
Suppose we waited and exposed all the entries during the <a>report element timing</a> algorithm.
An attacker could infer nontrivial information about the rendering timestamp of an image.
It would do so by only observing the timing for that image.
Even though the timestamp is not exposed as a member of the {{PerformanceElementTiming}} entry received,
the fact that we wait until the next <a>update the rendering</a> step means that the attacker can distinguish between a very slow rendering time and a very fast rendering time by measuring the time at which it received the entry.
This would unintentionally leak some of the display timing of the image.