Why doesn't triggering click() inside a click event listener cause an infinite loop?

Can someone explain the program flow of this JavaScript code:

const $leaveRoom = document.querySelector('#leave-button');
let a = 1;
$leaveRoom.addEventListener('click', () => {
  console.log(a);
  console.log("check");
  a++;
  $leaveRoom.click();
  console.log(a);
  a++;
});
<button id="leave-button">Leave Room</button>
The Output was:
1
check
2
check
3
4

This question may sound dumb but I am new to JavaScript. I am not able to understand the program flow of this code. I want to know how did I get 3 & 4 in the output.


The key to this question is the presence of a hidden Flag on each element.click() method.

Each element has an associated click in progress flag, which is initially unset.

the doc: https://html.spec.whatwg.org/multipage/interaction.html#dom-click

As soon as this method is activated, this flag changes from progess Status == unset to progess Status == active (pseudo code)

(then it returns to its initial status once the code it contains is fully executed)

When this flag is in the active state, then any call to this method is ignored.


Here is my original post to show the execution sequence of `console.log()`

const bt_leaveRoom = document.querySelector('#leave-button')
var counter = 0
var origin  = 'event clic'

bt_leaveRoom.addEventListener('click', () => 
  {
  let source = origin
  console.log(`_first console.log(): counter = ${ ++counter }, origin = ${source}`)

  origin = 'call'
  bt_leaveRoom.click()
  
  console.log(`second console.log(): counter = ${ ++counter }, origin = ${source}`)
  })
<button id="leave-button">Leave Room</button>

the Hidden Flag act like in the same way if I have coded this way :
replace this line:
bt_leaveRoom.click()
to:
if (source !== 'call') bt_leaveRoom.click()

But in fact the system use the method hidden flag (named progress flag ?)
which can be (in pseudo code)

if (progress_flag_of_bt_leaveRoom.click() is unset) do { bt_leaveRoom.click() }  

I tried a couple of things to find an answer to this question. I haven't really found a definitive answer to what's going on here, but I do believe that what I share here feeds into this excellent problem.

I simplified the code to focus on the recursive triggering of events.

Simplified Code

const $leaveRoom = document.querySelector('#leave-button');
let a = 1;
$leaveRoom.addEventListener('click', () => {
    console.log(a++);
    $leaveRoom.click();
});
<button id="leave-button">Leave Room</button>

We can see here that we have two calls for console.log, the first one is for the actual clicking of the button, and the second one is for the call to $leaveRoom.click();. It seems to stop there for some reason.

Using dispatchEvent

const $leaveRoom = document.querySelector('#leave-button');
let a = 1;
$leaveRoom.addEventListener('click', () => {
    console.log(a++);
    $leaveRoom.dispatchEvent(new Event('click'));
});
<button id="leave-button">Leave Room</button>

Here, the event is triggered multiple times (44 for me), this could be due to how fast your machine is. It seems to stop triggering eventually though, so I'm thinking the same phenomenon is happening here as well.

Using setTimeout

const $leaveRoom = document.querySelector('#leave-button');
let a = 1;
$leaveRoom.addEventListener('click', () => {
    console.log(a++);
    setTimeout(() => { $leaveRoom.click(); });
});
<button id="leave-button">Leave Room</button>

If you are looking for a way to infinitely trigger the click event, regardless of why the previous methods fail. This does seem to do the trick.

Having said all this, I still have no clue about this hidden force that stops the recursion for the previous methods. Maybe someone could shed some light on this.


const click1 = document.querySelector('#click1')
const click2 = document.querySelector('#click2')
const click3 = document.querySelector('#click3')

click1.addEventListener('click', (event) => {
  console.log("click1")
  click2.click()
});
click2.addEventListener('click', (event) => {
  console.log("click2")
  click3.click()
});
click3.addEventListener('click', (event) => {
  console.log("click3")
  click1.click()
});
<button id="click1">Click 1</button>
<button id="click2">Click 2</button>
<button id="click3">Click 3</button>

Actually it appears there is loop breaking logic in the event handling. The handlers will happily daisy chain user input events until the initial interaction dispatches a duplicate event. Then it will no longer dispatch any.