1. Introduction
The
Container
Timing
API
enables
monitoring
when
annotated
sections
of
the
DOM
are
displayed
on
screen
and
have
finished
their
initial
paint.
A
developer
can
mark
subsections
of
the
DOM
with
the
containertiming
attribute
(similar
to
elementtiming
for
the
Element
Timing
API)
and
receive
performance
entries
when
that
section
has
been
painted
for
the
first
time.
This API allows developers to measure the timing of various components in their pages. As developers increasingly organize their applications into components, there’s a growing demand to measure performance on subsections of an application or a web page.
Unlike
Element
Timing,
it
is
not
possible
for
the
renderer
to
know
when
a
section
of
the
DOM
has
finished
painting
(there
could
be
future
changes,
asynchronous
requests
for
new
images,
slow
loading
buttons,
etc.),
so
this
API
emits
candidates
in
the
form
of
PerformanceEntry
objects
when
there
has
been
an
update.
2. Motivation
Developers want to measure when subsections of the DOM have been painted, such as tables, widgets, or other components, so they can track paint times and submit them to analytics. Current Web APIs don’t adequately support this:
-
Element Timing is limited in what it can support and cannot be used for whole sections.
-
Largest Contentful Paint (LCP) isn’t useful enough to time when specific parts of the page have loaded.
-
User-space polyfills have significant drawbacks, such as:
-
Needing to mark elements before painting (requiring server-side changes or blocking rendering)
-
Requiring a MutationObserver to catch newly injected elements
-
Needing to run in the document head, increasing time to first paint
-
Can be less efficient at rectangle tracking compared to browser built-in 2D engines
-
Web authors know their domain better than anyone else and want to communicate the performance of their own content blocks in ways their users or organization would understand (e.g., "time to first tweet" ).
2.1. Life Cycle
In this example life cycle a component will paint multiple pieces of content at different times, each of these times will generate a new
PerformanceContainerTiming
entry
with
updated
information.
However,
once
a
region
has
been
painted,
subsequent
paints
of
that
same
region
will
not
generate
new
entries.
3. Usage Example
The following example demonstrates how to register a container root and observe its paint timings.
containertiming
attribute:
< div containertiming = "foobar" > < main > ...</ main > < aside > ...</ aside > </ div > < script > const observer= new PerformanceObserver(( list) => { let perfEntries= list. getEntries(); for ( const entryof perfEntries) { console. log( 'Container painted:' , entry. identifier, 'at' , entry. startTime, 'size:' , entry. size); } }); observer. observe({ entryTypes: [ "container" ] }); </ script >
The attribute should be set before the element is added to the document (in HTML, or if set in JavaScript, before adding it to the document). Setting the attribute retroactively will only capture subsequent events and future paints.
3.1. Ignoring Subtrees
containertimingignore
>
attribute:
< div containertiming = "foobar" > < main > ...</ main > <!-- Aside updates won't trigger container timing events --> < aside containertimingignore > ...</ aside > </ div >
4. Terminology
A
container
root
is
an
Element
that
has
the
containertiming
attribute.
An
ignored
subtree
is
a
subtree
rooted
at
an
Element
with
the
containertimingignore
>
attribute.
A
painted
region
is
a
region
(collection
of
rectangles)
representing
all
the
painted
portions
of
a
container
root
accumulated
since
it
was
first
observed.
The
painted
region
is
maintained
in
the
viewport
coordinate
space.
If
the
container
root
element
moves
(e.g.
due
to
layout
changes
or
moveBefore()
),
previously
accumulated
rectangles
are
not
adjusted
—
the
region
continues
to
expand
in
viewport
coordinates.
The container timing API provides timing information about when container roots are painted to the screen.
5.
The
PerformanceContainerTiming
Interface
[Exposed =Window ]interface :PerformanceContainerTiming PerformanceEntry {readonly attribute DOMString identifier ;readonly attribute DOMRectReadOnly intersectionRect ;readonly attribute unsigned long long size ;readonly attribute DOMHighResTimeStamp firstRenderTime ;readonly attribute Element ?lastPaintedElement ;readonly attribute Element ?rootElement ; };PerformanceContainerTiming includes PaintTimingMixin ;
PerformanceContainerTiming
object
has
these
associated
concepts:
-
An identifier initially set to an empty string.
-
An intersectionRect initially set to a
DOMRectReadOnlywith all values set to 0. -
A size initially set to 0.
-
A firstRenderTime initially set to 0.
-
A renderTime initially set to 0.
-
A lastPaintedElement containing the associated
Element, initially set tonull. -
A rootElement containing the associated
Elementthat is the container root, initially set tonull.
The
entryType
attribute’s
getter
must
return
the
DOMString
"container"
.
The
name
attribute’s
getter
must
return
the
empty
string.
The
duration
attribute
must
return
0.
The
startTime
attribute’s
getter
must
return
the
value
of
this
’s
renderTime
.
The
identifier
attribute
must
return
the
value
of
this
’s
identifier
.
The
intersectionRect
attribute
must
return
the
value
of
this
’s
intersectionRect
.
The
size
attribute
must
return
the
value
of
this
’s
size
.
The
firstRenderTime
attribute
must
return
the
value
of
this
’s
firstRenderTime
.
The
lastPaintedElement
attribute
must
return
the
value
of
this
’s
lastPaintedElement
.
The
rootElement
attribute
must
return
the
value
of
this
’s
rootElement
.
Note:
The
user
agent
needs
to
maintain
the
container
root
records
map
so
that
removed
content
does
not
introduce
memory
leaks.
In
particular,
it
can
tie
the
lifetime
of
the
entries
to
weak
pointers
to
the
Element
s
so
that
they
can
be
cleaned
up
sometime
after
the
Element
s
are
removed.
Since
the
map
is
not
exposed
to
web
developers,
this
does
not
expose
garbage
collection
timing.
6. Processing Model
Note:
A
user
agent
implementing
the
Container
Timing
API
would
need
to
include
"container"
in
supportedEntryTypes
for
Window
contexts.
This
allows
developers
to
detect
support
for
container
timing.
6.1. Per-Document State
For
each
Document
,
the
user
agent
must
maintain
a
container
root
records
map
that
maps
container
root
Element
s
to
Container
Timing
Record
objects.
6.2.
Extensions
to
the
Element
Interface
This section will be removed once the [DOM] specification had been modified.
We
extend
the
Element
interface
as
follows:
partial interface Element { [CEReactions ]attribute DOMString containertiming ; [CEReactions ]attribute DOMString ?; };containertimingIgnore
containertiming
attribute
is
a
DOMString
that
identifies
the
element
as
a
container
root
.
The
value
becomes
the
identifier
in
the
corresponding
PerformanceContainerTiming
entry.
The
attribute,
when
present,
marks
the
element
and
its
descendants
as
an
ignored
subtree
that
should
not
contribute
to
container
timing
measurements
for
ancestor
container
roots
.
containertimingignore
containertimingIgnore
6.3. Container Timing Record
This specification defines an internal data structure used by the processing model:
-
A paintTimingInfo which is a paint timing info .
-
An identifier which is a
DOMString. -
A paintedRegion which is a painted region , initially empty.
-
A lastNewPaintedAreaPaintTimingInfo which is a paint timing info , initially unset.
-
A lastNewPaintedAreaElement which is an
Elementor null, initially null. -
A lastNewPaintedAreaSize which is a number, initially 0.
A hasPendingChanges which is a boolean, initially false.
DOMString
identifier
,
perform
the
following
steps:
-
Let record be a new Container Timing Record .
-
Set record ’s paintTimingInfo to paintTimingInfo .
-
Set record ’s identifier to identifier .
-
Return record .
6.4. Registering Container Roots
When
an
Element
with
a
[^containertiming^]
content
attribute
is
connected
to
the
document:
-
The user agent must register the element as a container root .
-
The user agent must initialize an empty painted region for the container root .
-
The user agent must track all paint operations within the container root ’s subtree, excluding any ignored subtrees .
6.5. Removing the containertiming attribute
Element
element
,
perform
the
following
steps:
-
Let document be element ’s node document .
-
If document ’s container root records map contains an entry for element , remove that entry.
Note: If the [^containertiming^] attribute is later re-added to the same element, a new Container Timing Record will be created on the next paint. The painted region starts fresh — prior paint data is not preserved.
6.6. Disconnecting a container root
Element
element
is
disconnected
from
the
document,
perform
the
following
steps:
-
Let document be element ’s node document .
-
If document ’s container root records map contains an entry for element , remove that entry.
Note: If the element is reconnected to the document while still having the [^containertiming^] attribute, it is treated as a new container root registration. A new Container Timing Record will be created on the next paint with a fresh painted region .
6.7. Handle Element Painted for Container Timing
Document
document
,
a
paint
timing
info
paintTimingInfo
,
an
Element
element
,
and
a
DOMRectReadOnly
intersectionRect
,
perform
the
following
steps:
-
If element does not Contribute to container timing , return.
-
Let containerRoot be the result of get the container root element given element .
-
If containerRoot is null, return.
-
Let record be the entry in document ’s container root records map for containerRoot . If no such entry exists, set record to the result of create a Container Timing Record given paintTimingInfo and the value of containerRoot ’s [^containertiming^] content attribute; then add ( containerRoot → record ) to document ’s container root records map .
-
Let enclosingRect be the smallest enclosing rectangle of intersectionRect .
-
Maybe update the last new painted area for record given document , containerRoot , element , enclosingRect , and paintTimingInfo .
-
Mark the
Documentas having pending container timing changes.
Note: This algorithm is invoked for each image or text node that paints. The intersection rectangle should be computed using the intersection rect algorithm with the element as target and viewport as root, intersected with the visual viewport. For text nodes, the intersection rectangle is the smallest rectangle containing the border boxes of all text nodes in the set of owned text nodes , intersected with the visual viewport.
6.8. Emit Container Timing Entries
Document
document
,
perform
the
following
steps.
This
should
be
called
once
per
frame
after
all
paint
operations
have
been
processed:
-
If document does not have pending container timing changes, return.
-
For each containerRoot → record of document ’s container root records map :
-
If record ’s hasPendingChanges is false, continue.
-
Create a Container Timing entry given record and containerRoot .
-
Set record ’s hasPendingChanges to false.
-
Set record ’s lastNewPaintedAreaElement to null.
Set record ’s lastNewPaintedAreaSize to 0.
-
-
Mark the
Documentas no longer having pending container timing changes.
Note:
Unlike
some
other
paint
timing
APIs
that
may
emit
multiple
entries
per
painted
element,
Container
Timing
accumulates
painted
regions
for
each
container
root
and
emits
at
most
one
PerformanceContainerTiming
entry
per
container
root
per
frame.
This
batching
approach
is
more
efficient
and
provides
a
holistic
view
of
the
container’s
paint
state.
6.9. Getting the parent container root Element
Element
element
,
perform
the
following
steps:
-
Let parent be element ’s parentElement.
-
If parent is null, return null.
-
Return the result of get the container root element given parent .
6.10. Contributes to Container Timing
An
Element
contributes
to
the
container
timing
of
a
container
root
if
all
of
the
following
are
true:
-
It is a descendant of the container root .
-
It is not within an ignored subtree .
-
It is not in a shadow tree.
Element
element
contributes
to
the
container
timing
of
a
container
root
containerRoot
:
-
If element is null, return false.
-
If element is in a shadow tree, return false.
-
If element is not a descendant of containerRoot , return false.
-
If element is within an ignored subtree , return false.
-
Return true.
6.11. Getting the container root Element
Element
element
,
perform
the
following
steps:
-
If element is null, return null.
-
If element ’s [^containertiming^] content attribute is present, return element .
-
If element ’s parentElement is not null, return the result of get the container root element given element ’s parentElement.
-
Return null.
6.12. Maybe Update Last New Painted Area
Document
document
,
a
container
root
Element
containerRoot
,
an
Element
element
,
a
DOMRectReadOnly
enclosingRect
,
and
a
paint
timing
info
paintTimingInfo
,
perform
the
following
steps:
-
Let paintedRegion be record ’s paintedRegion .
-
If paintedRegion fully contains enclosingRect , return.
-
Let newPaintedArea be the area of enclosingRect that is not already contained in paintedRegion .
Set record ’s paintedRegion to the union of paintedRegion and enclosingRect .
-
Set record ’s lastNewPaintedAreaPaintTimingInfo to paintTimingInfo .
-
If newPaintedArea is greater than record ’s lastNewPaintedAreaSize :
Set record ’s lastNewPaintedAreaElement to element .
-
Set record ’s lastNewPaintedAreaSize to newPaintedArea .
Set record ’s hasPendingChanges to true.
-
If containerRoot ’s [^containertimingignore^] content attribute is present, return.
-
Let parentContainerRoot be the result of get the parent container root element given containerRoot .
-
If parentContainerRoot is null, return.
-
Let parentRecord be the entry in document ’s container root records map for parentContainerRoot . If no such entry exists, set parentRecord to the result of create a Container Timing Record given paintTimingInfo and the value of parentContainerRoot ’s [^containertiming^] content attribute; then add ( parentContainerRoot → parentRecord ) to document ’s container root records map .
-
Maybe update the last new painted area for parentRecord given document , parentContainerRoot , element , enclosingRect , and paintTimingInfo .
Note:
The
lastPaintedElement
is
the
element
that
contributed
the
largest
newly
painted
area
within
a
single
rendering
frame.
This
avoids
dependence
on
implementation-specific
paint
ordering
and
follows
a
similar
approach
to
the
Largest
Contentful
Paint
algorithm.
The
lastNewPaintedAreaSize
is
reset
after
entries
are
emitted
so
that
each
frame’s
comparison
starts
fresh.
Note:
This
algorithm
reports
any
change
to
the
painted
region,
regardless
of
size.
Even
a
1
pixel
change
in
the
painted
area
will
result
in
a
new
PerformanceContainerTiming
entry
being
queued.
Developers
who
wish
to
filter
out
small
changes
may
do
so
by
comparing
the
size
values
between
entries.
6.13. Create a Container Timing Entry
Element
containerRoot
,
the
user
agent
must
perform
the
following
steps:
-
Let entry be a new
PerformanceContainerTimingentry with its:-
entryTypeset to "container" -
nameset to the empty string -
startTimeset to record ’s lastNewPaintedAreaPaintTimingInfo ’spaintTime -
durationset to 0 -
identifierset to record ’s identifier -
firstRenderTimeset to record ’s paintTimingInfo ’spaintTime -
intersectionRectset to the bounding rectangle of record ’s paintedRegion -
sizeset to the total area of record ’s paintedRegion -
lastPaintedElementset to record ’s lastNewPaintedAreaElement -
rootElementset to containerRoot
-
-
Queue the PerformanceEntry entry .
7. Security and Privacy Considerations
7.1. Cross-Origin Restrictions
The API respects cross-origin boundaries:
-
Elements belonging to cross-origin iframes are not exposed to parent frames.
-
No timing information crosses frame boundaries unless explicitly passed by the developer via
postMessage.
7.2. Information Exposure
Most information provided by this API can already be estimated through existing APIs:
-
Element Timing returns first rendering time for images and text.
-
Paint Timing API provides related timestamps.
-
The combination of these APIs could approximate container timing information, though less efficiently.
The API does not expose:
-
Internal implementation details of the rendering engine
-
Information about elements the developer doesn’t already have access to
-
Timing information more granular than already available through existing Performance APIs
7.3. Timing Attacks
The
API
uses
DOMHighResTimeStamp
which
may
be
subject
to
resolution
limitations
for
security
purposes,
consistent
with
other
Performance
APIs.
7.4. Privacy Considerations
The API does not:
-
Enable tracking users across sites
-
Expose browsing history
-
Provide information about user behavior beyond what the site already has access to through script execution
8. Acknowledgments
Many thanks for valuable feedback and advice from:
-
Barry Pollard
-
Michael Mocny
-
Scott Haseley
-
Sergey Chernyshev