Repository navigation
insertCompositionText (and isComposing) cannot tell the input event accompanied by compositionEnd from the other WIP IME input events #176
Description
Activity
- addedAgenda+Queue this item for discussion at the next WG meetingQueue this item for discussion at the next WG meeting
on Oct 9, 2025 Another approach is to append a new additional property to
inputEvent.@tats-u Explain a bit more - what would that property hold? Preferably with an example of what would happen during a composition sequence that has both partial commits and a full final commit.
@johanneswilm Something like
isConfirmed.
falseonly in 2–5.trueotherwide (including when you input or delete some text without help of IME).I created a new issue related to it to uievents.
You might as well choose one and close the other.@tats-u Let's keep the discussion here. The other spec is maintained by other people and is not discussed at the Editing WG meetings.
It should be
falsein 5, 11 ,17 , and 23. However it should betruein 29.What should it be in 28?
onBeforeInput? I have not used it yet and in the first place I do not know when we should use this event instead ofonInput. In my opiniontrueis preferred but I am not so sure.Oh, ok. @tats-u try to read the spec so that we are all on the same page and so that we are sure that the thing you are asking for is not already covered in some other way so that we don't spend a lot of time reading through and trying to understand your proposal if it is already covered or doesn't fit the overall model.
Very briefly:
beforeInputcomes before the DOM change andinputcomes after the DOM change. A lot ofbeforeInputevents can be cancelled, which means that the browser will then not do the DOM change and there will not be aninputevent. SomebeforeInputevents cannot be cancelled (related to composition), which means the DOM change will take place under all circumstances. In that case, thebeforeInputevent can be used in order to note what the DOM looked like before the DOM change took place.The new
EditContextAPI can be used instead ofcontenteditablein order to get around the issue of these non-cancellablebeforeInput.https://w3c.github.io/uievents/#events-compositionevents only says "A user agent MUST dispatch this event when the DOM is about to be updated."
In that case, the beforeInput event can be used in order to note what the DOM looked like before the DOM change took place.
It does not seem to be concerned with my suggested
isConfirmed; it can continue to stick to the existingisComposing.isConfirmedcan be simply defined as "!isComposing || [the key input that triggered the event will also trigger compositionend]" if we defineisConfirmedthere astrue.The new EditContext API can be used instead of contenteditable in order to get around the issue of these non-cancellable beforeInput.
It is "too much" for casual usage (e.g. single simple text field).
In that case, the beforeInput event can be used in order to note what the DOM looked like before the DOM change took place.
It does not seem to be concerned with my suggested
isConfirmed; it can continue to stick to the existingisComposing.So are you saying that in your example, the
beforeInputevent in 28 should have theisConfirmedset tofalse? Or should it betrue?isConfirmedcan be simply defined as "!isComposing || [the key input that triggered the event will also trigger compositionend]" if we defineisConfirmedthere astrue.Are you saying that
isConfirmedshould be equal tofalsein all cases unless it is the (beforeInput/)inputevent that is the last during a composition that commits the final text to the DOM?What is the reason why we cannot just make
isComposingfalse for that last event?What do we do when the IME commits only a partial string to the DOM permanently while the composition continues in the remaining part of the string?
The new EditContext API can be used instead of contenteditable in order to get around the issue of these non-cancellable beforeInput.
It is "too much" for casual usage (e.g. single simple text field).
Right, but once implemented by other browsers, someone could create a JavaScript library that uses this, which then in turn can be used by "casual" JavaScript developers.
So are you saying that in your example, the beforeInput event in 28 should have the isConfirmed set to false? Or should it be true?
Probably
truebut I am not sure.What is the reason why we cannot just make isComposing false for that last event?
Is it not a breaking change that may break existing applications?
What do we do when the IME commits only a partial string to the DOM permanently while the composition continues in the remaining part of the string?
↑Partially committed "日本語" first and then commited "大好き" (Chrome / onKeyDown is filtered out)
I do not know about
onBeforeInputin 78.Also, I have just found that
input/beforeinputis not fired in Chromium (fired in Firefox) if you leave focus when you trying to input some text using IME.Right, but once implemented by other browsers, someone could create a JavaScript library that uses this, which then in turn can be used by "casual" JavaScript developers.
A lack of such libraries should not prevent developers from adopting newly-created UI frameworks (UI frameworks = e.g. React).
This is a dup of w3c/uievents#202
The
isConfirmedis an opposite property ofisComposing. So I think it's redundant. And changinginputTypevalue could cause breaking some web apps.FYI: Firefox is now dispatches 2
inputevents aroundcompositionendagainst a real web-compat issue. One is immediately beforecompositionendwithisComposing=true. Then, the other is immediately aftercompositionendwithisComposing=false. This approach breaks the 1on1 relation betweenbeforeinputandinput, but works in the wild for now.This is a dup of w3c/uievents#202
No as long as we try to avoid breaking changes. Isn't it also a breaking change for some existing applications to reorder the events?
but works in the wild for now.
We have to filter out redundant one of them to avoid unnecessary fire.
15 remaining items
Call 2026-02-12:
08:58 johanneswilm: need input from Mozilla
08:58 smaug: probably need to ping masayuki
08:59 smaug: what's the exact q?
08:59 johanneswilm: in FF you have an extra beforeinput event that's not according to spec, but makes sense to Masayuki08:59 johanneswilm: in FF you have an extra beforeinput event that's not according to spec, but makes sense to
-
I think that was meant to be extra input event. There is the extra input event with isComposing=false after compositionend. And that is one way to fix this issue.
-
Another approach is to have isComposing=false in the very latest input event right before compositionend.
Both these approaches would let the web dev to rely on input events without need for compositionend. Now the question is webcompat and developer ergonomics. FF is shipping the first approach, so it isn't at least horribly broken :) But which one would the web devs prefer?
Reacted by Masayuki Nakano-
I think that was meant to be extra input event. There is the extra input event with isComposing=false after compositionend. And that is one way to fix this issue.
This might risk a double character insertion, if your editor assumes that it has to insert the composed character in oncompositionend.
Another approach is to have isComposing=false in the very latest input event right before compositionend.
To me that approach makes the most sense. Because technically, the last input of a composed character is the final character (aka the "committed composed character"), so it's not part of the composition anymore. It could be treated like any other character that arrived (without composition)
In order to keep an internal model up2date with user inputs an editor dev could:
function onbeforeinput(event) { if (event.isComposing) return; // just ignore "uncommitted characters during composition" my_editor_model.insertText(event.data); }
But editors typically also want to track when a composition started and ended, in my case I capture the selection before the composition started, to overwrite the old contents once the composition is complete.
With the above approach I could do that by tracking these boundaries myself:
let before_composition_selection = null; function onbeforeinput(event) { if (event.isComposing) { if (!before_composition_selection) { before_composition_selection = __get_selection_from_dom(event.getTargetRanges()[0]) } return; // just ignore "uncommitted characters during composition" } if (!event.isComposing && before_composition_selection) { // composition just ended, we delete content that was selected, // when the composition started my_editor_model.delete_selection(before_composition_selection); before_composition_selection = null; } my_editor_model.insertText(event.data); }
However, if we could integrate those start/end boundaries into the before/input API's it would become even easier. Something like introducing
event.hasCompositionStartedandevent.hasCompositionEnded.let before_composition_selection = null; function onbeforeinput(event) { if (event.hasCompositionStarted) { before_composition_selection = __get_selection_from_dom(event.getTargetRanges()[0]); } if (event.isComposing) { return; // just ignore "uncommitted characters during composition" } if (event.hasCompositionEnded) { // we delete content that was selected, when the composition started my_editor_model.delete_selection(before_composition_selection); } my_editor_model.insertText(event.data); }
My only worry with having isComposing=false in the very latest input event, is that it might break existing editors, like mine in its current state (it relies on isComposing=true for the "committed character" and handles the insertion at oncompositionend).
Maybe the safest would be not to change any API's but only introduce
event.hasCompositionStartedandevent.hasCompositionEndedthis would still allow all reasoning needed. Feels slightly less intuitive, but would be the safest change. Here's how the code would look then:let before_composition_selection = null; function onbeforeinput(event) { if (event.hasCompositionStarted) { before_composition_selection = __get_selection_from_dom(event.getTargetRanges()[0]); } if (event.isComposing && !event.hasCompositionEnded) { return; // just ignore "uncommitted characters during composition" } if (event.hasCompositionEnded) { // we delete content that was selected, when the composition started my_editor_model.delete_selection(before_composition_selection); } my_editor_model.insertText(event.data); }
Generally I find the prospect appealing of not having to use oncompositionstart/end and track everything in before/input events. It could become the new recommended way for editors to handle character composition?
Call 2026-03-12:
08:27 next issue: #176
08:28 smaug: basically what masayuki said: spec has been documenting Chrome's behavior. IE (pre-blink) and FF were aligned
08:28 smaug: the bookended isComposing := false makes it easier for developers to know when it ends w/o compositionend
08:29 smaug: let's hear from web devs (Michael?)
08:29 johanneswilm: back when we discussed years ago, desire was for the event before the end to be cancelable
08:30 johanneswilm: in practice, developers probably better off waiting for the isComposing false and then cleaning up after
08:31 michael_aufreiter: there is a trick to cancel — remove focus temporarily on compositionstart
08:32 johanneswilm: from the perspective of a JS developer either approach seems reasonable
08:32 johanneswilm: is there any reason other browsers don't implement this?
08:32 smaug: …in any case, there is going to be some web compat risk
08:33 smaug: but I suppose something is broken today anyways
08:33 whsieh: seems reasonable at a glance, we'll want to discuss internally (WebKit)
08:34 whsieh: just to verify, they aren't mutually exclusive?
08:34 johanneswilm: correct
08:35 smaug: though you'd get 2 input events with isComposing = false (tiny bit weird, not that weird)
08:35 johanneswilm: so we need feedback from WebKit/Chromium?
08:35 (yes)
08:35 johanneswilm: and would FF be open to adopting level 2 input event spec behavior?
08:35 smaug: FF would be open to it, but let's hear from developers what they prefer
08:36 johanneswilm: many of the developers are in this call!
08:37 smaug: true, but it would be good to have input from people solving these issues at Google Docs/Office
08:37 Rakesh: office web apps have started using these events
08:37 Rakesh: need to look into it more before I comment…I think that was meant to be extra input event. There is the extra input event with isComposing=false after compositionend. And that is one way to fix this issue.
This might risk a double character insertion, if your editor assumes that it has to insert the composed character in oncompositionend.
Yes, might be. However, if
inputevent is handled withoutisComposing, a lot of duplication must happen during a composition because multipleinputevents are usually fired during a composition. So, unless web apps handleinputevent tricky, this must be enough safe.Another approach is to have isComposing=false in the very latest input event right before compositionend.
To me that approach makes the most sense. Because technically, the last input of a composed character is the final character (aka the "committed composed character"), so it's not part of the composition anymore. It could be treated like any other character that arrived (without composition)
Yeah, but when I suggested, it was not taken because it's hard to wording IIRC.
With the above approach I could do that by tracking these boundaries myself:
let before_composition_selection = null;
function onbeforeinput(event) {
if (event.isComposing) {
if (!before_composition_selection) {
before_composition_selection = __get_selection_from_dom(event.getTargetRanges()[0])
}
return; // just ignore "uncommitted characters during composition"
}if (!event.isComposing && before_composition_selection) {
// composition just ended, we delete content that was selected,
// when the composition started
my_editor_model.delete_selection(before_composition_selection);
before_composition_selection = null;
}my_editor_model.insertText(event.data);
}Well, currently, I don't suggest to make
isComposingofbeforeinputtofalsebecause the composition has not ended yet because the DOM has not been changed yet. However, I guess it should work, I need to think deeper for the scenario...My only worry with having isComposing=false in the very latest input event, is that it might break existing editors, like mine in its current state (it relies on isComposing=true for the "committed character" and handles the insertion at oncompositionend).
Interesting. Then, isn't it already broken on Firefox?
Maybe the safest would be not to change any API's but only introduce
event.hasCompositionStartedandevent.hasCompositionEndedthis would still allow all reasoning needed. Feels slightly less intuitive, but would be the safest change.Well, I think we should be careful to add new APIs which is almost similar to extant API but a little bit different. That may make the developers confused and lead misuses.
Thank you for your inputs @masayuki-nakano. I'm also still undecided which solution is the best.
You are right to set
isComposingto false in the last input has weird semantics and will likely lead to confusion. But I also don't really would expect that extra input event with composing=false to fire after the composition has ended as Firefox is doing it now.Can we find a solution that follows the principle of least surprise (no surprising extra events, no changing isComposing property) that just lets a user check inside beforeinput whether they are at the very beginning, somewhere in the middle, or at the very end of a composition?
Interesting. Then, isn't it already broken on Firefox?
Yeah now that you say it... I don't see that extra input event with isComposing false in Firefox in my app after a composition finished. So no double insertion. Will update if I find out why that is.
Call 2026-05-14:
Input Events Behavior (Issue 176): Johannes Wilm discussed issue 176
regarding input events, noting that Firefox triggers an extra input
event at the end of composition, which some developers are mitigating in
their code. The participants agreed that a standardized behavior is
necessary, as relying on GitHub discussions is insufficient for
developers. Rakesh Goulikar committed to delegating the task of
investigating this behavior on Android and other platforms, with an
update expected at the next meeting.Wanted to re-state the requirements (without any solution preference, pls correct me if I made any wrong assumption)
As an editor developer during
onbeforeinputoroninputwhenevent.isComposingistrueI want to know wether the new input is:- right at the beginning of a composition (when typed "s" on Japanese keyboard)
- in the middle of a composition (when typed "i" right after)
- at the end of a composition (selected the final candidate and committed with ENTER, e.g. "市")
Call 2026-05-14:
Input Events Behavior (Issue 176): Johannes Wilm discussed issue 176
regarding input events, noting that Firefox triggers an extra input
event at the end of composition, which some developers are mitigating in
their code. The participants agreed that a standardized behavior is
necessary, as relying on GitHub discussions is insufficient for
developers. Rakesh Goulikar committed to delegating the task of
investigating this behavior on Android and other platforms, with an
update expected at the next meeting.I'm yet to find the right poc for this, will keep updates posted.
- removedAgenda+Queue this item for discussion at the next WG meetingQueue this item for discussion at the next WG meeting
on Jun 11, 2026 - right at the beginning of a composition (when typed "s" on Japanese keyboard)
- in the middle of a composition (when typed "i" right after)
The need for strict distinction between these two is not particularly great. What matters far more is the distinction between the end of composition and any other phase within composition.
Loadingflowchart TD Start([onInput]) --> Q1{isComposition is} Q1 -- false --> ContentChanged["Content changed"] Q1 -- true --> Q2{Is composition ending?} Q2 -- Yes --> ContentChanged Q2 -- No --> Ignore["Ignore event"](when typed "i" right after)
(when typed "i" right after, or pressed Space, or cycled through candidates)
The need for strict distinction between these two is not particularly great.
Of course, there's no harm in being able to distinguish between them.

You get the completely same kind of
inputevents in 2–6 where:isComposingistrueinputTypeisinsertCompositionTextA different type should be assigned to 6.
How can we notice the end of composition without combining with
compositionEnd?isComposinginonInputistrueeven when it is the finish of the IME composition / NoonInputwhoseisComposingisfalseis triggered when you complete IME composition uievents#394Another approach (adding additional property to
InputEvent):InputEventto determine whether the text being input by IME has just been confirmed without help ofcompositionenduievents#403