There\'s a difference in the execution order of the microtask/task queues when a button is clicked in the DOM, vs it being programatically clicked.
So, Chrome answer just because it's interesting (see T.J Crowder's excellent answer for the general DOM answer).
btn.click();
Calls into HTMLElement::click() in C++ which is the counterpart of the DOMElement:
void HTMLElement::click() {
DispatchSimulatedClick(nullptr, kSendNoEvents,
SimulatedClickCreationScope::kFromScript);
}
Which basically does some work around dispatchMouseEvent and deals with edge cases:
void EventDispatcher::DispatchSimulatedClick(
Node& node,
Event* underlying_event,
SimulatedClickMouseEventOptions mouse_event_options,
SimulatedClickCreationScope creation_scope) {
// This persistent vector doesn't cause leaks, because added Nodes are removed
// before dispatchSimulatedClick() returns. This vector is here just to
// prevent the code from running into an infinite recursion of
// dispatchSimulatedClick().
DEFINE_STATIC_LOCAL(Persistent>>,
nodes_dispatching_simulated_clicks,
(MakeGarbageCollected>>()));
if (IsDisabledFormControl(&node))
return;
if (nodes_dispatching_simulated_clicks->Contains(&node))
return;
nodes_dispatching_simulated_clicks->insert(&node);
if (mouse_event_options == kSendMouseOverUpDownEvents)
EventDispatcher(node, *MouseEvent::Create(event_type_names::kMouseover,
node.GetDocument().domWindow(),
underlying_event, creation_scope))
.Dispatch();
if (mouse_event_options != kSendNoEvents) {
EventDispatcher(node, *MouseEvent::Create(event_type_names::kMousedown,
node.GetDocument().domWindow(),
underlying_event, creation_scope))
.Dispatch();
node.SetActive(true);
EventDispatcher(node, *MouseEvent::Create(event_type_names::kMouseup,
node.GetDocument().domWindow(),
underlying_event, creation_scope))
.Dispatch();
}
// Some elements (e.g. the color picker) may set active state to true before
// calling this method and expect the state to be reset during the call.
node.SetActive(false);
// always send click
EventDispatcher(node, *MouseEvent::Create(event_type_names::kClick,
node.GetDocument().domWindow(),
underlying_event, creation_scope))
.Dispatch();
nodes_dispatching_simulated_clicks->erase(&node);
}
It is entirely synchronous by design to make testing simple as well as for legacy reasons (think DOMActivate weird things).
This is just a direct call, there is no task scheduling involved. EventTarget in general is a synchronous interface that does not defer things and it predates microtick semantics and promises :]