Which types of queues are in event loop?

From the HTML specs, the most important point being made about this matter is in the task-source definition where we can read:

Per its source field, each task is defined as coming from a specific task source. For each event loop, every task source must be associated with a specific task queue.

Note

Essentially, task sources are used within standards to separate logically-different types of tasks, which a user agent might wish to distinguish between. Task queues are used by user agents to coalesce task sources within a given event loop.

Example

For example, a user agent could have one task queue for mouse and key events (to which the user interaction task source is associated), and another to which all other task sources are associated. Then, using the freedom granted in the initial step of the event loop processing model, it could give keyboard and mouse events preference over other tasks three-quarters of the time, keeping the interface responsive but not starving other task queues. Note that in this setup, the processing model still enforces that the user agent would never process events from any one task source out of order.

And in the task queue definition:

An event loop has one or more task queues. A task queue is a set of tasks.

To summarize all this, the specs only require that every event loop has at least one task queue. They won’t get into more specification about the task queues, but rather use task sources, that the user agents (UA) will link to whatever task queue they wish however they see fit, as long as the tasks in each task source are executed in order.

There are many task sources being used across the HTML specs, for instance here is a list of the generic ones, but every specs can define there own, just like the Message API, where each MessagePort object will have its own task source! (meaning UAs may map each of these message tasks source to different task queues).

Getting an extensive list of all the task sources is not really possible, and not really useful since they could actually all end up in the same and only task queue.


An other very important part of the specs we need to look at is the event loop processing model.

This algorithm defines all the steps an event loop should go through at each iteration.
It’s a bit complex at first sight, and it’s not simplifying over time since more and more contexts are adhering to this model, with very different issues to deal with (looking at you OffscreenCanvas in Worker…).

But, for what we are interested in here, let’s pretend it’s simple and that we are in a Window context.

The first step is kind of where the task prioritization is being designed by the specs:

Let taskQueue be one of the event loop’s task queues, chosen in an implementation-defined manner […]

Just here, they say to UAs that they have the power to choose from which task queue to pick the next task from. This means that for example, if they have a dedicated task queue for the user interaction task source, and an other just for the networking task source, and that both contain tasks waiting, then they are free to choose whichever they prefer to run first.

Now regarding the rendering, if we look how the processing model goes after this task is executed, all microtasks are too and a bunch of measurements are made, we see that at step 11, they should update the rendering. That’s actually part of all event loop’s iterations (in a Window context), but the first steps of this algorithm is to define if this was indeed a rendering frame or not, meaning that most of the time it will just early exit without doing anything.
When it is a rendering frame though, it has to perform all the rendering steps, as part of the event loop iteration, i.e maybe after a normal task has been executed.
So from the specs point of view, we can’t really talk about prioritization here, it’s just one part of every event loop iteration, just like the microtask checkpoint, it doesn’t go back to the step 1 where they can pick what task to execute, they are forced to execute that part of the event loop. Though technically, in the rendering steps the UA is also the one responsible to determine when it has a “rendering opportunity”, so if they want, they can actually set up that prioritization.

So for us web-authors, that means that yes, we can have a requestAnimationFrame callback fire before a setTimeout(fn, 0) one (or any other task), at least in the case that the one task that did call this requestAnimationFrame() method was itself executed at the beginning of a rendering frame.

This may actually happen quite often, here is a small snippet that should make it happen in Firefox quite reliably, and sometimes in Chrome:

/*
  In latest FF and Chrome browsers, UI events like mousemove are being throttled by the UA.
  (as recommended by the specs)
  They make the limit rate match the one of the screen-refresh, like resize or scroll events.
  However, at least in Firefox they don't make it part of the 'update the rendering' algorithm,
  but execute it as a normal task.
  So a 'mousemove' event in this browser actually gives us accesss to the first step of a 'rendering frame'.
  
  This simple snippet tries to demonstrate that,
  if successful, "rAF" should always be logged first.
*/
onmousemove = (evt) => {
  console.clear();
  setTimeout( () => console.log( 'timeout' ), 0 );
  requestAnimationFrame( () => console.log( 'rAF' ) );
};
move your mouse over the frame

Now we can answer all your points:

1) render callback is given the highest priority.
Is it true?

As we just demonstrated, not really, even though it may happen render callbacks are executed in the same event-loop iteration as the task that did schedule them, we can’t really talk about prioritization here.

2) Does render queue exist as separate queue or is it alias for render callbacks?

Specs, don’t define any special task queue, but there is no task source for rendering either. The update the rendering algorithm is made of a lot of different tasks to execute though, and in there lies the run the animation frame callbacks command, that you might be referring to. But these callbacks are stored in a Map, not in a queue.

3) Which callbacks are render? As I take in any repaint is render callback

It’s all in update the rendering, but you may want to focus on what comes after step 5, since before is just some checks.

4) Are there any other types of queues and if there are where can I read about them?

As already said, specs don’t define task queues, and task sources are too loosely defined to give a complete list.

But remember that all this was from the point of view of the specs. Implementers may deviate from this model in many ways, and the model is itself loose enough to allow a whole bunch of different setups.

As an example, I’d like to point you to a Firefox issue, which introduces a very interesting case:
They wanted to have setTimeout callbacks have less priority than other tasks, but only at page load. For this, they did create a new task queue, just for this case.
And now, in Firefox, setTimeout callbacks have less priority than other tasks, but only at page load:

function test() {
  setTimeout( () => console.log('timeout'));
  onmessage = (evt) => console.log('message');
  postMessage('', '*');
}
console.log('at page load');
test();

setTimeout( () => {
  console.log('after page load');
  test();
}, 1000 );

/* results in Firefox:
at page load
message
timeout
after page load
timeout
message

Chrome will always print "message" first, but because they have a 2ms min timeout on setTimeout
*/

And an other thing to keep in sight is the maybe incoming Prioritized postTask API, with which we can already play in Chrome under the #enable-experimental-web-platform-features flag, and which exposes a scheduler.postTask(fn, priority) method, which allows us to control the task prioritization.

Leave a Comment