Open Bug 1305387 Opened 3 years ago Updated 10 days ago

No "input" event should be fired immediately after "compositionend" event

Categories

(Core :: DOM: Events, defect, P3)

defect

Tracking

()

ASSIGNED
Webcompat Priority P3
Tracking Status
firefox72 --- affected

People

(Reporter: duanyao.ustc, Assigned: masayuki)

References

Details

User Agent: Mozilla/5.0 (Windows NT 10.0; WOW64; rv:48.0) Gecko/20100101 Firefox/48.0
Build ID: 20160726073904

Steps to reproduce:

Listen "compositionend" and "input" event on an input or a textarea element.
Type something with an input method.

Tested on Windows 10 + Firefox 48/52,and "Microsoft Pinyin" input method.


Actual results:

The event order is:

(typing)
  input event (multiple times)
(commit the composition)
  input event
  compositionend event
  input event


Expected results:

The event order should be:

(typing)
  input event (multiple times)
(commit the composition)
  input event
  compositionend event


See the spec "4.7.6. Input Events During Composition"
https://w3c.github.io/uievents/#events-composition-input-events

Chrome 53 works as expected.
I could reproduce this on Mac. This is a long-standing issue at least since the build of 2015-01-01.
Status: UNCONFIRMED → NEW
Ever confirmed: true
Priority: -- → P3
We can fix this bug quickly now.

Taking.
Assignee: nobody → masayuki
Status: NEW → ASSIGNED
OS: Unspecified → All
Hardware: Unspecified → All
Version: 52 Branch → Trunk
Blocks: 1447239
No longer blocks: 1451672
Now, I believe that the standardized behavior (actual behavior of Chrome and Safari) does not make sense from the point of view of web application developers since isComposing attribute value of "input" event followed by "compositionend" becomes true.

I filed a spec issue: https://github.com/w3c/uievents/issues/202

See the issue for the detail.
This tripped me up today and it turns out the behavior of Chrome/Safari here is actually easier for me.

Specifically, I am using React which synthesizes "change" events from input events.

You can see an example here: https://jsfiddle.net/birtles/so1ku9j3/

If you run that example on Fennec and type てす then click the テスト candidate you'll get output like:

  change-て
  change-てす
  compositionend-テスト
  change-テスト

However, performing the same actions in Chrome for Android gives:

  change-て
  change-てす
  change-テスト
  compositionend-テスト

Now if the React component is using 'change' events to update state and then 'compositionend' as a trigger for committing the data (e.g. calling a callback on the parent component) it will end up committing the incomplete string.

This doesn't seem to be problematic on desktop since we generally get more intermediate input events.
The Chrome/Safari behavior requires any web apps to listen to compositionend event. Firefox/Edge behavior does not require compositionend events since web apps can ignore only input events whose isComposing is true.

In other words, if isComposing value of the last input event of Chrome/Safari should be false, web apps can handle simpler.
I agree with you about the value of isComposing. I think we should follow the spec's ordering but set isComposing to 'false' for the last input event (the one before compositionend). Simply fixing the event ordering would fix the problem I encountered in comment 8.
(In reply to Brian Birtles (:birtles) from comment #10)
> I agree with you about the value of isComposing. I think we should follow
> the spec's ordering but set isComposing to 'false' for the last input event
> (the one before compositionend).

However, InputEvent.isComposing is declared as it's set to true if dispatched between "compositionstart" and "compositionend". So, anyway, we need an update of spec :-(
https://w3c.github.io/uievents/#dom-inputevent-iscomposing

Just wanted to add some context—although this might already be accounted for.

Safari and Chrome currently have different behaviors here, and I think Safari's is the most correct. The key is not just the order that input events fire in, but also the inputType property. Here are the results for each browser:

Results when simply typing Option-E then E, which results in: é

I think the correct ordering is:

  1. Composition starts
  2. Any amount of input events, with insertCompositionText or deleteCompositionText as the inputType (non-cancellable)
  3. A single input event with insertFromComposition to "commit" the change (cancellable)
  4. Composition ends

Safari does this. Chrome doesn't ever fire an insertFromComposition event to commit the change, so none of the events are cancellable and you're never sure what the committed input was just by looking at the input events.

From my experimenting so far, Safari's behavior is all that's required to handle them gracefully. I actually haven't needed to check the isComposing flag in Safari at all or ever attach to composition* events, because you're guaranteed that insertCompositionText/deleteCompositionText are input types that only fire for interim composition edits, and that you'll get an insertFromComposition at the end with the new data (which you can cancel and handle yourself too).

I think looking at React is going to be problematic because their input events (and beforeinput events) are polyfilled in weird ways.

This might be the reason for a webcompat bug on Firefox.
https://webcompat.com/issues/44208

Webcompat Priority: --- → ?
Webcompat Priority: ? → P3
You need to log in before you can comment on or make changes to this bug.