, and background-images.
11 | We also support observing certain groups of text nodes. Web developers can better understand which are the critical images and text of their sites, so after annotating them the browser can provide timing information about those.
12 | This image and text converage means that the majority of web content can be timed via this API.
13 | 1. Enable analytics providers to measure display time of key images or text, without explicit opt in from web developers.
14 | In many cases it's not feasible for developers to modify their HTML just to get better performance insights, so it's important to provide basic information even for websites that cannot annotate their elements.
15 |
16 |
17 | ### How do we register elements for observation?
18 |
19 | An element can be registered for observation via the `elementtiming` HTML attribute.
20 | Having this attribute set will signal the browser to expose render timing information for this element (its source image, background image, and/or text).
21 | It should be noted that setting the `elementtiming` attribute does not work retroactively: once an element has loaded and is rendered, setting the attribute will have no effect.
22 | Thus, it is strongly encouraged to set the attribute before the element is added to the document (in HTML, or if set on Javascript, before adding it to the document).
23 | Having the attribute implies that:
24 |
25 | * If the element is an image, there will be an entry for the image.
26 | * If the element is affected by (possibly multiple) background images, there will be one entry for each of those background images.
27 | * If the element is associated to at least one text node, there will be one entry for the group of associated text nodes.
28 |
29 | ### Image considerations
30 |
31 | We define the **image rendering timestamp** as the next paint that occurs after the image has become fully loaded.
32 | This is important to distinguish as progressively rendered images may have multiple initial renderings before the image has even been fully received by the user agent.
33 |
34 | Allowing third-party origins to measure the time an arbitrary image resource takes to render could expose sensitive information such as whether a user is logged into a website.
35 | Therefore, for privacy and security reasons, the [image rendering timestamp](#image-time) is only exposed in entries corresponding to resources that pass the [timing allow check](https://w3c.github.io/resource-timing/#dfn-timing-allow-check).
36 | However, to enable a more holistic picture, the rest of the information is exposed for arbitrary images.
37 |
38 | ### Text considerations
39 |
40 | We say that a text node **belongs to** the closest block-level [Element](https://dom.spec.whatwg.org/#element) ancestor of the node.
41 | This means that an element could have 0 or many associated text nodes with it.
42 |
43 | We say that an element is *text-painted* if at least one text node [belongs to](#belong) and has been painted at least once.
44 | Thus, the **text rendering timestamp** of an element is the time when it becomes *text-painted*.
45 |
46 | Let the *text rect* of a text node be the display rectangle of that node within the viewport.
47 | We define the **text rect** of an element as the smallest rectangle which contains the geometric union of the text rects of all text nodes which [belong to](#belong) the element.
48 |
49 | ### What information is exposed?
50 |
51 | A `PerformanceElementTiming` entry has the following attributes:
52 | * `name`: for images, "image-paint". For text: "text-paint".
53 | * `entryType`: it will always be the string "element".
54 | * `startTime`: equal to `renderTime` if it is nonzero, otherwise equal to `loadTime`.
55 | * `duration`: always be set to 0.
56 | * `renderTime`: for images, the [image rendering timestamp](#image-time), or 0 when the resource does not pass the [timing allow check](https://w3c.github.io/resource-timing/#dfn-timing-allow-check). For text, the [text rendering timestamp](#text-time).
57 | * `loadTime`: for images, the latest between the time when the image resource is loaded and the time when the image resource is associated to the element. For text, 0.
58 | * `intersectionRect`: for images, the display rectangle of the image within the viewport. For text, the [text rect](#text-rect) of the associated text (only counting text nodes which have been painted at least once).
59 | * `identifier`: the value of the `elementtiming` attribute of the element.
60 | * `naturalWidth`: the [intrinsic](https://drafts.csswg.org/css2/conform.html#intrinsic) width of the image. It matches with the corresponding DOM [attribute](https://html.spec.whatwg.org/multipage/embedded-content.html#dom-img-naturalwidth) for img. 0 for text.
61 | * `naturalHeight`: the [intrinsic](https://drafts.csswg.org/css2/conform.html#intrinsic) height of the image. It matches with the corresponding DOM [attribute](https://html.spec.whatwg.org/multipage/embedded-content.html#dom-img-naturalheight) for img. 0 for text.
62 | * `id`: the element's ID.
63 | * `element`: points to the element. This will be "null" if the element is [disconnected](https://dom.spec.whatwg.org/#connected).
64 | * `url`: for images, the initial URL for the resource request. For text, this will be an empty string.
65 |
66 | Note: for background images, the element is the one being affected by the background image style.
67 |
68 | Sample code:
69 |
70 | ```javascript
71 |
72 |
73 | const observer = new PerformanceObserver((list) => {
74 | let perfEntries = list.getEntries().forEach(function(entry) {
75 | // Send the information to analytics, or in this case just log it to console.
76 | if (entry.identifier === 'foobar') {
77 | if (entry.renderTime)
78 | console.log("My image took " + entry.renderTime + " to render!");
79 | else
80 | console.log("Rendering time not available. Image took " + entry.loadTime + " to load!");
81 | }
82 | });
83 | });
84 | observer.observe({entryTypes: ['element']});
85 | ```
86 |
87 | ### Questions
88 |
89 | #### What about Shadow DOM?
90 |
91 | Unfortunately Shadow DOM elements are not currently supported.
92 |
93 | #### What about invisible or occluded elements?
94 |
95 | The entry creation might be affected by _visibility_: for instance, elements are not exposed if the style visibility is set to none, or the opacity is 0.
96 | However, occlusion will not affect entry creation: an entry is seen if the element is there, but hidden by a full-screen pop-up on the page.
97 |
98 | #### What are some differences between text and image observation?
99 |
100 | * Whereas for images it is OK to care only about those which are associated to a resource, for text that is definitely not the case.
101 | An image not associated to a resource will need to be constructed manually and it is uncommon for such an image to be part of the key content of a website.
102 | Note that we consider inline images (those with data URIs) to have an associated resource, it is just inlined.
103 | On the other hand, text is relevant regardless of whether it is associated to a webfont or not.
104 |
105 | * Image rendering steps are different from text rendering steps.
106 | For images, initial paints may not include all of the image content and may be low quality.
107 | It is only once we have fully loaded and decoded the image that we can be certain that the content being displayed is meaningful.
108 | In contrast, any text painted is meaningful.
109 | It should be noted that this is not perfect because there could be block level elements containing some text that renders quickly and some text that is blocked on webfonts.
110 | In this case, for the purposes of Element Timing, the text blocked by webfonts will be completely ignored.
111 |
112 | * Text is actually simpler when it comes to security and privacy considerations.
113 | Cross-origin images could reveal a lot of information about users, but text and webfonts embedded in a website do not.
114 | Therefore, whereas for images we had to consider resources that passed the timing allow check versus resources that did not, for text this distinction is not needed.
115 |
116 |
--------------------------------------------------------------------------------
/index.bs:
--------------------------------------------------------------------------------
1 |
2 | Title: Element Timing API
3 | Status: ED
4 | Shortname: element-timing
5 | Group: webperf
6 | Level: 1
7 | Editor: Ian Clelland, Google https://google.com, iclelland@chromium.org, w3cid 76841
8 | Tim Dresser, Google https://google.com, tdresser@chromium.org
9 | Former Editor: Nicolás Peña Moreno, Google https://google.com, npm@chromium.org
10 | URL: https://w3c.github.io/element-timing/
11 | Repository: https://github.com/w3c/element-timing
12 | Test Suite: https://github.com/web-platform-tests/wpt/tree/master/element-timing
13 | Abstract: This document defines an API that enables monitoring when large or developer-specified image elements and text nodes are displayed on screen.
14 | Default Highlight: js
15 |
16 |
17 |
18 | urlPrefix: https://w3c.github.io/performance-timeline/; spec: PERFORMANCE-TIMELINE-2;
19 | type: interface; url: #the-performanceentry-interface; text: PerformanceEntry;
20 | type: attribute; for: PerformanceEntry;
21 | text: name; url: #dom-performanceentry-name;
22 | text: entryType; url: #dom-performanceentry-entrytype;
23 | text: startTime; url: #dom-performanceentry-starttime;
24 | text: duration; url: #dom-performanceentry-duration;
25 | type: dfn; url: #dfn-queue-a-performanceentry; text: queue the PerformanceEntry;
26 | type: interface; url: #the-performanceobserver-interface; text: PerformanceObserver;
27 | type: attribute; for: PerformanceObserver;
28 | text: supportedEntryTypes; url: #supportedentrytypes-attribute;
29 | urlPrefix: https://w3c.github.io/IntersectionObserver; spec: INTERSECTION-OBSERVER;
30 | type: dfn; url: #calculate-intersection-rect-algo; text: intersection rect algorithm;
31 | urlPrefix: https://html.spec.whatwg.org/multipage; spec: HTML;
32 | type: dfn; url: //webappapis.html#event-loop-processing-model; text: event loop processing model;
33 | urlPrefix: https://drafts.csswg.org/css-backgrounds-3; spec: CSS-BACKGROUNDS-3;
34 | type: dfn; url: #propdef-background-image; text: background-image;
35 | urlPrefix: https://wicg.github.io/largest-contentful-paint/; spec: LARGEST-CONTENTFUL-PAINT;
36 | type: dfn; url:#potentially-add-a-largestcontentfulpaint-entry; text: potentially add a LargestContentfulPaint entry;
37 | urlPrefix: https://w3c.github.io/paint-timing/; spec: PAINT-TIMING;
38 | type: dfn; url:#set-of-owned-text-nodes; text: set of owned text nodes;
39 | type: dfn; url:#process-image-that-finished-loading; text: process image that finished loading;
40 | type: dfn; url:#timing-eligible; text: timing-eligible
41 | type: dfn; url:#the-paint-timing-steps; text: the paint timing steps
42 | type: dfn; url:#exposed-for-paint-timing; text: exposed for paint timing
43 | type: dfn; url:#pending-image-record; text: pending image record
44 | type: dfn; for:pending image record; url:#pending-image-record-element; text: element
45 | type: dfn; for:pending image record; url:#pending-image-record-loadtime; text: loadTime
46 | type: dfn; for:pending image record; url:#pending-image-record-request; text: request
47 | type: dfn; url:#paint-timing-info; text: paint timing info
48 | type: dfn; for: paint timing info; text: default paint timestamp;
49 | type: dfn; for:PaintTimingMixin; url:#painttimingmixin-paint-timing-info; text: paint timing info
50 | type: interface; text:PaintTimingMixin; url:#painttimingmixin
51 |
52 |
53 |
54 | spec:dom; type:dfn; text:descendant
55 |
56 |
57 | Introduction {#sec-intro}
58 | =====================
59 |
60 | This section is non-normative.
61 |
62 | Knowing when critical elements are displayed on screen is key to understanding page load performance.
63 | While fast rendering of the essential components is not sufficient for a satisfactory loading experience, it is necessary.
64 | Therefore, monitoring these rendering timestamps is important to improve and prevent regressions in page loads.
65 |
66 | This specification gives developers and analytics providers an API to measure rendering timestamps of critical elements.
67 | There is currently no good way to measure these timestamps for real users.
68 | Existing approaches would require either registering observers too early or significant DOM manipulation.
69 | These approaches are discussed on the [[#sec-security]] section.
70 |
71 | 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.
72 | Thus, this API exposes rendering timing information about web-developer-annotated elements.
73 |
74 | Elements exposed {#sec-elements-exposed}
75 | ------------------------
76 |
77 | The Element Timing API supports timing information about timing-eligible
78 | elements, as defined by [[PAINT-TIMING]].
79 |
80 | Elements that have a "elementtiming
" content attribute are reported in the report image element timing and the report text element timing algorithms.
81 |
82 | Usage example {#sec-example}
83 | ------------------------
84 |
85 | The following example shows an image that is registered for observation via its elementtiming
attribute, and an observer gathering the timing information.
86 |
87 |
88 |
89 | This is text I care about.
90 | ...
91 |
98 |
99 |
100 | The following are sample elements whose rendering timestamps could be measured by using this API and which should be compared to page navigation:
101 | * The images in the image carousel of a shopping site.
102 | * The main photo in a story of a news site.
103 | * The title of a blog post.
104 | * The first paragraph in an entry of an encyclopedia site.
105 |
106 | The API could have use cases outside of page load by comparing the rendering timestamps with input timestamps.
107 | For example, developers could monitor the time it takes for a widget to show up after a click that triggers it.
108 |
109 | Element Timing {#sec-element-timing}
110 | =======================================
111 |
112 | Element Timing involves the following new interfaces:
113 |
114 | {{PerformanceElementTiming}} interface {#sec-performance-element-timing}
115 | ------------------------------------------------------------------------
116 |
117 |
118 | [Exposed=Window]
119 | interface PerformanceElementTiming : PerformanceEntry {
120 | readonly attribute DOMHighResTimeStamp renderTime;
121 | readonly attribute DOMHighResTimeStamp loadTime;
122 | readonly attribute DOMRectReadOnly intersectionRect;
123 | readonly attribute DOMString identifier;
124 | readonly attribute unsigned long naturalWidth;
125 | readonly attribute unsigned long naturalHeight;
126 | readonly attribute DOMString id;
127 | readonly attribute Element? element;
128 | readonly attribute USVString url;
129 | [Default] object toJSON();
130 | };
131 |
132 | PerformanceElementTiming includes PaintTimingMixin;
133 |
134 |
135 | A {{PerformanceElementTiming}} object reports timing information about one associated element.
136 |
137 | Each {{PerformanceElementTiming}} object has these associated concepts, all of which are initially set to null
:
138 | * A request containing the image request (if the entry is for image content).
139 | * An element containing the associated {{Element}}.
140 |
141 | The associated concepts and some attributes for {{PerformanceElementTiming}} are specified in the processing model in [[#sec-report-image-element]] and [[#sec-report-text]].
142 |
143 | The {{PerformanceEntry/entryType}} attribute's getter must return the {{DOMString}} "element"
.
144 |
145 | The {{PerformanceEntry/name}} attribute's getter must return the value it was initialized to.
146 |
147 | The {{PerformanceEntry/startTime}} attribute's getter must return the value of this 's {{renderTime}} if it is not 0, and the value of this 's {{loadTime}} otherwise.
148 |
149 | The {{PerformanceEntry/duration}} attribute's getter must return 0.
150 |
151 | The {{PerformanceElementTiming/renderTime}} attribute getter step is to return the [=default paint timestamp=] given [=this=]'s [=PaintTimingMixin/paint timing info=].
152 |
153 | The {{PerformanceElementTiming/loadTime}} attribute's getter must return the the value it was initialized to.
154 |
155 | The {{PerformanceElementTiming/intersectionRect}} attribute must return the value it was initialized to.
156 |
157 | The {{PerformanceElementTiming/identifier}} attribute's getter must return the value it was initialized to.
158 |
159 | The {{PerformanceElementTiming/naturalWidth}} attribute must return the value it was initialized to.
160 |
161 | The {{PerformanceElementTiming/naturalHeight}} attribute must return the value it was initialized to.
162 |
163 | The {{PerformanceElementTiming/id}} attribute's getter must return the value it was initialized to.
164 |
165 | The {{PerformanceElementTiming/element}} attribute's getter must perform the following steps:
166 |
167 | 1. If
this 's
element is not [=exposed for paint timing=] given null, return null.
168 | 1. Return
this 's
element .
169 |
170 |
171 | Note: This means that an element that is no longer descendant of the {{Document}} will no longer be returned by {{PerformanceElementTiming/element}}'s attribute getter.
172 |
173 | The {{PerformanceElementTiming/url}} attribute's getter must perform the following steps:
174 |
175 | 1. If
this 's
request is null, return the empty string.
176 | 1. Let |urlString| be
this 's
request 's
current URL .
177 | 1. Let |url| be the result of
parsing |urlString|.
178 | 1. If |url|'s
scheme is "`data`", trim |urlString| to its first 100 characters.
179 | 1. Return |urlString|.
180 |
181 |
182 | Note: The URL is trimmed for data URLs to avoid excessive memory in the entry.
183 |
184 | Processing model {#sec-processing-model}
185 | ========================================
186 |
187 | Note: A user agent implementing the Element Timing API would need to include "element"
in {{PerformanceObserver/supportedEntryTypes}} for {{Window}} contexts.
188 | This allows developers to detect support for element timing.
189 |
190 | Modifications to the DOM specification {#sec-modifications-DOM}
191 | --------------------------------------------------------
192 |
193 | This section will be removed once the [[DOM]] specification has been modified.
194 |
195 | We extend the {{Element}} interface as follows:
196 |
197 |
198 | partial interface Element {
199 | [CEReactions] attribute DOMString elementTiming;
200 | };
201 |
202 |
203 | The {{Element/elementTiming}} attribute must reflect the element's "elementtiming
" content attribute.
204 |
205 | Report Element Timing {#sec-report-element-timing}
206 | --------------------------------------------------
207 |
208 |
209 | When asked to
report element timing given a {{Document}} |doc|, a [/=paint timing info=] |paintTimingInfo|, an [=ordered set=] of [=pending image records=] |paintedImages|, and an [=ordered set=] of [=/elements=] |paintedTextNodes|, perform the following steps:
210 |
211 | 1. For each |record| in |paintedImages|:
212 | 1. Run the
report image element timing algorithm passing in |record|, |paintTimingInfo|, and |doc|.
213 | 1. For each {{Element}} |element| in |paintedTextNodes|:
214 | 1. Run the
report text element timing given |element|, |paintTimingInfo|, and |doc|.
215 |
216 |
217 | Report Image Element Timing {#sec-report-image-element}
218 | --------------------------------------------------------
219 |
220 |
221 | When asked to
report image element timing given a [=pending image record=] |record|, a [=/paint timing info=] |paintTimingInfo| and a {{Document}} |document|, perform the following steps:
222 |
223 | 1. If |record|'s [=pending image record/element=]'s "
elementtiming
" content attribute is absent, then abort these steps.
224 | 1. Let |intersectionRect| be the value returned by the
intersection rect algorithm using |record|'s [=pending image record/element=] as the target and viewport as the root.
225 | 1. Create and initialize a {{PerformanceElementTiming}} object |entry| with |document|'s [=relevant realm=], whose [=PaintTimingMixin/paint timing info=] is |paintTimingInfo|.
226 | 1. Initialize |entry|'s
request to |record|'s [=pending image record/request=].
227 | 1. Initialize |entry|'s
element to |record|'s [=pending image record/element=].
228 | 1. Initialize |entry|'s {{PerformanceEntry/name}} to the {{DOMString}} "image-paint".
229 | 1. Initialize |entry|'s {{loadTime}} to |record|'s [=pending image record/loadTime=].
230 | 1. Initialize |entry|'s {{intersectionRect}} to |intersectionRect|.
231 | 1. Initialize |entry|'s {{identifier}} to |record|'s [=pending image record/element=]'s "
elementtiming
" content attribute.
232 | 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.
233 | 1. Initialize |entry|'s {{id}} to |record|'s [=pending image record/element=]'s "
id
" content attribute.
234 | 1.
Queue the PerformanceEntry |entry|.
235 |
236 |
237 | Report Text Element Timing {#sec-report-text}
238 | --------------------------------------------------------
239 |
240 |
241 | When asked to
report text element timing given an {{Element}} |element|, a [=/paint timing info=] |paintTimingInfo| and a {{Document}} |document|, perform the following steps:
242 |
243 | 1. If |element|'s "
elementtiming
" content attribute is absent, then abort these steps.
244 | 1. Let |intersectionRect| be an empty rectangle.
245 | 1. For each {{Text}}
node |text| in |element|'s
set of owned text nodes :
246 | 1. Augment |intersectionRect| to be smallest rectangle containing the border box of |text| and |intersectionRect|.
247 | 1. Intersect |intersectionRect| with the visual viewport.
248 | 1. Create and initialize a {{PerformanceElementTiming}} object |entry| with |document|'s [=relevant realm=], whose [=PaintTimingMixin/paint timing info=] is |paintTimingInfo|.
249 | 1. Initialize |entry|'s
element to |element|.
250 | 1. Initialize |entry|'s {{PerformanceEntry/name}} to the {{DOMString}} "text-paint".
251 | 1. Initialize |entry|'s {{loadTime}} to 0.
252 | 1. Initialize |entry|'s {{intersectionRect}} to |intersectionRect|.
253 | 1. Initialize |entry|'s {{identifier}} to |element|'s "
elementtiming
" content attribute.
254 | 1. Initialize |entry|'s {{PerformanceElementTiming/naturalWidth}} and {{PerformanceElementTiming/naturalHeight}} to 0.
255 | 1. Initialize |entry|'s {{id}} to |element|'s "
id
" content attribute.
256 | 1.
Queue the PerformanceEntry |entry|.
257 |
258 |
259 | Security & privacy considerations {#sec-security}
260 | ===============================================
261 |
262 | This API exposes some information about cross-origin images.
263 | In particular, images have their resource load time exposed, which could be a source of privacy concerns.
264 |
265 | However, this is considered to not add new attacks to the web platform because the ResourceTiming API exposes a similar timestamp already.
266 | In addition, the onload handler exposes load timing when it is available, and the resource load time is a close proxy to this.
267 | The current high resolution time computed at the beginning of the onload handler would provide the image load time.
268 | We choose to expose the {{loadTime}} because it is very easy to obtain even without an onload handler.
269 | 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.
270 |
271 | The {{renderTime}} (display timestamp) is indeed newly exposed information. Implementations are advised to coarsen that timestamp further,
272 | to a 4 milliseconds resolution at least, to avoid exposing differences in decoding time between cross-origin images. Note that other checks,
273 | such as `Timing-Allow-Origin`, does not work here due to same-origin and cross-origin images being rendered at the same time.
274 | Exposing a coarse {{renderTime}} is anyway not a substantial attack vector, given that image [=natural size=] and loading time are exposed in other ways.
275 |
276 |
277 | // In the attacker frame.
278 |
279 |
285 |
286 | // In the attack.html iframe.
287 |
288 |
295 |
296 |
297 | The other nontrivial parameter being exposed here is the {{intersectionRect}}.
298 | This can already be polyfilled, for example using {{IntersectionObserver}}.
299 | The polyfill process would be similar: add an {{IntersectionObserver}} on the onload handler of the target image or text content.
300 | 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.
301 | 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.
302 |
303 | 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.
304 | Suppose we waited and exposed all the entries during the report element timing algorithm.
305 | An attacker could infer nontrivial information about the rendering timestamp of an image.
306 | It would do so by only observing the timing for that image.
307 | Even though the timestamp is not exposed as a member of the {{PerformanceElementTiming}} entry received,
308 | the fact that we wait until the next update the rendering 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.
309 | This would unintentionally leak some of the display timing of the image.
310 |
--------------------------------------------------------------------------------
/w3c.json:
--------------------------------------------------------------------------------
1 | {
2 | "group": 45211,
3 | "contacts": [
4 | "caribouW3"
5 | ],
6 | "shortName": "element-timing",
7 | "repo-type": "rec-track"
8 | }
9 |
--------------------------------------------------------------------------------