Living Standard — Last Updated 7 March 2021
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.
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]
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.
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.
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
.
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.
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 the arguments « true » and with classInstance as the callback this value .
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.
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.
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.
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
.
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 JavaScript 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 .
Initialize a global object's CSP list given workletGlobalScope . [CSP]
For each moduleURL of worklet 's added modules list :
Fetch a worklet script graph given moduleURL , insideSettings , worklet 's worklet destination type , what credentials mode? , insideSettings , and worklet 's module responses map . Wait until the algorithm asynchronously completes with 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 .
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 .
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
.
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 inheritedReferrerPolicy be outsideSettings 's referrer policy .
Let inheritedEmbedderPolicy be outsideSettings 's embedder policy .
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:
Return executionContext .
Return workletGlobalScope 's module map .
Not applicable (the responsible event loop is not a window event loop ).
Return UTF-8 .
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
.
Return origin .
Return inheritedReferrerPolicy .
Return inheritedEmbedderPolicy .
Return TODO .
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 .
Worklet
class
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
}])
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.
A
Worklet
has
a
list
of
global
scopes
,
which
contains
instances
of
the
Worklet
's
worklet
global
scope
type
.
It
is
initially
empty.
A
Worklet
has
an
added
modules
list
,
which
is
a
list
of
URLs
,
initially
empty.
Access
to
this
list
should
be
thread-safe.
A
Worklet
has
a
module
responses
map
,
which
is
an
ordered
map
from
URLs
to
responses
,
initially
empty.
Access
to
this
map
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 .
Parse moduleURL relative to outsideSettings .
If
this
fails,
then
return
a
promise
rejected
with
a
"
SyntaxError
"
DOMException
.
Let moduleURLRecord be the resulting URL record .
Let promise be a new promise.
Run the following steps in parallel :
If this 's global scopes is empty , then:
Create a worklet global scope given this .
Optionally, create additional global scope instances given this , 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 this 's global scopes 's size .
Let addedSuccessfully be false.
For each workletGlobalScope of this 's global scopes , queue a global task on the networking task source given workletGlobalScope to perform the following steps:
Fetch
a
worklet
script
graph
given
moduleURLRecord
,
outsideSettings
,
this
's
worklet
destination
type
,
options
["
credentials
"],
workletGlobalScope
's
relevant
settings
object
,
and
this
's
module
responses
map
.
Wait
until
the
algorithm
asynchronously
completes
with
script
.
Only
the
first
of
these
fetches
will
actually
perform
a
network
request;
the
ones
for
other
WorkletGlobalScope
s
will
reuse
reuse
responses
from
this
's
module
responses
map
.
If script is null, then:
Queue a global task on the networking task source given this '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 this '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 this 's added modules list .
Set addedSuccessfully to true.
Run a module script given script .
Queue a global task on the networking task source given this '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 .
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,
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
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
.