1. 11 Worklets
    1. 11.1 Introduction
      1. 11.1.1 Motivations
      2. 11.1.2 Code idempotence
      3. 11.1.3 Speculative evaluation
    2. 11.2 Examples
      1. 11.2.1 Loading scripts
      2. 11.2.2 Registering a class and invoking its methods
    3. 11.3 Infrastructure
      1. 11.3.1 The global scope
        1. 11.3.1.1 Agents and event loops
      2. 11.3.2 The Worklet class
      3. 11.3.3 The worklet's lifetime

11 Worklets

11.1 Introduction

Worklets are a piece of specification infrastructure which can be used for running scripts independent of the main JavaScript execution environment, while not requiring any particular implementation model.

The worklet infrastructure specified here cannot be used directly by web developers. Instead, other specifications build upon it to create directly-usable worklet types, specialized for running in particular parts of the browser implementation pipeline.

11.1.1 Motivations

Allowing extension points to rendering, or other sensitive parts of the implementation pipeline such as audio output, is difficult. If extension points were done with full access to the APIs available on Window, engines would need to abandon previously-held assumptions for what could happen in the middle of those phases. For example, during the layout phase, rendering engines assume that no DOM will be modified.

Additionaly, defining extension points in the Window environment would restrict user agents to performing work in the same thread as the Window object. (Unless implementations added complex, high-overhead infrastructure to allow thread-safe APIs, as well as thread-joining guarantees.)

Worklets are designed to allow extension points, while keeping guarantees that user agents currently rely on. This is done through new global environments, based on subclasses of WorkletGlobalScope.

Worklets are similar to web workers. However, they:

As worklets have relatively high overhead, they are best used sparingly. Due to this, a given WorkletGlobalScope is expected to be shared between multiple separate scripts. (This is similar to how a single Window is shared between multiple separate scripts.)

Worklets are a general technology that serve different use cases. Some worklets, such as those defined in CSS Painting API, provide extension points intended for stateless, idempotent, and short-running computations, which have special considerations as described in the next couple of sections. Others, such as those defined in Web Audio API, are used for stateful, long-running operations. [CSSPAINT] [WEBAUDIO]

11.1.2 Code idempotence

Some specifications which use worklets are intended to allow user agents to parallelize work over multiple threads, or to move work between threads as required. In these specifications, user agents might invoke methods on a web-developer-provided class in an implementation-defined order.

As a result of this, to prevent interoperability issues, authors who register classes on such WorkletGlobalScopes should make their code idempotent. That is, a method or set of methods on the class should produce the same output given a particular input.

This specification uses the following techniques in order to encourage authors to write code in an idempotent way:

Together, these restrictions help prevent two different scripts from sharing state using properties of the global object.

Additionally, specifications which use worklets and intend to allow implementation-defined behavior must obey the following:

11.1.3 Speculative evaluation

Some specifications which use worklets can invoke methods on a web-developer-provided class based on the state of the user agent. To increase concurrency between threads, a user agent may invoke a method speculatively, based on potential future states.

In these specifications, user agents might invoke such methods at any time, and with any arguments, not just ones corresponding to the current state of the user agent. The reuslts of such speculative evaluations are not displayed immediately, but can be cached for use if the user agent state matches the speculated state. This can increase the concurrency between the user agent and worklet threads.

As a result of this, to prevent interoperability risks between user agents, authors who register classes on such WorkletGlobalScopes should make their code stateless. That is, the only effect of invoking a method should be its result, and not any side effects such as updating mutable state.

The same techniques which encourage code idempotence also encourage authors to write stateless code.

11.2 Examples

For these examples, we'll use a fake worklet. The Window object provides two Worklet instances, which each run code in their own collection of FakeWorkletGlobalScopes:

window . fakeWorklet1
Returns one of the fake worklets.
window . fakeWorklet2
Returns another of the fake worklets.

Both of these have their worklet global scope type set to FakeWorkletGlobalScope, and their worklet destination type set to "fakeworklet". User agents should create at least two FakeWorkletGlobalScope instances per worklet.

"fakeworklet" is not actually a valid destination per Fetch. But this illustrates how real worklets would generally have their own worklet-type-specific destination. [FETCH]


Inside a FakeWorkletGlobalScope, the following global method is available:

registerFake(type, classConstructor)
Registers the JavaScript class given by classConstructor for use when the user agent later wants to do some operation specified by type.

11.2.1 Loading scripts

To load scripts into fake worklet 1, a web developer would write:

window.fakeWorklet1.addModule('script1.mjs');
window.fakeWorklet1.addModule('script2.mjs');

Note that which script finishes fetching and runs first is dependent on network timing: it could be either script1.mjs or script2.mjs. This generally won't matter for well-written scripts intended to be loaded in worklets, if they follow the suggestions about preparing for speculative evaluation.

If a web developer wants to perform a task only after the scripts have successfully run and loaded into some worklets, they could write:

Promise.all([
    window.fakeWorklet1.addModule('script1.mjs'),
    window.fakeWorklet2.addModule('script2.mjs')
]).then(() => {
    // Do something which relies on those scripts being loaded.
});

Another important point about script-loading is that loaded scripts can be run in multiple WorkletGlobalScopes per Worklet, as discussed in the section on code idempotence. In particular, the specification above for fake worklet 1 and fake worklet 2 require this. So, consider a scenario such as the following:

// script.mjs
console.log("Hello from a FakeWorkletGlobalScope!");
// app.mjs
window.fakeWorklet1.addModule("script.mjs");

This could result in output such as the following from a user agent's console:

[fakeWorklet1#1] Hello from a FakeWorkletGlobalScope!
[fakeWorklet1#4] Hello from a FakeWorkletGlobalScope!
[fakeWorklet1#2] Hello from a FakeWorkletGlobalScope!
[fakeWorklet1#3] Hello from a FakeWorkletGlobalScope!

If the user agent at some point decided to kill and restart the third instance of FakeWorkletGlobalScope, the console would again print [fakeWorklet1#3] Hello from a FakeWorkletGlobalScope! when this occurs.

11.2.2 Registering a class and invoking its methods

Let's say that one of the intended usages of our fake worklet by web developers is to allow them to customize the highly-complex process of boolean negation. They might register their customization as follows:

// script.mjs
registerFake('negation-processor', class {
  process(arg) {
    return !arg;
  }
});
// app.mjs
window.fakeWorklet1.addModule("script.mjs");

To make use of such registered classes, the specification for fake worklets could define a find the opposite of true algorithm, given a Worklet worklet, which invokes the process method on any class registered to one of worklet's global scopes as having type "negation-processor", with true as the argument, and then uses the result in some way.

11.3 Infrastructure

11.3.1 The global scope

Subclasses of WorkletGlobalScope are used to create global objects wherein code loaded into a particular Worklet can execute.

Other specifications are intended to subclass WorkletGlobalScope, adding APIs to register a class, as well as other APIs specific for their worklet type.

11.3.1.1 Agents and event loops

Each WorkletGlobalScope is contained in its own worklet agent, which has its corresponding event loop. However, in practice, implementation of these agents and event loops is expected to be different from most others.

A worklet agent exists for each WorkletGlobalScope since, in theory, an implementation could use a separate thread for each WorkletGlobalScope instance, and allowing this level of parallelism is best done using agents. However, because their [[CanBlock]] value is false, there is no requirement that agents and threads are one-to-one. This allows implementations the freedom to execute scripts loaded into a worklet on any thread, including one running code from other agents with [[CanBlock]] of false, such as the thread of a similar-origin window agent ("the main thread"). Contrast this with dedicated worker agents, whose true value for [[CanBlock]] effectively requires them to get a dedicated operating system thread.

Worklet event loops are also somewhat special. They are only used for tasks associated with addModule(), tasks wherein the user agent invokes author-defined methods, and microtasks. Thus, even though the event loop processing model specifies that all event loops run continuously, implementations can achieve observably-equivalent results using a simpler strategy, which just invokes author-provided methods and then relies on that process to perform a microtask checkpoint.

11.3.2 The Worklet class

The Worklet class provides the capability to add module scripts into its associated WorkletGlobalScopes. The user agent can then create classes registered on the WorkletGlobalScopes and invoke their methods.

Specifications that create Worklet instances must specify the following for a given instance:

await worklet . addModule(moduleURL[, { credentials }])

Loads and executes the module script given by moduleURL into all of worklet's global scopes. It can also create additional global scopes as part of this process, depending on the worklet type. The returned promise will fulfill once the script has been successfully loaded and run in all global scopes.

The credentials option can be set to a credentials mode to modify the script-fetching process. It defaults to "same-origin".

Any failures in fetching the script or its dependencies will cause the returned promise to be rejected with an "AbortError" DOMException. Any errors in parsing the script or its dependencies will cause the returned promise to be rejected with the exception generated during parsing.

11.3.3 The worklet's lifetime

The lifetime of a Worklet has no special considerations; it is tied to the object it belongs to, such as the Window.

The lifetime of a WorkletGlobalScope is, at a minimum, tied to the Document whose worklet global scopes contain it. In particular, discarding the Document will terminate the corresponding WorkletGlobalScope and allow it to be garbage-collected.

Additionally, user agents may, at any time, terminate a given WorkletGlobalScope, unless the specification defining the corresponding worklet type says otherwise. For example, they might terminate them if the worklet agent's event loop has no tasks queued, or if the user agent has no pending operations planning to make use of the worklet, or if the user agent detects abnormal operations such as infinite loops or callbacks exceeding imposed time limits.

Finally, specifications for specific worklet types can give more specific details on when to create WorkletGlobalScopes for a given worklet type. For example, they might create them during specific processes that call upon worklet code, as in the example.