Introduction

<!DOCTYPE html>
<title>Scheduling background tasks using requestIdleCallback</title>
<script>
var requestId = 0;
var pointsTotal = 0;
var pointsInside = 0;

function piStep() {
  var r = 10;
  var x = Math.random() * r * 2 - r;
  var y = Math.random() * r * 2 - r;
  return (Math.pow(x, 2) + Math.pow(y, 2) < Math.pow(r, 2))
}
function refinePi(deadline) {
  while (deadline.timeRemaining() > 0) {
    if (piStep())
      pointsInside++;
    pointsTotal++;
  }
  currentEstimate = (4 * pointsInside / pointsTotal);
  textElement = document.getElementById("piEstimate");
  textElement.innerHTML="Pi Estimate: " + currentEstimate;
  requestId = window.requestIdleCallback(refinePi);
}
function start() {
  requestId = window.requestIdleCallback(refinePi);
}
function stop() {
  if (requestId)
    window.cancelIdleCallback(requestId);
  requestId = 0;
}
</script>
<button onclick="start()">Click me to start!</button>
<button onclick="stop()">Click me to stop!</button>
<div id="piEstimate">Not started</div>

Idle Periods

After input processing, rendering and compositing for a given frame has been completed, the user agent's main thread often becomes idle until either: the next frame begins; another pending task becomes eligible to run; or user input is received. This specification provides a means to schedule execution of callbacks during this otherwise idle time via a `requestIdleCallback` API.

Callbacks posted via the `requestIdleCallback` API become eligible to run during user agent defined idle periods. When an idle callback is run it will be given a deadline which corresponds to the end of the current idle period. The decision as to what constitutes an idle period is user agent defined, however the expectation is that they occur in periods of quiescence where the browser expects to be idle.

One example of an idle period is the time between committing a given frame to the screen and starting processing on the next frame during active animations, as shown in . Such idle periods will occur frequently during active animations and screen updates, but will typically be very short (i.e., less than 16ms for devices with a 60Hz vsync cycle).

Example of an inter-frame idle period.
Example of an inter-frame idle period

The web-developer should be careful to account for all work performed by operations during an idle callback. Some operations, such as resolving a promise or triggering a page layout, may cause subsequent tasks to be scheduled to run after the idle callback has finished. In such cases, the application should account for this additional work by yielding before the deadline expires to allow these operations to be performed before the next frame deadline.

Another example of an idle period is when the user agent is idle with no screen updates occurring. In such a situation the user agent may have no upcoming tasks with which it can bound the end of the idle period. In order to avoid causing user-perceptible delays in unpredictable tasks, such as processing of user input, the length of these idle periods should be capped to a maximum value of 50ms. Once an idle period is finished the user agent can schedule another idle period if it remains idle, as shown in , to enable background work to continue to occur over longer idle time periods.

Example of an idle period when there are no pending frame updates.
Example of an idle period when there are no pending frame updates

During an idle period the user agent will run idle callbacks in FIFO order until either the idle period ends or there are no more idle callbacks eligible to be run. As such, the user agent will not necessarily run all currently posted idle callbacks within a single idle period. Any remaining idle tasks are eligible to run during the next idle period.

Only idle tasks which posted before the start of the current idle period are eligible to be run during the current idle period. As a result, if an idle callback posts another callback using `requestIdleCallback`, this subsequent callback won't be run during the current idle period. This enables idle callbacks to re-post themselves to be run in a future idle period if they cannot complete their work by a given deadline - i.e., allowing code patterns like the following example, without causing the callback to be repeatedly executed during a too-short idle period:

function doWork(deadline) {
  if (deadline.timeRemaining() <= 5) {
    // This will take more than 5ms so wait until we
    // get called back with a long enough deadline.
    requestIdleCallback(doWork);
    return;
  }
  // do work...
}

At the start of the next idle period newly posted idle callbacks are appended to the end of the runnable idle callback list, thus ensuring that reposting callbacks will be run round-robin style, with each callback getting a chance to be run before that of an earlier task's reposted callback.

When the user agent determines that the web page is not user visible it can throttle idle periods to reduce the power usage of the device, for example, only triggering an idle period every 10 seconds rather than continuously.

A final subtlety to note is that there is no guarantee that a user agent will have any idle CPU time available during heavy page load. As such, it is entirely acceptable that the user agent does not schedule any idle period, which would result in the idle callbacks posted via the `requestIdleCallback` API being postponed for a potentially unbounded amount of time. For cases where the author prefers to execute the callback within an idle period, but requires a time bound within which it can be executed, the author can provide the `timeout` property in the `options` argument to `requestIdleCallback`: if the specified timeout is reached before the callback is executed within an idle period, a task is queued to execute it.

The maximum deadline of 50ms is derived from studies [[RESPONSETIME]] which show that that a response to user input within 100ms is generally perceived as instantaneous to humans. Capping idle deadlines to 50ms means that even if the user input occurs immediately after the idle task has begun, the user agent still has a remaining 50ms in which to respond to the user input without producing user perceptible lag.

The IDL fragments in this specification MUST be interpreted as required for conforming IDL fragments, as described in the Web IDL specification. [[!WEBIDL]]

This specification defines a single conformance class:

conforming user agent
A user agent is considered to be a conforming user agent if it satisfies all of the MUST-, REQUIRED- and SHALL-level criteria in this specification. A conforming user agent must also be a [conforming implementation] of the IDL fragment in section , as described in the Web IDL specification [[!WEBIDL]].

`Window` interface extensions

The partial interface in the IDL fragment below is used to expose the `requestIdleCallback` operation on the `[Window]` object. [[!HTML5]]

partial interface Window {
  unsigned long requestIdleCallback(IdleRequestCallback callback, optional IdleRequestOptions options);
  void cancelIdleCallback(unsigned long handle);
};

dictionary IdleRequestOptions {
  unsigned long timeout;
};

interface IdleDeadline {
  DOMHighResTimeStamp timeRemaining();
  readonly attribute boolean didTimeout;
};

callback IdleRequestCallback = void (IdleDeadline deadline);

Each `[Window]` has:

The `requestIdleCallback` method

When `requestIdleCallback(callback, options)` is invoked, the user agent MUST run the following steps:

  1. Let window be this `Window` object.
  2. Increment the `window`'s idle callback identifier by one.
  3. Let handle be window's idle callback identifier current value.
  4. Append callback to `window`'s list of idle request callbacks, associated with handle.
  5. Return handle and then continue running this algorithm asynchronously.
  6. If the timeout property is present in options and has a positive value:
    1. Wait for timeout milliseconds.
    2. Wait until all invocations of this algorithm, whose timeout added to their posted time occurred before this one's, have completed.
    3. Optionally, wait a further user-agent defined length of time.

      This is intended to allow user agents to pad timeouts as needed to optimise the power usage of the device. For example, some processors have a low-power mode where the granularity of timers is reduced; on such platforms, user agents can slow timers down to fit this schedule instead of requiring the processor to use the more accurate mode with its associated higher power usage.

    4. [Queue a task] on the queue associated with the idle-task [task source], which performs the invoke idle callback timeout algorithm, passing handle and window as arguments.

`requestIdleCallback` only schedules a single callback, which will be executed during a single idle period. If the callback cannot complete its work before the given deadline then it should call `requestIdleCallback` again (which may be done from within the callback) to schedule a future callback for the continuation of its task, and exit immediately to return control back to the [event loop].

The `cancelIdleCallback` method

The `cancelIdleCallback` method is used to cancel a previously made request to schedule an idle callback. When `cancelIdleCallback(handle)` is invoked, the user agent MUST run the following steps:

  1. Let window be this `Window` object.
  2. Find the entry in either the `window`'s list of idle request callbacks or list of runnable idle callbacks that is associated with the value handle.
  3. If there is such an entry, remove it from both `window`'s list of idle request callbacks and the list of runnable idle callbacks.

`cancelIdleCallback` might be invoked for an entry in window's list of idle request callbacks or the list of runnable idle callbacks. In either case the entry should be removed from the list so that the callback does not run.

The `timeRemaining` method

When the `timeRemaining()` method is invoked on an `IdleDeadline` object it MUST return the amount of time remaining before the callback's deadline as a DOMHighResTimeStamp. The value SHOULD be accurate to 5 microseconds - see "Privacy and Security" section of [[!HR-TIME]]. This value is calculated by performing the following steps:

  1. Let deadline be the deadline of the associated callback as a `[DOMHighResTimeStamp]`. [[!HR-TIME-2]]
  2. Let now be the value returned by `[performance.now()]`.
  3. Let timeRemaining be deadline - now.
  4. If timeRemaining is negative, set it to 0.
  5. Return timeRemaining.

The `didTimeout` attribute on an `IdleDeadline` object MUST return `true` if the callback was invoked by the invoke idle callback timeout algorithm, and `false` otherwise.

Processing

Start an event loop's idle period

Whenever the user agent assesses that a given [event loop] is likely to remain idle for a non-trivial amount of time, and that background work could be executed on this event loop without impacting any high priority work occurring on other event-loops, it SHOULD initiate a new idle period for the [event loop].

The expectation is that the user agent will initiate idle periods regularly when the event loop becomes idle, for example, in between frame rendering and regularly during times when no frames are being rendered. If the `Document`'s `[hidden](pv-hidden)` attribute ([[PAGE-VISIBILITY]]) is `true` then the user agent can throttle idle period generation, for example limiting the Document to one idle period every 10 seconds to optimize for power usage.

When the user agent wishes to start an [event loop]'s idle period, the following steps MUST be performed:

  1. Let last_deadline be the last idle period deadline associated with the current event loop, or 0 if no previous deadline exists.
  2. If last_deadline is greater than the current time:
    1. Wait until the current time is greater than or equal to last_deadline.
  3. Let now be the current time.
  4. Let deadline be a time in the future until which the browser expects to remain idle.
  5. If deadline - now is greater than 50ms, then cap deadline by setting it to be now + 50ms.
  6. Let docs be the list of [fully active] `Document` objects associated with the event loop in question.
  7. For every document in docs perform the following steps:
    1. Let doclist be document's `Window` object's list of idle request callbacks.
    2. Let runlist be document's `Window` object's list of runnable idle callbacks.
    3. Append all entries from doclist into runlist preserving order.
    4. Clear doclist.
  8. [Queue a task] which performs the steps defined in the invoke idle callbacks algorithm with deadline as parameter.
  9. Save deadline as the last idle period deadline associated with the current event loop.

The [task source] for these [tasks](task) is the idle-task task source.

The time between now and deadline is referred to as the idle period. There can only be one idle period active at a given time. The idle period can end early if the user agent determines that it is no longer idle. If so, the next idle period cannot start until after deadline.

Also note, the expectation is that the user agent will choose deadline to ensure that no time-critical tasks will be delayed even if a callback runs for the whole time period from now to deadline. As such, it should be set to the minimum of: the closest timeout in the [list of active timers] as set via `[setTimeout]` and `[setInterval]`; the scheduled runtime for pending animation callbacks posted via `[requestAnimationFrame]`; pending internal timeouts such as deadlines to start rendering the next frame, process audio or any other internal task the user agent deems important; and a maximum cap of 50ms in the future to ensure responsiveness to unpredictable user input within the threshold of human perception.

Invoke idle callbacks algorithm

The invoke idle callbacks algorithm:

  1. Let docs be the list of [fully active] `Document` objects whose `Window` object's list of runnable idle callbacks is not empty.
  2. Let now be the current time.
  3. If now is less than deadline:
    1. Select any document from docs.
    2. Pop callback from document's `Window` object's list of runnable idle callbacks.
    3. If document's `Window` object's list of runnable idle callbacks is now empty, remove document from docs.
    4. Let deadlineArg be an `IdleDeadline` constructed with the given deadline and the `didTimeout` attribute set to `false`.
    5. Call callback with deadlineArg as its argument. If an uncaught runtime script error occurs, then [report the error].
    6. If docs is not empty, the user agent SHOULD [queue a task] which performs the steps in the invoke idle callbacks algorithm with deadline as parameter.

The user agent is free to end an idle period early, even if deadline has not yet occurred, by not executing the last step of the above algorithm. For example, the user agent may decide to do this if it determines that higher priority work has become runnable.

Invoke idle callback timeout algorithm

The invoke idle callback timeout algorithm:

  1. Let callback be the result of finding the entry in window's list of idle request callbacks or the list of runnable idle callbacks that is associated with the value given by the handle argument passed to the algorithm.
  2. If callback is not undefined:
    1. Remove callback from both window's list of idle request callbacks and the list of runnable idle callbacks.
    2. Let now be the current time.
    3. Let deadlineArg be an `IdleDeadline` constructed with a deadline of now and the `didTimeout` attribute set to `true`.
    4. Call callback with deadlineArg as its argument. If an uncaught runtime script error occurs, then [report the error].

Privacy and Security

TODO

Acknowledgments

The editors would like to thank the following people for contributing to this specification: Sami Kyostila, Alex Clarke, Boris Zbarsky, Marcos Caceres, Jonas Sicking, Robert O'Callahan, David Baron, Todd Reifsteck, Tobin Titus, Elliott Sprehn, Tetsuharu OHZEKI, Lon Ingram, Domenic Denicola and Philippe Le Hegaret.

[setTimeout]: http://www.w3.org/TR/html5/webappapis.html#dom-windowtimers-settimeout [setInterval]: http://www.w3.org/TR/html5/webappapis.html#dom-windowtimers-setinterval [conforming implementation]: http://www.w3.org/TR/WebIDL/#dfn-conforming-implementation [Window]: http://www.w3.org/TR/html5/browsers.html#the-window-object [queue a task]: http://www.w3.org/TR/html5/webappapis.html#queue-a-task [task]: http://www.w3.org/TR/2011/WD-html5-20110525/webappapis.html#concept-task [task source]: http://www.w3.org/TR/2011/WD-html5-20110525/webappapis.html#task-source [event loop]: http://www.w3.org/TR/html5/webappapis.html#event-loop [DOMHighResTimestamp]: http://www.w3.org/TR/hr-time-2/#dom-domhighrestimestamp [performance.now()]: http://www.w3.org/TR/hr-time-2/#widl-Performance-now-DOMHighResTimeStamp [pv-hidden]: http://www.w3.org/TR/page-visibility/#dom-document-hidden [fully active]: http://www.w3.org/TR/html5/browsers.html#fully-active [list of active timers]: http://www.w3.org/TR/html5/webappapis.html#list-of-active-timers [requestAnimationFrame]: http://www.w3.org/TR/animation-timing/#dom-windowanimationtiming-requestanimationframe [report the error]: http://www.w3.org/TR/html5/webappapis.html#report-the-error