At what point do the event handlers get determined when an event has been fired in the browser?

Viewed 90

I have been reading a lot about the event loop lately and the different queues (job/macrotask queue, microstask queue, render queue) it has and their priority. But am still not able to fully understand one thing, let's say a user fires 2 events one after the other pretty fast. Then both event handlers will be enqueued on the job queue and executed in the correct order. But what if the first event handler does some heavy computations and blocks the main thread for a while but it also manipulates the DOM in a way that changes the UI so that the element that the user clicked on the second time is removed and replaced by some other dom element that also has a click event registered.

Then if I have understood the process correctly, once the first event handler finishes execution, all the microtasks get executed and the microtask queue is emptied. Then the render queue gets emptied and the browser repaints. Here is where my actual question comes, does a second event handler get executed based on the new state of the UI, or based on the element which was there at the time of the click?

I tried this out (https://jsfiddle.net/jsx73t6c/4/) by having 3 buttons each with a click event handler registered to it that logs to the console that the respective button was clicked. I click fast on button 1 and button 2. Button 1's handler does a big loop so the main thread is blocked for a while and then removes button 2. And since button 2 is removed on it's place comes button 3 and I end up with 2 console logs indicating that button 1 and button 3 were clicked even though at the time when my mouse is clicked it was on button 1 and button 2. It seems like the event handlers are registered on the queue but the elements that click happens on are determined later when it's time to process the click event and trigger the event handlers that are registered. To me this behavior makes more sense and is a more predictable behavior than having the clicked elements determined at the time of the click based on the state of the DOM then. But I am still not sure if the behavior in my example is always guaranteed.

const button2 = getButton(2);
const button3 = getButton(3);

const removeButton2 = () => button2.parentElement.removeChild(button2);

button2.addEventListener("click", () => console.log("button 2 clicked"));

button3.addEventListener("click", () => console.log("button 3 clicked"));

button1.addEventListener("click", () => {
  console.log("button 1 clicked");

  const fragment = document.createDocumentFragment();
  for (let i = 0; i < 70_000; i++) {
    const a = document.createElement("a");
    a.innerHTML = "text " + i;
    fragment.appendChild(a);
  }

  document.documentElement.appendChild(fragment);
  removeButton2();
});
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <button id="button-1">button 1</button>
    <button id="button-2">button 2</button>
    <button id="button-3">button 3</button>
  </body>
</html>

I know that such real-world scenario is highly unlikely to stumble upon with the current state of browsers where everything happens so fast, but would appreciate some more light and resources on the topic.

1 Answers

This will be implementation dependent, even though all modern UAs do expose the same behavior. For instance in old IE, which was using a single process for everything, your click events while blocked would simply get ignored.

Modern browsers now have many helper process, so they can handle it (the browser process can keep the message until the renderer is ready to process it).
So yes, in current major browsers, your event will get processed by the renderer only when the first callback is done, that's when the actual DOM event will get constructed and thus it will use the updated DOM tree.

You can see this Chromium design doc that talks a bit about that: https://www.chromium.org/developers/design-documents/displaying-a-web-page-in-chrome/#life-of-a-mouse-click-message, and this one about IPC messaging in general: https://www.chromium.org/developers/design-documents/inter-process-communication/

Related