1. Introduction
This section is non-normative.
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 § 4 Security & privacy considerations 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.
1.1. Elements exposed
The Element Timing API supports timing information about the following elements:
-
img
elements. -
The poster images of
video
elements. -
Elements with a background-image.
-
Groups of text nodes, which are aggregated as described in § 3.2 Modifications to the CSS specification.
Elements that have a "elementtiming
" content attribute are reported in the report image element timing and the report text element timing algorithms.
1.2. Usage example
The following example shows an image that is registered for observation via its elementtiming
attribute, and an observer gathering the timing information.
< 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 >
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.
2. Element Timing
Element Timing involves the following new interfaces:
2.1. PerformanceElementTiming
interface
[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
A PerformanceElementTiming
object reports timing information about one associated element.
Each PerformanceElementTiming
object has these associated concepts, all of which are initially set to
:
-
A request containing the image request (if the entry is for image content).
-
An element containing the associated
Element
.
The associated concepts and some attributes for PerformanceElementTiming
are specified in the processing model in § 3.5 Report image Element Timing and § 3.6 Report text Element Timing.
The entryType
attribute’s getter must return the DOMString
.
The name
attribute’s getter must return the value it was initialized to.
The 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.
The duration
attribute’s getter must return 0.
The renderTime
attribute must return the value it was initialized to.
The loadTime
attribute’s getter must return the the value it was initialized to.
The intersectionRect
attribute must return the value it was initialized to.
The identifier
attribute’s getter must return the value it was initialized to.
The naturalWidth
attribute must return the value it was initialized to.
The naturalHeight
attribute must return the value it was initialized to.
The id
attribute’s getter must return the value it was initialized to.
The element
attribute’s getter must run the get an element algorithm this’s element and null as inputs.
Note: This means that an element that is no longer descendant of the Document
will no longer be returned by element
's attribute getter.
The url
attribute’s getter must perform the following steps:
Note: The URL is trimmed for data URLs to avoid excessive memory in the entry.
3. Processing model
Note: A user agent implementing the Element Timing API would need to include
in supportedEntryTypes
for Window
contexts.
This allows developers to detect support for element timing.
3.1. Modifications to the DOM specification
This section will be removed once the [DOM] specification has been modified.
We extend the Element
interface as follows:
partial interface Element { [CEReactions ]attribute DOMString ; };
elementTiming
The elementTiming
attribute must reflect the element’s "elementtiming
" content attribute.
Each Element
has an associated image request which is initially null.
When the processing model for an Element
element of type HTMLImageElement
, SVGImageElement
, or HTMLVideoElement
creates a new image resource (e.g., to be displayed as an image or poster image), element’s associated image request is set to the image request of the created image resource.
Note: Every image resource that is obtained from a URL whose scheme is equal to "data" has an associated image request which is not fetched but still needs to be loaded.
This request can be the associated image request of an Element
.
Note: The current language is vague since it does not point to specific algorithms. This can be made more rigorous when the corresponding processing models have a more unified processing model.
Each Element
has a set of owned text nodes, which is initially an empty ordered set.
Each Document
has images pending rendering, a list of triples (Element
, image request, DOMHighResTimeStamp) which are considered loaded but not yet rendered.
When an Element
element’s associated image request has become completely available, run the algorithm to process an image that finished loading passing in element and its associated image request as inputs.
Each Document
also has a set of elements with rendered text, which is initially an empty, ordered set.
3.2. Modifications to the CSS specification
When the user agent is executing the painting order, it must populate the set of owned text nodes of the painted Elements
so that the following is true:
-
If a
Text
object text will not be painted due to the font face being in its font block period, then it is not appended to the set of owned text nodes of anyElement
. -
Otherwise, text is appended to the set of owned text nodes of the
Element
which determines the containing block of text.
NOTE: A user agent might want to use a stack to efficiently compute the set of owned text nodes while implementing the painting order.
Every Element
has a list of associated background image requests which is initially an empty array.
When the processing model for the Element
element’s style requires a new image resource (to be displayed as background image), the image request created by the new resource is appended to element’s associated background image requests.
Whenever an image request in an Element
element’s associated background image requests becomes completely available, run the algorithm to process an image that finished loading with element and image request as inputs.
NOTE: we assume that there is one image request for each Element
that a background-image property affects and for each URL that the background-image property specifies.
So, for example, if there is a style with two URLs affecting all div
s, and there are two div
s, then there will be four image requests.
This means that a single background-image property could produce multiple PerformanceElementTiming
entries because it can affect multiple elements and because it can specify multiple URLs.
3.3. 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 the following substep at the end:
-
For each fully active
Document
in docs, run the element timing processing algorithm passing in theDocument
and now.
3.4. Process image that finished loading
-
Let element be the input
Element
. -
Let imageRequest be element’s associated image request.
-
Let root be element’s root.
-
If root is not a
Document
, return. -
Let now be the current high resolution time given element’s relevant global object.
-
If imageRequest is not a data URL [RFC2397] and if the timing allow check fails for imageRequest’s resource, run the report image element timing algorithm, passing in the triple (element, imageRequest, now), 0, and root as inputs.
-
Otherwise, add the triple (element, imageRequest, now) to root’s images pending rendering.
3.5. Report image Element Timing
Document
document, perform the following steps:
-
Let intersectionRect be the value returned by the intersection rect algorithm using element as the target and viewport as the root.
-
Let exposedElement be the result of running get an element with element and document as input.
-
If exposedElement is not null, call the potentially add a LargestContentfulPaint entry algorithm with intersectionRect, imageRequest, renderTime, loadTime, element, and document.
-
If element’s "
elementtiming
" content attribute is absent, then abort these steps. -
Create and initialize a
PerformanceElementTiming
object entry.-
Initialize entry’s request to imageRequest.
-
Initialize entry’s element to element.
-
Initialize entry’s
renderTime
to renderTime. -
Initialize entry’s
loadTime
to loadTime. -
Initialize entry’s
intersectionRect
to intersectionRect. -
Initialize entry’s
identifier
to element’s "elementtiming
" content attribute. -
Initialize entry’s
naturalWidth
andnaturalHeight
by running the same steps for animg
'snaturalWidth
andnaturalHeight
attribute getters, but using imageRequest as the image. -
Initialize entry’s
id
to element’s "id
" content attribute.
-
-
Queue the PerformanceEntry entry.
3.6. Report text Element Timing
Element
element, a DOMHighResTimestamp renderTime and a Document
document, perform the following steps:
-
Let intersectionRect be an empty rectangle.
-
For each
Text
node text in element’s set of owned text nodes:-
Augment intersectionRect to be smallest rectangle containing the border box of text and intersectionRect.
-
-
Intersect intersectionRect with the visual viewport.
-
Let exposedElement be the result of running get an element with element and document as input.
-
If exposedElement is not null, call the potentially add a LargestContentfulPaint entry algorithm with intersectionRect, null, renderTime, 0, exposedElement, and document.
-
If element’s "
elementtiming
" content attribute is absent, then abort these steps. -
Create and initialize a
PerformanceElementTiming
object entry with document’s relevant realm.-
Initialize entry’s element to element.
-
Initialize entry’s
renderTime
to renderTime. -
Initialize entry’s
loadTime
to 0. -
Initialize entry’s
intersectionRect
to intersectionRect. -
Initialize entry’s
identifier
to element’s "elementtiming
" content attribute. -
Initialize entry’s
naturalWidth
andnaturalHeight
to 0. -
Initialize entry’s
id
to element’s "id
" content attribute.
-
-
Queue the PerformanceEntry entry.
3.7. Element Timing processing
Document
doc and a timestamp now and performs the following steps:
-
For each imagePendingRenderingTriple in doc’s images pending rendering list:
-
Let imageRequest be the image request of imagePendingRenderingTriple.
-
If imageRequest is fully decoded, then run the following steps:
-
Run the report image element timing algorithm passing in imagePendingRenderingTriple, now, and doc.
-
Remove imagePendingRenderingTriple from doc’s images pending rendering list.
-
-
-
For each
Element
element in doc’s descendants:-
If element is contained in doc’s set of elements with rendered text, continue.
-
If element’s set of owned text nodes is empty, continue.
-
Append element to doc’s set of elements with rendered text.
-
Run the report text element timing given element, now, and doc.
-
3.8. Get an element algorithm
Element
element and Document
document as inputs, run these steps:
-
If element is not connected, return
.null -
Let settings be this’s relevant settings object.
-
if document is null, let document be settings’s relevant global object’s associated
Document
. -
If element’s root is not equal to document or if document is not fully active, return
.null -
Return element.
4. Security & privacy considerations
This API exposes some information about cross-origin images. In particular, images that do not pass the timing allow check 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 current high resolution time 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 timing allow check.
For clarity, here is a code snippet using the PaintTiming API:
// 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 >
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 timing allow check.
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 element timing processing 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 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.
This would unintentionally leak some of the display timing of the image.