JS Self-Profiling API

Draft Community Group Report

Latest published version:
none
Latest editor's draft:
https://wicg.github.io/js-self-profiling/
Editors:
(Facebook)
(Facebook)
Feedback:
GitHub WICG/js-self-profiling (pull requests, new issue, open issues)

Abstract

This specification describes an API that allows web applications to control a sampling profiler for measuring client JavaScript execution times.

Status of This Document

This is a preview

Do not attempt to implement this version of the specification. Do not reference this version as authoritative in any way. Instead, see https://wicg.github.io/js-self-profiling/ for the Editor's draft.

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.

1. Introduction

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.

1.1 Examples

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.

Example 1: Profiling a computationally-intensive operation.
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.

Example 2: Profiling a page load.
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

2. Conformance

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.

3. Definitions

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.

4. Profiling Sessions

A profiling session is an abstract producer of samples. Each session has:

  1. A state, which is one of {started, paused, stopped}.
  2. A sample interval, defined as the periodicity at which the session obtains samples.
    Note

    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.

  3. An agent to profile.
  4. A realm to profile.
  5. A time origin that samples' timestamps are measured relative to.
  6. A sample buffer size limit.
  7. A ProfilerTrace storing captured samples.

Multiple profiling sessions on the same page SHOULD be supported.

4.1 States

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.

Note

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.

5. Processing Model

To take a sample given a profiling session, perform the following steps:

  1. If the length of ProfilerTrace.samples is greater than or equal to the sample buffer size limit associated with the profiling session, fire a new event of type samplebufferfull to the associated Profiler, move the state to stopped, and return.
  2. Let sample be a new ProfilerSample.
  3. Set the ProfilerSample.timestamp property of sample to the current high resolution time relative to the profiling session's time origin.
  4. Let stack be the execution context stack associated with the profiling session's agent.
  5. Set the ProfilerSample.stackId property of sample to the result of the get a stack ID algorithm on stack.
  6. Let marker be the result of running the capture a marker algorithm given the profiling session's associated environment settings object.
  7. If marker is not null, set the ProfilerSample.marker property of sample to marker.
  8. Add sample to the ProfilerTrace.samples associated with the session's ProfilerTrace.

To capture a marker given an environment settings object settings, perform the following steps:

  1. Let candidateMarker be the ProfilerMarker value that best describes the top-level work the user agent was performing on the profiling session's agent when the sample was taken, or null if none applies.
  2. If candidateMarker is null, return null.
  3. If the work described by candidateMarker was performed for a specific document that is not same origin with the origin of settings, return null.
  4. Return the result of running filter a marker given candidateMarker and settings.
Note

The mapping from a user agent's internal activity to a ProfilerMarker value is intentionally implementation-defined, as engine architectures differ. As non-normative guidance, a user agent is expected to report script while executing author script, gc during garbage collection, style while recalculating computed style, layout while computing box geometry (i.e. reflow), and paint while rasterizing or compositing document content. The other marker is used for user-agent work that does not fall into one of the preceding categories. When no category applies (for example, when the agent is idle), the capture a marker algorithm returns null and the ProfilerSample.marker attribute is left absent on the resulting ProfilerSample.

Note

The same-origin check ensures a marker only describes work performed for a document that is same origin with the profiler, consistent with the requirement that the API not reveal cross-origin activity. Work that is not attributable to a single document — for example, engine-wide garbage collection — is not constrained by that check; such work is instead exposed only when the profiler is cross-origin isolated, as enforced by filter a marker.

To filter a marker given a ProfilerMarker marker and an environment settings object settings, perform the following steps:

  1. If settings' cross-origin isolated capability is true, return marker.
  2. If marker is "style" or "layout", return marker.
  3. Return null.
Note

The filter a marker algorithm produces the observable behavior that in non-cross-origin-isolated contexts, only the style and layout markers appear on ProfilerSample; the script, gc, paint, and other markers are suppressed. See Conditional marker exposure for the rationale behind this behavior. User agents MAY offer opt-in mechanisms (for example, an origin-trial token or a document policy) that widen the set returned by filter a marker for a specific environment settings object; such opt-ins MUST NOT expose markers cross-origin.

To get a stack ID given an execution context stack bound to stack, perform the following steps:

  1. If stack is empty, return undefined.
  2. Let head be the top element of stack, and tail be the remainder of stack after removing its top element.
  3. Let parentId be the result of calling get a stack ID recursively on tail.
  4. Let frameId be the result of calling get a frame ID on head.
  5. If frameId is undefined, return parentId.
  6. Let profilerStack be a new ProfilerStack with ProfilerStack.frameId equal to frameId, and ProfilerStack.parentId equal to parentId.
  7. Return the result of running get an element ID on profilerStack and ProfilerTrace.stacks.

To get a frame ID given an execution context bound to context, perform the following steps:

  1. If the realm associated with context does not match the realm associated with the profiling session, return undefined.
  2. Let instance be equal to the function instance associated with context.
  3. Let scriptOrModule be equal to the ScriptOrModule associated with context.
  4. Let attributedScriptOrModule be equal to the result of running the following algorithm:
    1. If scriptOrModule is non-null, return scriptOrModule.
    2. If instance is a built-in function object, return the ScriptOrModule containing the function that invoked instance.
      Editor's note

      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.

      Issue

      "[...] 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.

    3. Otherwise, return null.
  5. If attributedScriptOrModule is null, return undefined.
  6. Let attributedScript be the script obtained from attributedScriptOrModule.[[HostDefined]].
  7. If attributedScript is a classic script and its muted errors boolean is equal to true, return undefined.
    Editor's note

    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.

  8. Let frame be a new ProfilerFrame.
  9. Set ProfilerFrame.name of frame to the function instance name associated with instance.
  10. If scriptOrModule is non-null:
    1. Let script be the script obtained from scriptOrModule.[[HostDefined]].
    2. Let resourceString be equal to the base URL of script.
    3. Set ProfilerFrame.resourceId to the result of running get an element ID on resourceString and ProfilerTrace.resources.
    4. Set ProfilerFrame.line of frame to the 1-based index of the line at which instance is defined in script.
    5. Set ProfilerFrame.column of frame to the 1-based index of the column at which instance is defined in script.
  11. Return the result of running get an element ID on frame and ProfilerTrace.frames.

To get an element ID for an item in a list, run the following steps:

  1. If there exists an element in list component-wise equal to item, return its index.
  2. Otherwise, append item to the end of list and return its index.

6. The Profiler Interface

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.

Issue 33: How does document policy work in workers?

Profiler is only exposed on Window until consensus is reached on [Permissions-Policy] and Worker integration.

6.1 new Profiler(options)

new Profiler(options) runs the following steps given an object options of type ProfilerInitOptions:
  1. If options' sampleInterval is less than 0, throw a RangeError.
  2. Get the policy value for "js-profiling" in the Document. If the result is false, throw a "NotAllowedError" DOMException.
  3. Create a new profiling session where:
    1. The associated sample interval is set to either ProfilerInitOptions.sampleInterval OR the next lowest interval supported by the UA.
    2. The associated time origin is equal to the time origin of the current global object.
    3. The associated sample buffer size limit is set to maxBufferSize.
    4. The associated agent is set to the surrounding agent.
    5. The associated realm is set to the current realm record.
    6. The associated ProfilerTrace is set to «[resources → «», frames → «», stacks → «», samples → «»]».
  4. Return a new Profiler associated with the newly created profiling session.

6.2 stop() method

Stops the profiler and returns a trace. This method MUST run these steps:

  1. If the associated profiling session's state is stopped, return a promise rejected with an "InvalidStateError" DOMException.
  2. Set the profiling session's state to stopped.
  3. Let p be a new promise.
  4. Run the following steps in parallel:
    1. Perform any implementation-defined work to stop the profiling session.
    2. Resolve p with the ProfilerTrace associated with the profiler's profiling session.
  5. Return p.

Any samples taken after stop() is invoked SHOULD NOT be included by the profiling session.

7. The ProfilerTrace Dictionary

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.

Note

Inspired by the V8 trace event format and Gecko profile format, this representation is designed to be easily and efficiently serializable.

7.1 The ProfilerSample Dictionary

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 by the take a sample algorithm, or be absent if no marker was set. The set of marker values that may be exposed is governed by the filter a marker algorithm.

Note

In cross-origin isolated contexts the filter a marker algorithm returns every value in the ProfilerMarker enumeration. In non-isolated contexts it returns only style and layout, so any other marker is suppressed (the marker attribute is absent on the corresponding ProfilerSample).

7.2 The ProfilerStack Dictionary

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.

7.3 The ProfilerFrame Dictionary

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.

8. The ProfilerInitOptions dictionary

WebIDLdictionary ProfilerInitOptions {
  required DOMHighResTimeStamp sampleInterval;
  required unsigned long maxBufferSize;
};

ProfilerInitOptions MUST support the following fields:

9. Document Policy

This spec defines a configuration point in Document Policy with name js-profiling. Its type is boolean with default value false.

Note

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.

10. Automation

For the purposes of user-agent automation and application testing, this document defines the following [WebDriver] extension command.

10.1 Force Sample

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:

  1. Let sessions be a list of all profiling sessions created in the current browsing context.
  2. For each session of sessions:
    1. If the state of session is started, take a sample with session.
  3. Return success with data null.

11. Privacy and Security

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.

11.1 Cross-origin script contents

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).

11.2 Cross-origin execution

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.

11.3 Timing attacks

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.

Note

See [HR-Time]'s discussion on clock resolution.

11.4 Conditional marker exposure

By default, the filter a marker algorithm exposes the script, gc, paint, and other markers only when the profiler's environment settings object has its cross-origin isolated capability set to true. These markers can reflect engine work that cannot reliably be attributed to a single origin — for example, garbage collection and other cross-context engine activity can correlate work across contexts, and painting may composite content from other frames. Exposing the timing of such work without isolation could therefore contribute to a cross-origin side channel of the kind that cross-origin isolation is designed to mitigate, so it is gated behind it. A user agent MAY widen the exposed set through an explicit opt-in (as described in the filter a marker note above), but such opt-ins MUST NOT expose markers cross-origin.

The style and layout markers are exposed without cross-origin isolation because the timing they reveal is already same-origin observable. A document can already synchronously flush, and time, its own style and layout using existing APIs — for example, getBoundingClientRect() forces layout, and getComputedStyle() flushes style (and layout where required). To preserve this property, the capture a marker algorithm reports a marker only for work performed for a document that is same origin with the profiler; work that cannot be attributed to a same-origin document is not reported through these markers. As a result, they expose no timing information that a same-origin document could not already obtain for itself.

A. References

A.1 Normative references

[dom]
DOM Standard. Anne van Kesteren. WHATWG. Living Standard. URL: https://dom.spec.whatwg.org/
[ECMA-262]
ECMAScript Language Specification. Ecma International. URL: https://tc39.es/ecma262/multipage/
[HR-TIME-2]
High Resolution Time Level 2. Ilya Grigorik. W3C. 21 November 2019. W3C Recommendation. URL: https://www.w3.org/TR/hr-time-2/
[html]
HTML Standard. Anne van Kesteren; Domenic Denicola; Dominic Farolino; Ian Hickson; Philip Jägenstedt; Simon Pieters. WHATWG. Living Standard. URL: https://html.spec.whatwg.org/multipage/
[HTML5]
HTML5. Ian Hickson; Robin Berjon; Steve Faulkner; Travis Leithead; Erika Doyle Navara; Theresa O'Connor; Silvia Pfeiffer. W3C. 27 March 2018. W3C Recommendation. URL: https://www.w3.org/TR/html5/
[infra]
Infra Standard. Anne van Kesteren; Domenic Denicola. WHATWG. Living Standard. URL: https://infra.spec.whatwg.org/
[RFC2119]
Key words for use in RFCs to Indicate Requirement Levels. S. Bradner. IETF. March 1997. Best Current Practice. URL: https://www.rfc-editor.org/info/rfc2119/
[RFC8174]
Ambiguity of Uppercase vs Lowercase in RFC 2119 Key Words. B. Leiba. IETF. May 2017. Best Current Practice. URL: https://www.rfc-editor.org/info/rfc8174/
[WebDriver]
WebDriver. Simon Stewart; David Burns. W3C. 2 July 2026. W3C Working Draft. URL: https://www.w3.org/TR/webdriver2/
[WEBIDL]
Web IDL Standard. Edgar Chen; Timothy Gu. WHATWG. Living Standard. URL: https://webidl.spec.whatwg.org/

A.2 Informative references

[HR-Time]
High Resolution Time. Yoav Weiss. W3C. 24 March 2026. W3C Working Draft. URL: https://www.w3.org/TR/hr-time-3/
[Permissions-Policy]
Permissions Policy. Ian Clelland. W3C. 18 June 2026. W3C Working Draft. URL: https://www.w3.org/TR/permissions-policy-1/