Long Tasks API

Editor’s Draft,

More details about this document
This version:
https://w3c.github.io/longtasks/
Test Suite:
http://w3c-test.org/longtask-timing/
Issue Tracking:
GitHub
Inline In Spec
Editor:
(Google)
Former Editors:
(Google)
(Google)
(Google)

Abstract

This document defines an API that web page authors can use to detect presence of "long tasks" that monopolize the UI thread for extended periods of time and block other critical tasks from being executed - e.g. reacting to user input.

Status of this document

This is a public copy of the editors’ draft. It is provided for discussion only and may change at any moment. Its publication here does not imply endorsement of its contents by W3C. Don’t cite this document other than as work in progress.

GitHub Issues are preferred for discussion of this specification.

This document is governed by the 03 November 2023 W3C Process Document.

If you wish to make comments regarding this document, please send them to public-web-perf@w3.org (subscribe, archives) with [LongTasks] at the start of your email’s subject.

1. Introduction

As the page is loading and while the user is interacting with the page afterwards, both the application and browser queue various events that are then executed by the browser -- e.g. user agent schedules input events based on user’s activity, the application schedules callbacks for requestAnimationFrame and other callbacks, etc. Once in the queue, the browser dequeues these events one-by-one and executes them.

However, some tasks can take a long time (multiple frames) and if/when that happens, the UI thread may become blocked and block all other tasks as well. To the user, this is commonly visible as a "locked up" page where the browser is unable to respond to user input; this is a major source of bad user experience on the web today:

Delayed "time to Interactive":

while the page is loading, or even completely visually rendered, long tasks often tie up the main thread and prevent the user from interacting with the page. Poorly designed third-party content is frequently the culprit.

High/variable input latency:

critical user-interaction events (e.g. tap, click, scroll, wheel, etc.) are queued behind long tasks which yields janky and unpredictable user experience.

High/variable event handling latency:

like input, processing event callbacks (e.g. onload events, etc.) delay application updates.

Janky animations and scrolling:

some animation and scrolling interactions require coordination between compositor and main threads; if a long task is blocking the main thread it can affect responsiveness of animations and scrolling.

Some applications (and RUM vendors) are already attempting to identify and track cases where "long tasks" happen. For example, one known pattern is to install a ~short periodic timer and inspect the elapsed time between the successive expirations: if the elapsed time is greater than the timer period, then there is high likelihood that one or more long tasks have delayed execution of the event loop. This approach mostly works but has several bad performance implications: by polling to detect long tasks, the application prevents quiescence and long idle blocks (see requestIdleCallback); it’s bad for battery life; there is no way to know what is causing the delay (e.g. first party or third party code).

The RAIL performance model suggests that applications should respond to user input in less than 100ms (for touch move and scrolling, the threshold is 16ms). The goal of this API is to surface notifications about tasks that may prevent the application from hitting these targets. This API surfaces tasks that take 50ms or more. A website without these tasks should respond to user input in under 100ms: it will take less than 50ms to finish the task that is being executed when the user input is received and less than 50ms to execute the task to react to such user input.

1.1. Usage Example

const observer = new PerformanceObserver(function(list) {
    for (const entry of list.getEntries()) {
        // Process long task notifications:
        // report back for analytics and monitoring
        // ...
    }
});
// Register observer for previous and future long task notifications.
observer.observe({type: "longtask", buffered: true});
// Long script execution after this will result in queueing
// and receiving "longtask" entries in the observer.

2. Terminology

Long task refers to any of the following occurrences whose duration exceeds 50ms:

The browsing context container for a browsing context bc is bc’s active document's node navigable's container.

Note: This term is outdated, and the new terms should be reused when revamping this.

Culprit browsing context container refers to the browsing context container (iframe, object, etc.) that is being implicated, on the whole, for a long task.

Attribution refers to identifying the type of work (such as script, layout etc.) that contributed significantly to the long task, as well as identifying which culprit browsing context container is responsible for that work.

3. Long Task Timing

Long Task timing involves the following new interfaces:

3.1. PerformanceLongTaskTiming interface

[Exposed=Window]
interface PerformanceLongTaskTiming : PerformanceEntry {
    /* Overloading PerformanceEntry */
    readonly attribute DOMHighResTimeStamp startTime;
    readonly attribute DOMHighResTimeStamp duration;
    readonly attribute DOMString name;
    readonly attribute DOMString entryType;

    readonly attribute FrozenArray<TaskAttributionTiming> attribution;
    [Default] object toJSON();
};

The values of the attributes of a PerformanceLongTaskTiming are set in the processing model in § 4.1 Report long tasks. The following provides an informative summary of how they will be set.

The name attribute’s getter will return one of the following strings:

"unknown"

The long task originated from work that the user agent performed outside of the event loop.

"self"

The long task originated from an event loop task within this browsing context.

"same-origin-ancestor"

The long task originated from an event loop task within a same-origin ancestor navigable.

"same-origin-descendant"

The long task originated from an event loop task within a same-origin descendant browsing context.

"same-origin"

The long task originated from an event loop task within a same-origin browsing context that is not an ancestor or descendant.

"cross-origin-ancestor"

The long task originated from an event loop task within a cross-origin ancestor navigable.

"cross-origin-descendant"

The long task originated from an event loop task within a cross-origin descendant browsing context.

"cross-origin-unreachable"

The long task originated from an event loop task within a cross-origin browsing context that is not an ancestor or descendant.

"multiple-contexts"

The long task originated from an event loop task involving multiple browsing contexts.

Note: There are some inconsistencies across these names, such as the "-unreachable" and the "-contexts" suffixes. These names are kept for backward compatibility reasons.

The entryType attribute’s getter step is to return "longtask".

The startTime attribute’s getter step is to return a DOMHighResTimeStamp of when the task started.

The duration attribute’s getter step is to return a DOMHighResTimeStamp equal to the elapsed time between the start and end of task, with a 1 ms granularity.

The attribution attribute’s getter will return a frozen array of TaskAttributionTiming entries.

3.2. TaskAttributionTiming interface

[Exposed=Window]
interface TaskAttributionTiming : PerformanceEntry {
    /* Overloading PerformanceEntry */
    readonly attribute DOMHighResTimeStamp startTime;
    readonly attribute DOMHighResTimeStamp duration;
    readonly attribute DOMString name;
    readonly attribute DOMString entryType;

    readonly attribute DOMString containerType;
    readonly attribute DOMString containerSrc;
    readonly attribute DOMString containerId;
    readonly attribute DOMString containerName;
    [Default] object toJSON();
};

The values of the attributes of a TaskAttributionTiming are set in the processing model in § 4.1 Report long tasks. The following provides an informative summary of how they will be set.

The name attribute’s getter will always return "unknown".

The entryType attribute’s getter will always return "taskattribution".

The startTime attribute’s getter will always return 0.

The duration attribute’s getter will always return 0.

The containerType attribute’s getter will return the type of the culprit browsing context container, such as "iframe", "embed", or "object". If no single culprit browsing context container is found, it will return "window".

The containerName attribute’s getter will return the value of the container’s name content attribute. If no single culprit browsing context container is found, it will return the empty string.

The containerId attribute’s getter will return the value of the container’s id content attribute. If no single culprit browsing context container is found, it will return the empty string.

The containerSrc attribute’s getter will return the value of the container’s src content attribute. If no single culprit browsing context container is found, it will return the empty string.

3.3. Pointing to the culprit

This section is non-normative.

A long task can involve different types of work (such as script, layout, style etc), and it could be executed within different browsing contexts, or it could be global in nature such as a long garbage collection that spans the entire agent cluster or browsing context group set.

Thus attribution has a couple of facets:

Therefore, name and attribution fields on PerformanceLongTaskTiming together paint the picture for where the blame rests for a long task. When delivering this information the Web’s same-origin policy must be adhered to.

These fields are not independent. The following gives an overview of how they are related:

name Culprit browsing context container implicated by attribution
"self" empty
"same-origin-ancestor" same-origin culprit
"same-origin-descendant" same-origin culprit
"same-origin" same-origin culprit
"cross-origin-ancestor" empty
"cross-origin-descendant" empty
"cross-origin-unreachable" empty
"multiple-contexts" empty
"unknown" empty

4. Processing model

Note: A user agent implementing the Long Tasks API would need to include "longtask" in supportedEntryTypes for Window contexts, respectively.

This allows developers to detect support for long tasks.

4.1. Report long tasks

Given start time, end time, top-level browsing contexts, and task, perform the following algorithm:
  1. Record task end time given end time and task’s document.

  2. If end time minus start time is less than the long tasks threshold of 50 ms, abort these steps.

  3. Let destinationRealms be an empty set.

  4. Determine the set of JavaScript Realms to which reports will be delivered:

    For each top-level browsing context topmostBC in top-level browsing contexts:

    1. Add topmostBC’s active document's relevant Realm to destinationRealms.

    2. Let descendantBCs be topmostBC’s active document's list of the descendant browsing contexts.

    3. Let document be descendantBC’s active document.

    4. For each descendantBC in descendantBCs, add (document’s relevant Realm, document’s relevant settings object's cross-origin isolated capability) to destinationRealms.

  5. A user agent may remove some JavaScript Realms from destinationRealms.

Note: this removal could be used to avoid reporting long tasks for JavaScript Realms that the user agent handles in a separate process. However, this concept is not specified precisely.

there is some ongoing discussion regarding the scope of which Documents gain visibility over which long tasks, so this logic could change in the future. [Issue #75]

  1. For each (destinationRealm, crossOriginIsolatedCapability) in destinationRealms:

    1. Let name be the empty string. This will be used to report minimal culprit attribution, below.

    2. Let culpritSettings be null.

    3. Process task’s script evaluation environment settings object set to determine name and culpritSettings as follows:

      1. If task’s script evaluation environment settings object set is empty: set name to "unknown" and culpritSettings to null.

      2. Otherwise, if task’s script evaluation environment settings object set's length is greater than one: set name to "multiple-contexts" and culpritSettings to null.

      3. Otherwise, i.e. if task’s script evaluation environment settings object set's length is one:

        1. Set culpritSettings to the single item in task’s script evaluation environment settings object set.

        2. Let destinationSettings be destinationRealm’s relevant settings object.

        3. Let destinationOrigin be destinationSettings’s origin.

        4. Let destinationBC be destinationSettings’s global object's browsing context.

        5. Let culpritBC be culpritSettings’s global object's browsing context.

        6. Assert: culpritBC is not null.

        7. If culpritSettings is the same as destinationSettings, set name to "self".

        8. Otherwise, if culpritSettings’s origin and destinationOrigin are same origin:

          1. If destinationBC is null, set name to "same-origin".

          2. Otherwise, if culpritBC is an ancestor of destinationBC, set name to "same-origin-ancestor".

          3. Otherwise, if destinationBC is an ancestor of culpritBC, set name to "same-origin-descendant".

          4. Otherwise, set name to "same-origin".

        9. Otherwise:

          1. If destinationBC is null, set name to "cross-origin-unreachable".

          2. Otherwise, if culpritBC is an ancestor of destinationBC, set name to "cross-origin-ancestor" and set culpritSettings to null.

            NOTE: this is not reported because of security. Developers should look this up themselves.

          3. Otherwise, if destinationBC is an ancestor of culpritBC, set name to "cross-origin-descendant".

          4. Otherwise, set name to "cross-origin-unreachable".

    4. Let attribution be a new TaskAttributionTiming object with destinationRealm and set its attributes as follows:

      1. Set attribution’s name attribute to "unknown".

        NOTE: future iterations of this API will add more values to the name attribute of a TaskAttributionTiming object, but for now it can only be a single value.

      2. Set attribution’s entryType attribute to "taskattribution".

      3. Set attribution’s startTime and duration to 0.

      4. Set attribution’s containerType attribute to "window".

      5. Set attribution’s containerName and containerSrc attributes to the empty string.

      6. If culpritSettings is not null:

        1. Let culpritBC be culpritSettings’s global object's browsing context.

        2. Assert: culpritBC is not null.

        3. Let container be culpritBC’s browsing context container.

        4. Assert: container is not null.

        5. Set attribution’s containerId attribute to the value of container’s ID, or the empty string if the ID is unset.

        6. If container is an iframe element:

          1. Set attribution’s containerType attribute to "iframe".

          2. Set attribution’s containerName attribute to the value of container’s name content attribute, or the empty string if the attribute is absent.

          3. Set attribution’s containerSrc attribute to the value of container’s src content attribute, or the empty string if the attribute is absent.

          NOTE: it is intentional that we record the frame’s src attribute here, and not its current URL, as this is meant primarily to help identify frames, and allowing discovery of the current URL of a cross-origin iframe is a security problem.

        7. If container is a frame element:

          1. Set attribution’s containerType attribute to "frame".

          2. Set attribution’s containerName attribute to the value of container’s name content attribute, or the empty string if the attribute is absent.

          3. Set attribution’s containerSrc attribute to the value of container’s src content attribute, or the empty string if the attribute is absent.

        8. If container is an object element:

          1. Set attribution’s containerType attribute to "object".

          2. Set attribution’s containerName attribute to the value of container’s name content attribute, or the empty string if the attribute is absent.

          3. Set attribution’s containerSrc attribute to the value of container’s data content attribute, or the empty string if the attribute is absent.

        9. If container is an embed element:

          1. Set attribution’s containerType attribute to "embed".

          2. Set attribution’s containerName attribute to the empty string.

          3. Set attribution’s containerSrc attribute to the value of container’s src content attribute, or the empty string if the attribute is absent.

    5. Create a new PerformanceLongTaskTiming object newEntry with destinationRealm and set its attributes as follows:

      1. Set newEntry’s name attribute to name.

      2. Set newEntry’s entryType attribute to "longtask".

      3. Set newEntry’s startTime attribute to the result of coarsening start time given crossOriginIsolatedCapability.

      4. Let dur be the result of coarsening end time given crossOriginIsolatedCapability, minus newEntry’s startTime.

      5. Set newEntry’s duration attribute to the integer part of dur.

      6. If attribution is not null, set newEntry’s attribution attribute to a new frozen array containing the single value attribution.

        NOTE: future iterations of this API will add more values to the attribution attribute, but for now it only contains a single value.

    6. Queue the PerformanceEntry newEntry.

5. Security & privacy considerations

Long Tasks API adheres to the same-origin policy by including origin-safe attribution information about the source of the long task. There is a 50ms threshold for long tasks. Durations are only provided in 1 ms granularity. Together this provides adequate protection against cross-origin leaks.

The Long Tasks API provides timing information about the duration and type of tasks executed by the user, as well as attribution such as the browsing context causing the function calls. This could enable an attacker to perform side-channel timing attacks to guess the user’s action, or identify the user. For example, a pattern of long script followed by a long render could be put together to guess user’s interaction with a social widget. Detailed function call attribution would be used to determine the user’s action.

While the API doesn’t introduce any new privacy attacks, it could make existing privacy attacks faster. Mitigations for this are possible and can be implemented as needed:

5.1. What is Exposed to Observers?

All observers within the top level page (i.e. all iframes in the page and the main frame) will receive notifications about presence of long tasks. We expose the start time of the task, its duration (with 1 ms granularity), and a pointer to the culprit frame. This information can already be observed today, and with higher resolution, using setTimeout. An attacker can do this by clearing everything else on the page and adding the vulnerable cross-origin resource to ensure that delays from the setTimeout are caused by that resource. Observers in other different pages (tabs or windows) should not receive notifications, regardless of the architecture of the user agent.

Cross origin rules for what is exposed:

5.2. Attack Scenarios Considered

The following are the timing attacks considered:

  1. Traditional timing attacks: using external resource load time to reveal the size of private data. For instance the number of hidden pictures in a gallery, whether username is valid, etc. See an example.

  2. Side-channel timing attacks: using time for video parsing, script parsing, App Cache reads or Cache API (service workers) usage to uniquely identify a user, or to create a profile of the user’s age, gender, location, and interests etc. For instance, status updates from a social network can be limited to certain demographic (eg. females of age 20-30) the file size of the permalink page can be used to determine whether the user is in the target demographic.

These scenarios are addressed by the 50ms threshold AND respecting cross-origin boundary i.e. not showing task type or additional attribution to untrusted cross origin observers.

Conformance

Document conventions

Conformance requirements are expressed with a combination of descriptive assertions and RFC 2119 terminology. The key words “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY”, and “OPTIONAL” in the normative parts of this document are to be interpreted as described in RFC 2119. However, for readability, these words do not appear in all uppercase letters in this specification.

All of the text of this specification is normative except sections explicitly marked as non-normative, examples, and notes. [RFC2119]

Examples in this specification are introduced with the words “for example” or are set apart from the normative text with class="example", like this:

This is an example of an informative example.

Informative notes begin with the word “Note” and are set apart from the normative text with class="note", like this:

Note, this is an informative note.

Conformant Algorithms

Requirements phrased in the imperative as part of algorithms (such as "strip any leading space characters" or "return false and abort these steps") are to be interpreted with the meaning of the key word ("must", "should", "may", etc) used in introducing the algorithm.

Conformance requirements phrased as algorithms or specific steps can be implemented in any manner, so long as the end result is equivalent. In particular, the algorithms defined in this specification are intended to be easy to understand and are not intended to be performant. Implementers are encouraged to optimize.

Index

Terms defined by this specification

Terms defined by reference

References

Normative References

[DOM]
Anne van Kesteren. DOM Standard. Living Standard. URL: https://dom.spec.whatwg.org/
[ECMASCRIPT]
ECMAScript Language Specification. URL: https://tc39.es/ecma262/multipage/
[HR-TIME-2]
Ilya Grigorik. High Resolution Time Level 2. URL: https://w3c.github.io/hr-time/
[HR-TIME-3]
Yoav Weiss. High Resolution Time. URL: https://w3c.github.io/hr-time/
[HTML]
Anne van Kesteren; et al. HTML Standard. Living Standard. URL: https://html.spec.whatwg.org/multipage/
[LONG-ANIMATION-FRAMES]
Long Animation Frames API. Editor's Draft. URL: https://w3c.github.io/long-animation-frames/
[PERFORMANCE-TIMELINE]
Nicolas Pena Moreno. Performance Timeline. URL: https://w3c.github.io/performance-timeline/
[RFC2119]
S. Bradner. Key words for use in RFCs to Indicate Requirement Levels. March 1997. Best Current Practice. URL: https://datatracker.ietf.org/doc/html/rfc2119
[WEBIDL]
Edgar Chen; Timothy Gu. Web IDL Standard. Living Standard. URL: https://webidl.spec.whatwg.org/

IDL Index

[Exposed=Window]
interface PerformanceLongTaskTiming : PerformanceEntry {
    /* Overloading PerformanceEntry */
    readonly attribute DOMHighResTimeStamp startTime;
    readonly attribute DOMHighResTimeStamp duration;
    readonly attribute DOMString name;
    readonly attribute DOMString entryType;

    readonly attribute FrozenArray<TaskAttributionTiming> attribution;
    [Default] object toJSON();
};

[Exposed=Window]
interface TaskAttributionTiming : PerformanceEntry {
    /* Overloading PerformanceEntry */
    readonly attribute DOMHighResTimeStamp startTime;
    readonly attribute DOMHighResTimeStamp duration;
    readonly attribute DOMString name;
    readonly attribute DOMString entryType;

    readonly attribute DOMString containerType;
    readonly attribute DOMString containerSrc;
    readonly attribute DOMString containerId;
    readonly attribute DOMString containerName;
    [Default] object toJSON();
};

Issues Index

there is some ongoing discussion regarding the scope of which Documents gain visibility over which long tasks, so this logic could change in the future. [Issue #75]
MDN

PerformanceLongTaskTiming/attribution

In only one current engine.

FirefoxNoneSafariNoneChrome58+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebView?Samsung Internet?Opera Mobile?
MDN

PerformanceLongTaskTiming/toJSON

In only one current engine.

FirefoxNoneSafariNoneChrome58+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebView?Samsung Internet?Opera Mobile?
MDN

PerformanceLongTaskTiming

In only one current engine.

FirefoxNoneSafariNoneChrome58+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebView?Samsung Internet?Opera Mobile?
MDN

TaskAttributionTiming/containerId

In only one current engine.

FirefoxNoneSafariNoneChrome58+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebView?Samsung Internet?Opera Mobile?
MDN

TaskAttributionTiming/containerName

In only one current engine.

FirefoxNoneSafariNoneChrome58+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebView?Samsung Internet?Opera Mobile?
MDN

TaskAttributionTiming/containerSrc

In only one current engine.

FirefoxNoneSafariNoneChrome58+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebView?Samsung Internet?Opera Mobile?
MDN

TaskAttributionTiming/containerType

In only one current engine.

FirefoxNoneSafariNoneChrome58+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebView?Samsung Internet?Opera Mobile?
MDN

TaskAttributionTiming/toJSON

In only one current engine.

FirefoxNoneSafariNoneChrome58+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebView?Samsung Internet?Opera Mobile?
MDN

TaskAttributionTiming

In only one current engine.

FirefoxNoneSafariNoneChrome58+
Opera?Edge79+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebView?Samsung Internet?Opera Mobile?