W3C

Long Tasks API 1

W3C First Public Working Draft,

This version:
https://www.w3.org/TR/2017/WD-longtasks-1-20170907/
Latest published version:
https://www.w3.org/TR/longtasks-1/
Latest published version of Long Task:
https://www.w3.org/TR/longtasks/
Editor's Draft:
https://w3c.github.io/longtasks/
Test Suite:
http://w3c-test.org/longtask-timing/
Issue Tracking:
GitHub
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 section describes the status of this document at the time of its publication. Other documents may supersede this document. A list of current W3C publications and the latest revision of this technical report can be found in the W3C technical reports index at https://www.w3.org/TR/.

This document was published by the Web Performance Working Group as a First Public Working Draft. This document is intended to become a W3C Recommendation.

Feedback and comments on this specification are welcome, please send them to public-web-perf@w3.org (subscribe, archives) with [longtasks] at the start of your email’s subject.

Publication as a First Public Working Draft does not imply endorsement by the W3C Membership. This is a draft document and may be updated, replaced or obsoleted by other documents at any time. It is inappropriate to cite this document as other than work in progress.

This document was produced by a group operating under the 5 February 2004 W3C Patent Policy. W3C maintains a public list of any patent disclosures made in connection with the deliverables of the group; that page also includes instructions for disclosing a patent. An individual who has actual knowledge of a patent which the individual believes contains Essential Claim(s) must disclose the information in accordance with section 6 of the W3C Patent Policy.

This document is governed by the 1 March 2017 W3C Process Document.

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, these events are then dequeued one-by-one by the browser and executed.

However, some task can take a long time (multiple frames), and if and when that happens, the UI thread is locked and all other tasks are blocked 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 long tasks often tie up the main thread and prevent the user from interactive with the page even though the page is visually rendered. Poorly designed third-party content is a frequent culprit.

High/variable input latency:

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

High/variable event handling latency:

similar to input, but for processing event callbacks (e.g. onload events, and so on), which delay application updates.

Janky animations and scrolling:

some animation and scrolling interactions require coordination between compositor and main threads; if the main thread is blocked due to a long task, 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 calls: 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 timer. This mostly works, but it has several bad performance implications: the application is polling to detect long tasks, which prevents quiescence and long idle blocks (see requestIdleCallback); it’s bad for battery life; there is no way to know who caused the delay (e.g. first party vs third party code)

RAIL performance model suggests that applications should respond in under 100ms to user input; for touch move and scrolling in under 16ms. Our goal with this API is to surface notifications about tasks that may prevent the application from hitting these targets.

1.1. Usage Example

var observer = new PerformanceObserver(function(list) {
    var perfEntries = list.getEntries();
    for (var i = 0; i < perfEntries.length; i++) {
        // Process long task notifications:
        // report back for analytics and monitoring
        // ...
    }
});
// register observer for long task notifications
observer.observe({entryTypes: ["longtask"]});
// Long script execution after this will result in queueing
// and receiving “longtask” entries in the observer.

2. Terminology

Long task refers to an event loop task that exceeds 50ms.

Frame or frame context refers to the browsing context, such as iframe (not animation frame), embed or object in which some work (such as script or layout) occurs.

Culprit frame refers to the browsing context (iframe, embed or 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 AND which browsing context is responsible for that work.

3. Long Task Timing

Long Task timing involves the following new interfaces

3.1. PerformanceLongTaskTiming interface

interface PerformanceLongTaskTiming : PerformanceEntry {
    readonly attribute FrozenArray<TaskAttributionTiming> attribution;
};

PerformanceLongTaskTiming extends the following attributes of PerformanceEntry interface:

PerformanceLongTaskTiming adds the following attributes:

3.2. TaskAttributionTiming interface

interface TaskAttributionTiming : PerformanceEntry {
    readonly attribute DOMString containerType;
    readonly attribute DOMString containerSrc;
    readonly attribute DOMString containerId;
    readonly attribute DOMString containerName;
};

TaskAttributionTiming extends the following attributes of PerformanceEntry interface:

TaskAttributionTiming adds the following attributes:

3.3. Pointing to the culprit

Long task represents the top level event loop task. Within this task, different types of work (such as script, layout, style etc) may be done, and they could be executed within different frame contexts. The type of work could also be global in nature such as a long GC that is process or frame-tree wide.

Thus pointing to the culprit has 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 origin-policy must be adhered to.

As an illustration, the TaskAttributionTiming entry in attribution is populated with "script" work, and the container or frame implicated in attribution should match up with the name as follows:

value of name culprit frame implicated in attribution
self empty
same-origin-ancestor same-origin culprit frame
same-origin-descendant same-origin culprit frame
same-origin same-origin culprit frame
cross-origin-ancestor empty
cross-origin-descendant empty
cross-origin-unreachable empty
multiple-contexts empty
unknown empty

4. Processing Model

4.1. Modifications to other specifications

4.1.1. HTML: event loop definitions

Each task gets an associated start time, end time, and script evaluation environment settings object set.

4.1.2. HTML: event loop processing model

Before Step #3:

After Step #3:

4.1.3. HTML: calling scripts

In prepare to run script, add a step at the end to add settings to the currently running task’s script evaluation environment settings object set.

4.2. Additions to the Long Task Spec

4.2.1. Report Long Tasks

Given a task task, perform the following algorithm:

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

  2. Let destinationRealms be an empty set.

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

    For each environment settings object settings in task’s script evaluation environment settings object set:

    1. Let topmostBC be settings’s responsible browsing context’s top-level browsing context.

    2. Add topmostBC’s Window’s relevant Realm to destinationRealms.

    3. Let descendantBCs be topmostBC’s active document’s list of descendant browsing contexts.

    4. For each descendantBC in descendantBCs, add descendantBC’s Window’s relevant Realm to destinationRealms.

  4. For each destinationRealm in destinationRealms:

    1. Let name be the empty string. This will be used to report minimal frame 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. 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. 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 destinationOrigin be destinationRealm’s relevant settings object’s origin.

        3. Let destinationBC be destinationRealm’s relevant settings object’s responsible browsing context.

        4. If culpritSettings’s origin and destinationOrigin are [same origin]:

          1. If culpritSettings’s responsible browsing context is an ancestor of destinationBC, set name to “same-origin-ancestor”.

          2. If culpritSettings’s responsible browsing context is a descendant of destinationBC, set name to “same-origin-descendant”.

        5. Otherwise:

          1. If culpritSettings’s responsible browsing context 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.

          2. If culpritSettings’s responsible browsing context is a descendant of destinationBC, set name to “cross-origin-descendant”.

  5. Create a new TaskAttributionTiming object attribution and set its attributes as follows:

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

    2. Set attribution’s entryType attribute to “taskattribution”

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

    4. If culpritSettings is not null, and culpritSettings’s responsible browsing context has a browsing context container that is an iframe element, then let iframe be that element, and perform the following steps:

      1. Set attribution’s frameName attribute to the value of iframe’s name content attribute, or null if the attribute is absent.

      2. Set attribution’s frameSrc attribute to the value of iframe’s src content attribute, or null 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.

      3. Set attribution’s frameId attribute to the value of iframe’s id content attribute, or null if the attribute is absent.

  6. Create a new PerformanceLongTaskTiming object newEntry 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 start time

    4. Set newEntry’s startTime attribute to end time minus start time

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

  7. Queue the PerformanceEntry newEntry on destinationRealm.

    NOTE: the "queue a PerformanceEntry" algorithm will end up doing nothing if no observers are registered. Implementations likely will want to bail out from this algorithm earlier in that case, instead of assembling all the above information only to find out nobody is listening for it.

5. Security & Privacy Considerations

Long Tasks API adheres to cross-origin policy by including origin-safe attribution information about the source of the long task. There is a 50ms threshold for long tasks. Together this provides adequate protection against security attacks against browser.

However, privacy related attacks are possible, while the API doesn’t introduce any new privacy attacks, it could make existing privacy attacks faster. Mitigations for this are possible and discussed in the security review in this document.

6. Acknowledgements

Special thanks to all the contributors for their technical input and suggestions that led to improvements to this specification.

Index

Terms defined by this specification

Terms defined by reference

References

Normative References

[HR-TIME-2]
Ilya Grigorik; James Simonsen; Jatinder Mann. High Resolution Time Level 2. URL: https://www.w3.org/TR/hr-time-2/
[PERFORMANCE-TIMELINE-2]
Ilya Grigorik; Jatinder Mann; Zhiheng Wang. Performance Timeline Level 2. URL: https://www.w3.org/TR/performance-timeline-2/
[WebIDL]
Cameron McCormack; Boris Zbarsky; Tobie Langel. Web IDL. URL: https://heycam.github.io/webidl/

IDL Index

interface PerformanceLongTaskTiming : PerformanceEntry {
    readonly attribute FrozenArray<TaskAttributionTiming> attribution;
};

interface TaskAttributionTiming : PerformanceEntry {
    readonly attribute DOMString containerType;
    readonly attribute DOMString containerSrc;
    readonly attribute DOMString containerId;
    readonly attribute DOMString containerName;
};