0

For the following test case (I replaced the XHR call with setTimeout to represent an XHR call with a very fast response):

<html>
  <body>
     <div onclick="console.log('onclick parent')">
       Event Queue Test:
       <input type=text onblur="console.log('onblur'); setTimeout(function() {console.log('onblur timeout')})"/>
     </div>
   </body>
</html>

If I click in the text field and then click on the text preceding the text field, I get the following output in Chrome (v70):

onblur
onblur timeout
onclick parent

I would have expected the parent onclick handler to have been queued before the setTimeout event. In Chrome, at least, it does not. Is this behavior specified by the standard or implementation-dependent? I'm also interested in workarounds to always have the parent onclick handler run before the timeout (or XHR) handler.

In my actual use case, this issue is problematic because the events are not guaranteed to be queued in the same order, depending on how long the XHR request takes to run.

Kylos
  • 1,888
  • 14
  • 24
  • 1
    Your call to `setTimeout()` is incorrect. You're not passing a function, you're passing the return value from the call to `console.log()`. – Pointy Oct 31 '18 at 16:40
  • Thanks for pointing that out. I made a rookie mistake in distilling this to a test case. I fixed it and the queuing issue still exists. setTimeout is not the actual issue. This is not a duplicate. – Kylos Oct 31 '18 at 17:07
  • 2
    OK looking at this closely, I thought it might be a `console.log()` anomaly. Now I don't think it is. I altered the code a little so that it doesn't directly depend on `console.log()` output order making sense, and it's still weird. I'm testing with Firefox, and it's as if the browser takes around 100ms or so to figure out that the "click" event on the "Event queue" text needs to fire on the `
    `. If the `setTimeout()` delay is greater than 100 (more or less) then things happen in an order that makes more sense.
    – Pointy Oct 31 '18 at 17:37
  • Anyway I'm not sure that there's any guarantee that the "click" (which follows the triggering of "mousedown" and "mouseup" of course) will happen at any given point. In other words, though the behavior is unexpected, it's hard to say categorically that it's wrong. – Pointy Oct 31 '18 at 17:38
  • I just performed that test on my own (getting rid of `console.log()`) and still see the timeout event queued before the onclick. Glad someone else is able to reproduce it. – Kylos Oct 31 '18 at 17:39
  • Oh it definitely happens. Maybe I'll try it and add mouse event handlers too, and maybe "focus". I don't know much about the precise details of how browsers schedule the "stack" of events in situations like a "click" (which is sort-of a "high level" event). – Pointy Oct 31 '18 at 17:44
  • 1
    Ah: maybe it's this: "click" cannot fire until "mouseup" has happened, and of course unless you're an industrial robot there's a brief period of time between when your mouse button goes down and when it goes back up. The "focus" event on the parent `
    ` fires first, then "mousedown". The "blur" on the `` immediately follows "mousedown" in all cases.
    – Pointy Oct 31 '18 at 17:55

1 Answers1

1

This won't necessarily help with your actual XHR problem, but I think I understand what's going on. I extended your sample code, using jQuery for convenience:

<div class=parent>Label: <input class=field></div>

and the JavaScript:

var $d = $(document);

var t = {
  mousedown: -1,
  mouseup: -1,
  focus: -1,
  click: -1,
  blur: -1,
  timer: -1
};

["mousedown", "mouseup", "focus", "click"].forEach(function(event) {
    $d.on(event, ".parent", function() {
    t[event] = Date.now();
  })
});
$d.on("blur", ".field", function() {
    t.blur = Date.now();
  setTimeout(function() {
    t.timer = Date.now();
  }, 150);
});
$d.on("click", ".parent", function() {
    setTimeout(function() {
    var race = Object.keys(t).sort(function(k1, k2) { return t[k1] - t[k2];});
    console.log("Result: ", race);
    race.forEach(function(key) { t[key] = -1; });
  }, 1000);
});

What that code does is order the events by the times they actually happen. In the situation of clicking on the text after clicking and focusing in the input field, the order with a short timeout (less than 100ms or so) is

  1. focus
  2. mousedown
  3. blur
  4. timer
  5. mouseup
  6. click

The "blur" and "timer" events are for the input field and its setTimeout() callback. If I make that timeout interval longer, like 150ms or so, then I get

  1. focus
  2. mousedown
  3. blur
  4. mouseup
  5. click
  6. timer

That, I think, makes some sense: the "mousedown" and "mouseup" events are driven by your actual physical interaction with the mouse button. The "click" event does not make sense until "mouseup" is received. With a short timer in the "blur" handler, the timer can fire before the mouse has physically communicated the "up" event through your OS etc.

Pointy
  • 405,095
  • 59
  • 585
  • 614
  • 1
    Thanks! Reading the HTML spec, it doesn't seem that it specifies precisely when the focus events will be fired relative to mouse events. Your testing makes sense and explains what I've been seeing. It seems reasonable that focusing would happen on mouse down. – Kylos Oct 31 '18 at 18:40