1

Given the following script:

console.log("start of hard script");
const start = performance.now();
setTimeout(() => console.log('setTimeout'),0);
document.addEventListener("DOMContentLoaded", () => {
  console.log('fired event DOMContentLoaded')
});
document.addEventListener("click" , () => {
  console.log("fired event click")
});
while(start + 1000 > performance.now());
console.log("end of hard script")

I am sure I read somewhere that the user interaction queue will be more prioritized than the timer queue.

I wanted to see how that priority is defined, but saw in the specs:

Let taskQueue be one of the event loop's task queues, chosen in an implementation-defined manner, with the constraint that the chosen task queue must contain at least one runnable task. If there is no such task queue, then jump to the microtasks step below.

If WHATWG doesn't define a concrete order of queues, I'd like to know by what criteria implementors run? How do they evaluate the "important-ness" of queues? And in the end I'd like to see an example that shows the order of these queues, if it is possible.

Kaiido
  • 123,334
  • 13
  • 219
  • 285
MaximPro
  • 563
  • 8
  • 21
  • There is a similar question what also well-answered https://stackoverflow.com/questions/53627773/which-types-of-queues-are-in-event-loop – MaximPro Jan 30 '22 at 21:15

1 Answers1

2

I'd like to know by what criteria implementors run? How do they evaluate the "important-ness" of queues?

That's basically their call, a design choice made from years of experience looking at how their tool is being used and what should be prioritized (and also probably a good part of common sense).

The WHATWG indeed doesn't define at all how this task prioritization should be implemented. All they do is to define various task-sources (not even task-queues), to ensure that two tasks queued in the same source will get executed in the correct order.

The closest we have of defining some sort of prioritization is the incoming Prioritized Task Scheduling API which will give us, web-authors, the mean to post prioritized tasks, with three priority levels: "user-blocking", "user-visible" and "background".

To check what browsers actually implement, you'd have to go through their source-code and inspect it thoroughly.
I myself already spent a couple hours in there and all I can tell you about it is that you better be motivated if you want to get the full picture.
A few points that may interest you:

  • All browsers don't expose the same behavior at all.
  • In Chrome, setTimeout() still has a minimum delay of 1ms (https://crbug.com/402694)
  • In Firefox, because Chrome's 1ms delay was producing different results on some web-pages, they create a special very-low-priority task-queue only for the timers scheduled before the page load, the ones scheduled after are queued in a normal priority task-queue. (https://bugzil.la/1270059)
  • At least in Chrome, each task-queue has a "starvation" protection, which prevents said queue to flood the event-loop with its own task, by letting the queues with lower priority also execute some of their tasks, after some time (not sure how much).

And in the end I'd like to see an example that shows the order of these queues, if it is possible.

As hinted before, that's quite complicated, since there is no "one" order.

Your own example though is quite a good test, which in my Chrome browser does show correctly that the UI task-queue has an higher priority than the timer one (the while loop takes care of the 1ms minimum delay I talked about). But for the DOMContentLoaded though, I must admit I'm not entirely sure it shows anything significant: The HTML parser is also blocked by the while loop and thus the task to fire the event will only get posted after the whole script is executed.

But given this task is posted on the DOM Manipulation task source, we can check it's priority by forcing a other task that uses this task source, e.g script.onerror.
So here is an update to your snippet, with a few more task sources, called in reverse order of what my Chrome's prioritization seems to be:

const queueOnDOMManipulationTaskSource = (cb) => {
  const script = document.createElement("script");
  script.onerror = (evt) => {
    script.remove();
    cb();
  };
  script.src = "";
  document.head.append(script);
};
const queueOnTimerTaskSource = (cb) => {
  setTimeout(cb, 0);
}
const queueOnMessageTaskSource = (cb) => {
  const { port1, port2 } = new MessageChannel();
  port1.onmessage = (evt) => {
    port1.close();
    cb();
  };
  port2.postMessage("");
};
const queueOnHistoryTraversalTaskSource = (cb) => {
  history.pushState("", "", location.href);
  addEventListener("popstate", (evt) => {
    cb();
  }, { once: true });
  history.back();
}
const queueOnNetworkingTaskSource = (cb) => {
  const link = document.createElement("link");
  link.onerror = (evt) => {
    link.remove();
    cb();
  };
  link.href = ".foo";
  link.rel = "stylesheet";
  document.head.append(link);
};
const makeCB = (log) => () => console.log(log);
console.log("The page will freeze for 3 seconds, try to click on this frame to queue an UI task");
// let the message show 
setTimeout(() => {
  window.scheduler?.postTask(makeCB("queueTask background"), {
    priority: "background"
  });
  queueOnHistoryTraversalTaskSource(makeCB("History Traversal"));
  queueOnNetworkingTaskSource(makeCB("Networking"));
  queueOnTimerTaskSource(makeCB("Timer"));
  // the next three are a tie in current Chrome
  queueOnMessageTaskSource(makeCB("Message"));
  window.scheduler?.postTask(makeCB("queueTask user-visible"), {
    priority: "user-visible"
  });
  queueOnDOMManipulationTaskSource(makeCB("DOM Manipulation"));

  window.scheduler?.postTask(makeCB("queueTask user-blocking with delay"), {
    priority: "user-blocking",
    delay: 1
  });
  window.scheduler?.postTask(makeCB("queueTask user-blocking"), {
    priority: "user-blocking"
  });
  document.addEventListener("click", makeCB("UI task source"), {
    once: true
  });
  const start = performance.now();
  while (start + 3000 > performance.now());
}, 1000);
Kaiido
  • 123,334
  • 13
  • 219
  • 285
  • Nice answer! I am looking forward when this API will be standardized. By the way you could also add `requestAnimationFrame` API for comparison with others. I've tested it and it appears after `UI task source` – MaximPro Jan 30 '22 at 20:26
  • requestAnimationFrame doesn't participate in the prioritized tasks per se. See https://stackoverflow.com/a/62188756/3702797 – Kaiido Jan 31 '22 at 00:30
  • yeah, I know it. Still I wanted to specify one thing, there in [specification](https://wicg.github.io/scheduling-apis/#sec-task-priorities) has been written the next: `user-blocking is the highest priority, and is meant to be used for tasks that are blocking the user’s ability to interact with the page, such as rendering the core experience or responding to user input.` Why the render process is marked as task with user-blocking priority? [whatwg](https://html.spec.whatwg.org/multipage/webappapis.html#update-the-rendering) spec doesn't define task for rendering. I am stuck – MaximPro Jan 31 '22 at 00:57
  • This is not about requestAnimationFrame, which once again doesn't participate in the prioritization system, since its task are not "chosen". This is about "what web-authors think should be very important", something visual may be part of this category, as an example. – Kaiido Jan 31 '22 at 01:00
  • `something visual may be part of thos category, as an example`. Could you give an example of it, something concrete? I don't understand this abstraction. – MaximPro Jan 31 '22 at 01:04
  • I can't tell for sure what the authors of this spec had in mind when they wrote this, but obvious situations would be an app that want to display a message to their users after some async stuff happened, this would be "something visual", and they might want it to to happen ASAP – Kaiido Jan 31 '22 at 01:08
  • I am sorry for bothering, but couldn't you look at my [question](https://stackoverflow.com/questions/71072045/does-transform-opacity-property-invoke-paint-step-in-the-browser-conveyor)? I think you might know something about it. – MaximPro Feb 13 '22 at 00:11