Why does setTimeout(.., 0) trigger before browser action in Firefox? Bug?

Viewed 610

I have an input.onkeydown handler and I check input.value after setTimeout(..0).

I expect input.value to have the new value when the setTimeout callback runs.

In all browsers except Firefox it does. In Firefox it's not always the case.

The code to check is:

<input id="input">
<script>
  input.onkeydown = function() {
    setTimeout(() => this.value = this.value.toUpperCase());
  };
</script>

The demo: http://plnkr.co/edit/rZmiHdttSXNdpKkR8YbH?p=preview

As I uppercase the input value after setTimeout(..0), it should be uppercased always. But as said, in Firefox it's not.

Here's the demo video; the first few seconds demonstrate the problem: https://jmp.sh/9XSROQ2

The relevant spec part is https://dom.spec.whatwg.org/#concept-event-dispatch.

Am I not getting something, or is this a long-standing bug in Firefox?

P.S. If I add console.log in setTimeout, I sometimes see the old value.

P.P.S. The purpose of this question is to know if I understand setTimeout correctly or not. I'm familiar with a variety of ways to uppercase input; please do not suggest oninput, requestAnimationFrame, or such.

2 Answers

The actual specs are here and actually do not define the relation between the keydown and the change of the input's current value.

However they read, (emphasize mine)

For input elements without a defined input activation behavior, but to which these events apply, any time the user causes the element's value to change without an explicit commit action, the user agent must queue a task to fire an event named input at the input element, with the bubbles attribute initialized to true. The corresponding change event, if any, will be fired when the control loses focus.

So it is actually specced that the input event (that you should be listening to anyway) must fire asynchronously. Since this event is the one that testifies of a change in the current value, I don't think Firefox's behavior of applying the changes caused by the Key event in the next event loop is hardly a bug; remember that browsers have to make this change asynchronously after the event is dead (and can't be cancelled anymore by any handler).

Some additional notes (which may be related to a real cause btw), inputing a combined character (e.g ^ + a => â) I have a 100% repro in FF on macOs (because yes, I suspect it may also be related to the OS).

But of course, even if it doesn't go against any specs, and even if you have an easy fix (listen for input event) you may still want to file a bug-report at least for not behaving like other vendors.

I think its because Firefox is handling setTimeout(fn,0) synchronous also asked here: Why does Firefox handle setTimeout(fn, 0) synchronous but setTimeout(fn, 1) asynchronous?

Chrome and node replacing 0 with 1: Difference between setTimeout(fn, 0) and setTimeout(fn, 1)? So its always async for them. If you run it with setTimeout(fn,1), it also works in FF.

If you want to get it sync, use "oninput". The input value is not updated completely when the "onkeydown" event goes of. Working with, or without timeout in chrome/ff

input.oninput = function() {
    this.value = this.value.toUpperCase()
};
Related