Microtasks
According the HTML spec, a microtask is: "a colloquial way of referring to a task that was created via the queue a microtask algorithm"(source). Each event-loop--meaning window, worker, or worklet--has it's own microtask queue. The tasks queued on it are run as part of the perform a microtask checkpoint algorithm, which is called into from various places, the main one being after running a task from an task queue that isn't the microtask queue, and each call to this algorithm drains the microtask queue--running all tasks that have been enqueued up to that point(without re-entrancy).
The microtask queue in Servo
The MicroTaskQueue
is a straightforward implementation based on the spec: a list of tasks and a boolean to prevent re-entrancy at the checkpoint.
One is created for each runtime, matching the spec since a runtime is created per event-loop.
For a window event-loop, which can contain multiple window objects, the queue is shared among all GlobalScope
it contains.
Dedicated workers use a child runtime, but that one still comes with its own microtask queue.
Microtask queueing
A task can be enqueued on the microtask queue from both Rust, and from the JS engine.
- From JS: the JS engine will call into
enqueue_promise_job
whenever it needs to queue a microtask to call into promise handlers. This callback mechanism is setup once per runtime. This means that resolving a promise, either from Rust or from JS, will result in this callback being called into, and a microtask being enqueued. Strictly speaking, the microtask is still enqueued from Rust. - From Rust, there are various places from which microtask are explicitly enqueued by "native" Rust:
- To implement the await a stable state algorithm, via the script-thread, apparently only via the script-thread, meaning worker event-loop never use this algorithm.
- To implement the dom-queuemicrotask algorithm, both on window and worker event-loops.
- And various other places in the DOM, which can all be traced back to the variants of
Microtask
- A microtask can only ever be enqueued from steps running on a task itself, never from steps running "in-parallel" to an event-loop.
Running Microtask Checkpoints
The perform-a-microtask-checkpoint corresponds to MicrotaskQueue::checkpoint
, and is called into at multiple points:
- In the parser, when encountering a
script
tag as part of tokenizing. This corresponds to #parsing-main-incdata:perform-a-microtask-checkpoint. - Again in the parser, ss part of creating an element. This corresponds to #creating-and-inserting-nodes:perform-a-microtask-checkpoint.
- As part of cleaning-up after running a script. This corresponds to #calling-scripts:perform-a-microtask-checkpoint.
- At two points(one, two) in the
CustomElementRegistry
, the spec origin of these calls is unclear: it appears to be "clean-up after script", but there are no reference to this in the parts of the spec that the methods are documented with. - In a worker event-loop, as part of step 2.8 of the event-loop-processing-model
- In two places(one, two) in a window event-loop(the
ScriptThread
), again as part of step 2.8 of the event-loop-processing-model. This needs to consolidated into one call, and what is a "task" needs to be clarified(TODO(#32003)). - Our paint worklet implementation does not seem to run this algorithm yet.