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);