Copyright © 2025 the Contributors to the JS Self-Profiling API Specification, published by the Web Platform Incubator Community Group under the W3C Community Contributor License Agreement (CLA) . A human-readable summary is available.
This specification describes an API that allows web applications to control a sampling profiler for measuring client JavaScript execution times.
This specification was published by the Web Platform Incubator Community Group . It is not a W3C Standard nor is it on the W3C Standards Track. Please note that under the W3C Community Contributor License Agreement (CLA) there is a limited opt-out and other conditions apply. Learn more about W3C Community and Business Groups .
GitHub Issues are preferred for discussion of this specification.
This section is non-normative.
Complex web applications currently have limited visibility into where JS execution time is spent on clients. Without the ability to efficiently collect stack samples, applications are forced to instrument their code with profiling hooks that are imprecise and can significantly slow down execution. By providing an API to manipulate a sampling profiler, applications can gather rich execution data for aggregation and analysis with minimal overhead.
This section is non-normative.
The following example demonstrates how a user may profile an expensive operation, gathering JS execution samples every 10ms. The trace can be sent to a server for analysis to debug outliers and JS execution characteristics in aggregate.
const profiler = new Profiler({ sampleInterval: 10, maxBufferSize: 10000 });
const start = performance.now();
for (let i = 0; i < 1000000; i++) {
doWork();
}
const duration = performance.now() - start;
const trace = await profiler.stop();
const traceJson = JSON.stringify({
duration,
trace,
});
sendTrace
(traceJson);
Another common real-world scenario is profiling JS across a pageload. This example profiles the onload event, sending performance timing data along with the trace.
const profiler = new Profiler({ sampleInterval: 10, maxBufferSize: 10000 });
window.addEventListener('load', async () => {
const trace = await profiler.stop();
const traceJson = JSON.stringify({
timing: performance.timing,
trace,
});
sendTrace(traceJson);
});
//
Rest
of
the
page's
JS
initialization
logic
As well as sections marked as non-normative, all authoring guidelines, diagrams, examples, and notes in this specification are non-normative. Everything else in this specification is normative.
The key words MAY , MUST , MUST NOT , NOT REQUIRED , RECOMMENDED , SHOULD , and SHOULD NOT in this document are to be interpreted as described in BCP 14 [ RFC2119 ] [ RFC8174 ] when, and only when, they appear in all capitals, as shown here.
A sample is a descriptor of the instantaneous state of execution at a given point in time. Each sample is associated with a stack .
A stack is a list of frames that MUST be ordered sequentially from outermost to innermost frame.
A frame is an element in the context of a stack containing information about the current execution state.
A profiling session is an abstract producer of samples . Each session has:
{started,
paused,
stopped}
.
The UA is NOT REQUIRED to take samples at this rate. However, it is RECOMMENDED that sampling is prioritized to take samples at this rate to produce higher quality traces.
ProfilerTrace
storing
captured
samples
.
Multiple profiling sessions on the same page SHOULD be supported.
In
the
started
state,
the
UA
SHOULD
make
a
best-effort
to
capture
samples
by
executing
the
take
a
sample
algorithm
in
parallel
each
time
the
sample
interval
has
elapsed.
In
the
paused
and
stopped
states,
the
UA
SHOULD
NOT
capture
samples.
Profiling
sessions
MUST
begin
in
the
started
state.
The
UA
MAY
move
a
session
from
started
to
paused
,
and
from
paused
to
started
.
The user agent is RECOMMENDED to pause the sampling of a profiling session if the browsing context is not in the foreground.
A
stopped
session
MUST
NOT
move
to
the
started
or
paused
states.
To take a sample given a profiling session , perform the following steps:
Profiler
,
move
the
state
to
stopped
,
and
return.
ProfilerSample
.
ProfilerTrace
.
To get a stack ID given an execution context stack bound to stack , perform the following steps:
undefined
.
undefined
,
return
parentId
.
ProfilerStack
with
ProfilerStack.frameId
equal
to
frameId
,
and
ProfilerStack.parentId
equal
to
parentId
.
To get a frame ID given an execution context bound to context , perform the following steps:
undefined
.
ScriptOrModule
associated
with
context
.
ScriptOrModule
containing
the
function
that
invoked
instance
.
The
purpose
of
the
above
logic
is
to
ensure
that
built-in
functions
invoked
by
inaccessible
scripts
are
not
exposed
in
traces,
by
using
the
ScriptOrModule
that
invoked
them
for
attribution.
"[...]
the
ScriptOrModule
containing
the
function
that
invoked
instance
"
should
be
defined
more
rigorously.
We
could
leverage
the
top-most
execution
context
on
the
stack
that
defines
a
ScriptOrModule
to
provide
this,
but
it's
not
ideal
--
there
may
(theoretically)
be
other
mechanisms
for
a
builtin
to
be
enqueued
on
the
execution
context
stack,
in
which
case
the
attribution
would
be
invalid.
undefined
.
true
,
return
undefined
.
This check ensures that we avoid including stack frames from cross-origin scripts served in a CORS-cross-origin response. We may want to consider renaming muted errors to better reflect this use case.
ProfilerFrame
.
To get an element ID for an item in a list , run the following steps:
WebIDL[Exposed=Window]
interface Profiler
: EventTarget {
readonly attribute DOMHighResTimeStamp sampleInterval
;
readonly attribute boolean stopped
;
constructor
(ProfilerInitOptions
options);
Promise<ProfilerTrace
> stop
();
};
Each
Profiler
MUST
be
associated
with
exactly
one
profiling
session
.
The
sampleInterval
attribute
MUST
reflect
the
sample
interval
of
the
associated
profiling
session
expressed
as
a
DOMHighResTimeStamp
.
The
stopped
attribute
MUST
be
true
if
and
only
if
the
profiling
session
has
state
stopped
.
Profiler
is
only
exposed
on
Window
until
consensus
is
reached
on
[
Permissions-Policy
]
and
Worker
integration.
new
Profiler(options)
runs
the
following
steps
given
an
object
options
of
type
ProfilerInitOptions
:
sampleInterval
is
less
than
0,
throw
a
RangeError
.
"js-profiling"
in
the
Document
.
If
the
result
is
false,
throw
a
"NotAllowedError"
DOMException.
maxBufferSize
.
ProfilerTrace
is
set
to
«[
resources
→
«»,
frames
→
«»,
stacks
→
«»,
samples
→
«»]»
.
Profiler
associated
with
the
newly
created
profiling
session
.
Stops the profiler and returns a trace. This method MUST run these steps:
stopped
,
return
a
promise
rejected
with
an
"InvalidStateError"
DOMException.
stopped
.
ProfilerTrace
associated
with
the
profiler's
profiling
session
.
Any
samples
taken
after
stop()
is
invoked
SHOULD
NOT
be
included
by
the
profiling
session
.
WebIDLtypedef DOMString ProfilerResource
;
dictionary ProfilerTrace
{
required sequence<ProfilerResource
> resources
;
required sequence<ProfilerFrame
> frames
;
required sequence<ProfilerStack
> stacks
;
required sequence<ProfilerSample
> samples
;
};
The
resources
attribute
MUST
return
the
ProfilerResource
list
set
by
the
take
a
sample
algorithm.
The
frames
attribute
MUST
return
the
ProfilerFrame
list
set
by
the
take
a
sample
algorithm.
The
stacks
attribute
MUST
return
the
ProfilerStack
list
set
by
the
take
a
sample
algorithm.
The
samples
attribute
MUST
return
the
ProfilerSample
list
set
by
the
take
a
sample
algorithm.
Inspired by the V8 trace event format and Gecko profile format , this representation is designed to be easily and efficiently serializable.
WebIDLenum ProfilerMarker
{ "script
", "gc
", "style
", "layout
", "paint
", "other
" };
dictionary ProfilerSample
{
required DOMHighResTimeStamp timestamp
;
unsigned long long stackId
;
ProfilerMarker
? marker
;
};
timestamp
MUST
return
the
value
it
was
initialized
to.
stackId
MUST
return
the
value
it
was
initialized
to.
marker
MUST
return
the
value
it
was
initialized
to,
if
present.
The
availability
of
markers
depends
on
the
context's
cross-origin
isolation
status.
In
cross-origin
isolated
contexts,
all
marker
types
are
available.
In
non-isolated
contexts,
only
style
and
layout
markers
are
exposed
for
security
reasons.
WebIDLdictionary ProfilerStack
{
unsigned long long parentId
;
required unsigned long long frameId
;
};
parentId
MUST
return
the
value
it
was
initialized
to.
frameId
MUST
return
the
value
it
was
iniitalized
to.
WebIDLdictionary ProfilerFrame
{
required DOMString name
;
unsigned long long resourceId
;
unsigned long long line
;
unsigned long long column
;
};
name
MUST
return
the
value
it
was
initialized
to.
resourceId
MUST
return
the
value
it
was
initialized
to.
line
MUST
return
the
value
it
was
initialized
to.
column
MUST
return
the
value
it
was
initialized
to.
WebIDLdictionary ProfilerInitOptions
{
required DOMHighResTimeStamp sampleInterval
;
required unsigned long maxBufferSize
;
};
ProfilerInitOptions
MUST
support
the
following
fields:
sampleInterval
is
the
application's
desired
sample
interval
.
This
value
MUST
be
greater
than
or
equal
to
zero.
maxBufferSize
is
the
desired
sample
buffer
size
limit
,
in
samples.
This
spec
defines
a
configuration
point
in
Document
Policy
with
name
js-profiling
.
Its
type
is
boolean
with
default
value
false
.
Document
policy
is
leveraged
to
give
UAs
the
ability
to
avoid
storing
required
metadata
for
profiling
when
the
document
does
not
explicitly
request
it.
While
this
metadata
could
conceivably
be
generated
in
response
to
a
profiler
being
started,
we
store
this
bit
on
the
document
in
order
to
signal
to
the
engine
as
early
as
possible
(as
profiling
early
in
page
load
is
a
common
use
case).
This
overhead
may
be
non-trivial
depending
on
the
implementation,
and
therefore
we
default
to
false
.
For the purposes of user-agent automation and application testing, this document defines the following [ WebDriver ] extension command .
HTTP Method | URI Template |
---|---|
POST |
/session/{session
id}/forcesample
|
The Force Sample extension command forces all profiling sessions to take a sample for the purpose of enabling more deterministic testing.
The remote end steps are:
started
,
take
a
sample
with
session
.
null
.
This section is non-normative.
The following sections detail some of the privacy and security choices of the API, illustrating protection strategies against various types of attacks.
The
API
avoids
exposing
contents
of
cross-origin
scripts
by
requiring
all
functions
included
via
the
take
a
sample
algorithm
to
be
defined
in
a
script
served
with
CORS-same-origin
through
the
muted
errors
property.
Browser
builtins
(such
as
performance.now()
)
must
also
only
be
included
when
invoked
from
CORS-same-origin
script.
As a result, the API does not expose any new insight into the contents or execution characteristics of cross-origin script, beyond what is already possible through manual instrumentation. UAs are encouraged to verify this holds if they choose to support extremely low sample interval values (e.g. less than one millisecond).
Cross-origin
execution
contexts
should
not
be
observable
by
the
API
through
the
realm
check
in
the
take
a
sample
algorithm.
Cross-origin
iframes
and
other
execution
contexts
that
share
an
agent
with
a
profiler
will
therefore
not
have
their
execution
observable
through
this
API.
Timing attacks remain a concern for any API that could introduce a new source of high-resolution timing information. Timestamps gathered in traces should be obtained from the same source as [ HR-Time ]'s current high resolution time to avoid exposing a new vector for side-channel attacks.
See [ HR-Time ]'s discussion on clock resolution .
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in: