HTML Standard
11
Worklets
11.1
Introduction
11.1.1
Motivations
11.1.2
Code idempotence
11.1.3
Speculative evaluation
11.2
Examples
11.2.1
Loading scripts
11.2.2
Registering a class and invoking its methods
11.3
Infrastructure
11.3.1
The global scope
11.3.1.1
Agents and event loops
11.3.1.2
Creation and termination
11.3.1.3
Script settings for worklets
11.3.2
The
Worklet
class
11.3.3
The worklet's lifetime
11
Worklets
11.1
Introduction
This section is non-normative.
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
This section is non-normative.
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.
Additionally, 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:
Are thread-agnostic. That is, they are not designed to run on a dedicated separate thread,
like each worker is. Implementations can run worklets wherever they choose (including on the main
thread).
Are able to have multiple duplicate instances of the global scope created, for the purpose
of parallelism.
Do not use an event-based API. Instead, classes are registered on the global scope, whose
methods are invoked by the user agent.
Have a reduced API surface on the global scope.
Have a lifetime for their
global object
which is defined by other
specifications, often in an
implementation-defined
manner.
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
WorkletGlobalScope
s 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:
No reference to the global object is available (i.e., there is no counterpart to
self
on
WorkletGlobalScope
).
Although this was the intention when worklets were first specified, the
introduction of
globalThis
has made it no longer true. See
issue #6059
for more discussion.
Code is loaded as a
module script
, which results in the code being executed
in strict mode and with no shared
this
referencing the global
proxy.
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:
They must require user agents to always have at least two
WorkletGlobalScope
instances per
Worklet
, and randomly assign a method or set of methods on a class to
a particular
WorkletGlobalScope
instance. These specifications may provide an
opt-out under memory constraints.
These specifications must allow user agents to create and destroy instances of their
WorkletGlobalScope
subclasses at any time.
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 results 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
WorkletGlobalScope
s 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
This section is non-normative.
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
FakeWorkletGlobalScope
s:
partial
interface
Window
SameObject
SecureContext
readonly
attribute
Worklet
fakeWorklet1
SameObject
SecureContext
readonly
attribute
Worklet
fakeWorklet2
};
Each
Window
has two
Worklet
instances,
fake
worklet 1
and
fake worklet 2
. 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]
The
fakeWorklet1
getter steps are to return
this
's
fake worklet 1
The
fakeWorklet2
getter steps are to return
this
's
fake worklet 2
Global
=(
Worklet
FakeWorklet
),
Exposed
FakeWorklet
SecureContext
interface
FakeWorkletGlobalScope
WorkletGlobalScope
undefined
registerFake
DOMString
type
Function
classConstructor
);
};
Each
FakeWorkletGlobalScope
has a
registered class constructors map
which is an
ordered map
, initially empty.
The
registerFake(
type
classConstructor
method
steps are to set
this
's
registered class constructors
map
type
] to
classConstructor
11.2.1
Loading scripts
This section is non-normative.
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
WorkletGlobalScope
s 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
This section is non-normative.
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
Optionally,
create a worklet global scope
for
worklet
Let
workletGlobalScope
be one of
worklet
's
global scopes
, chosen in an
implementation-defined
manner.
Let
classConstructor
be
workletGlobalScope
's
registered class
constructors map
["
negation-processor
"].
Let
classInstance
be the result of
constructing
classConstructor
, with
no arguments.
Let
function
be
Get
classInstance
process
"). Rethrow any exceptions.
Let
callback
be the result of
converting
function
to a Web IDL
Function
instance.
Return the result of
invoking
callback
with « true » and "
rethrow
", and with
callback this value
set to
classInstance
Another, perhaps better, specification architecture would be to extract the "
process
" property and convert it into a
Function
at registration time, as part of the
registerFake()
method steps.
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.
Exposed
Worklet
SecureContext
interface
WorkletGlobalScope
{};
Other specifications are intended to subclass
WorkletGlobalScope
adding APIs to register a class, as well as other APIs specific for their worklet type.
Each
WorkletGlobalScope
has an associated
module map
. It is a
module map
initially empty.
11.3.1.1
Agents and event loops
This section is non-normative.
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.
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.1.2
Creation and termination
To
create a worklet global scope
for a
Worklet
worklet
Let
outsideSettings
be
worklet
's
relevant settings
object
Let
agent
be the result of
obtaining a
worklet agent
given
outsideSettings
. Run the rest of these steps in that
agent.
Let
realmExecutionContext
be the result of
creating a new realm
given
agent
and the following customizations:
For the global object, create a new object of the type given by
worklet
's
worklet global scope type
Let
workletGlobalScope
be the
global
object
of
realmExecutionContext
's Realm component.
Let
insideSettings
be the result of
setting up a worklet environment settings object
given
realmExecutionContext
and
outsideSettings
Let
pendingAddedModules
be a
clone
of
worklet
's
added modules
list
Let
runNextAddedModule
be the following steps:
If
pendingAddedModules
is not empty
, then:
Let
moduleURL
be the result of
dequeuing
from
pendingAddedModules
Fetch a worklet script graph
given
moduleURL
insideSettings
worklet
's
worklet destination type
what credentials mode?
insideSettings
worklet
's
module responses map
, and with
the following steps given
script
This will not actually perform a network request, as it will just reuse
responses
from
worklet
's
module responses map
. The main
purpose of this step is to create a new
workletGlobalScope
-specific
module
script
from the
response
Assert
script
is not null, since the fetch succeeded and
the source text was successfully parsed when
worklet
's
module responses map
was initially
populated with
moduleURL
Run a module script
given
script
Run
runNextAddedModule
Abort these steps.
Append
workletGlobalScope
to
outsideSettings
's
global
object
's
associated
Document
's
worklet
global scopes
Append
workletGlobalScope
to
worklet
's
global
scopes
Run the
responsible event loop
specified by
insideSettings
Run
runNextAddedModule
To
terminate a worklet global scope
given a
WorkletGlobalScope
workletGlobalScope
Let
eventLoop
be
workletGlobalScope
's
relevant agent
's
event loop
If there are any
tasks
queued in
eventLoop
's
task queues
, discard them without processing them.
Wait for
eventLoop
to complete the
currently running
task
If the previous step doesn't complete within an
implementation-defined
period
of time, then
abort the script
currently running in
the worklet.
Destroy
eventLoop
Remove
workletGlobalScope
from the
global scopes
of the
Worklet
whose
global scopes
contains
workletGlobalScope
Remove
workletGlobalScope
from the
worklet global scopes
of the
Document
whose
worklet global
scopes
contains
workletGlobalScope
11.3.1.3
Script settings for worklets
To
set up a worklet environment settings object
, given a
JavaScript
execution context
executionContext
and an
environment settings
object
outsideSettings
Let
origin
be a unique
opaque
origin
Let
inheritedAPIBaseURL
be
outsideSettings
's
API base
URL
Let
inheritedPolicyContainer
be a
clone
of
outsideSettings
's
policy container
Let
realm
be the value of
executionContext
's Realm
component.
Let
workletGlobalScope
be
realm
's
global object
Let
settingsObject
be a new
environment settings object
whose
algorithms are defined as follows:
The
realm execution context
Return
executionContext
The
module map
Return
workletGlobalScope
's
module map
The
API base URL
Return
inheritedAPIBaseURL
Unlike workers or other globals derived from a single resource, worklets have
no primary resource; instead, multiple scripts, each with their own URL, are loaded into the
global scope via
worklet.addModule()
. So this
API base URL
is rather unlike that of other globals. However, so far this doesn't matter, as no APIs
available to worklet code make use of the
API base URL
The
origin
Return
origin
The
has cross-site
ancestor
Return true.
The
policy container
Return
inheritedPolicyContainer
The
cross-origin
isolated capability
Return
TODO
The
time origin
Assert
: this algorithm is never called, because the
time origin
is not available in a worklet
context.
Set
settingsObject
's
id
to a new
unique opaque string,
creation URL
to
inheritedAPIBaseURL
top-level creation URL
to null,
top-level
origin
to
outsideSettings
's
top-level origin
target browsing context
to null, and
active service worker
to
null.
Set
realm
's [[HostDefined]] field to
settingsObject
Return
settingsObject
11.3.2
The
Worklet
class
Worklet
Support in all current engines.
Firefox
76+
Safari
14.1+
Chrome
65+
Opera
Edge
79+
Edge (Legacy)
Internet Explorer
No
Firefox Android
Safari iOS
Chrome Android
WebView Android
Samsung Internet
Opera Android
The
Worklet
class provides the capability to add module scripts into its
associated
WorkletGlobalScope
s. The user agent can then create classes registered on
the
WorkletGlobalScope
s and invoke their methods.
Exposed
Window
SecureContext
interface
Worklet
NewObject
Promise
undefined
addModule
USVString
moduleURL
optional
WorkletOptions
options
= {});
};
dictionary
WorkletOptions
RequestCredentials
credentials
= "same-origin";
};
Specifications that create
Worklet
instances must specify the following for a
given instance:
its
worklet global scope type
, which must be a Web IDL type that
inherits
from
WorkletGlobalScope
; and
its
worklet destination type
, which must be a
destination
, and is used when fetching
scripts.
await
worklet
addModule
moduleURL
[, {
credentials
}])
Worklet/addModule
Support in all current engines.
Firefox
76+
Safari
14.1+
Chrome
65+
Opera
Edge
79+
Edge (Legacy)
Internet Explorer
No
Firefox Android
Safari iOS
Chrome Android
WebView Android
Samsung Internet
Opera Android
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.
Worklet
has a
list
of
global scopes
, which contains
instances of the
Worklet
's
worklet global scope type
. It is initially
empty.
Worklet
has an
added modules
list
, which is a
list
of
URLs
, initially empty.
Access to this list should be thread-safe.
Worklet
has a
module
responses map
, which is an
ordered map
from
URLs
to
either "
fetching
" or
tuples
consisting of a
response
and either null, failure, or a
byte
sequence
representing the response body. This map is initially empty, and access to it
should be thread-safe.
The
added modules list
and
module responses map
exist to ensure that
WorkletGlobalScope
s created at different times get equivalent
module scripts
run in them, based on the same source text. This allows the
creation of additional
WorkletGlobalScope
s to be transparent to the author.
In practice, user agents are not expected to implement these data structures, and the
algorithms that consult them, using thread-safe programming techniques. Instead, when
addModule()
is called, user agents can fetch the module
graph on the main thread, and send the fetched source text (i.e., the important data contained in
the
module responses map
) to each
thread which has a
WorkletGlobalScope
Then, when a user agent
creates
a new
WorkletGlobalScope
for a given
Worklet
, it can simply send the map of
fetched source text and the list of entry points from the main thread to the thread containing
the new
WorkletGlobalScope
The
addModule(
moduleURL
options
method steps are:
Let
outsideSettings
be the
relevant settings object
of
this
Let
moduleURLRecord
be the result of
encoding-parsing a URL
given
moduleURL
, relative to
outsideSettings
If
moduleURLRecord
is failure, then return
a promise rejected with
SyntaxError
DOMException
Let
promise
be a new promise.
Let
workletInstance
be
this
Run the following steps
in parallel
If
workletInstance
's
global
scopes
is empty
, then:
Create a worklet global scope
given
workletInstance
Optionally,
create
additional
global scope instances given
workletInstance
, depending on the specific worklet in
question and its specification.
Wait for all steps of the
creation
process(es) — including those taking place within the
worklet
agents
— to complete, before moving on.
Let
pendingTasks
be
workletInstance
's
global scopes
's
size
Let
addedSuccessfully
be false.
For each
workletGlobalScope
of
workletInstance
's
global
scopes
queue a global task
on the
networking task source
given
workletGlobalScope
to
fetch a worklet script graph
given
moduleURLRecord
outsideSettings
workletInstance
's
worklet destination type
options
["
credentials
"],
workletGlobalScope
's
relevant settings object
workletInstance
's
module responses map
, and the following
steps given
script
Only the first of these fetches will actually perform a network request; the
ones for other
WorkletGlobalScope
s will reuse
responses
from
workletInstance
's
module responses map
If
script
is null, then:
Queue a global task
on the
networking task source
given
workletInstance
's
relevant global object
to perform the following
steps:
If
pendingTasks
is not −1, then:
Set
pendingTasks
to −1.
Reject
promise
with an
AbortError
DOMException
Abort these steps.
If
script
's
error to
rethrow
is not null, then:
Queue a global task
on the
networking task source
given
workletInstance
's
relevant global object
to perform the following
steps:
If
pendingTasks
is not −1, then:
Set
pendingTasks
to −1.
Reject
promise
with
script
's
error to rethrow
Abort these steps.
If
addedSuccessfully
is false, then:
Append
moduleURLRecord
to
workletInstance
's
added
modules list
Set
addedSuccessfully
to true.
Run a module script
given
script
Queue a global task
on the
networking task source
given
workletInstance
's
relevant global object
to perform the following
steps:
If
pendingTasks
is not −1, then:
Set
pendingTasks
to
pendingTasks
− 1.
If
pendingTasks
is 0, then resolve
promise
Return
promise
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
Each
Document
has a
worklet
global scopes
, which is a
set
of
WorkletGlobalScope
s, initially
empty.
The lifetime of a
WorkletGlobalScope
is, at a minimum, tied to the
Document
whose
worklet global
scopes
contain it. In particular,
destroying
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
WorkletGlobalScope
s for a
given worklet type. For example, they might create them during specific processes that call upon
worklet code, as in the
example