Skip to content

insertCompositionText (and isComposing) cannot tell the input event accompanied by compositionEnd from the other WIP IME input events #176

Description

@tats-u
  1. Enable Japanese IME
  2. Type s
  3. Type i
  4. Press Space to convert and get e.g. "詩"
  5. Change the candidate to another one, e.g. "市"
  6. Press Enter to Commit

You get the completely same kind of input events in 2–6 where:

  • isComposing is true
  • inputType is insertCompositionText

A different type should be assigned to 6.
How can we notice the end of composition without combining with compositionEnd?

Another approach (adding additional property to InputEvent):

Activity

  1. added
    Agenda+Queue this item for discussion at the next WG meeting
    on Oct 9, 2025
  2. smaug---- commented on Oct 9, 2025

    @smaug----
  3. tats-u commented on Oct 13, 2025

    @tats-u
    Author

    Another approach is to append a new additional property to inputEvent.

  4. johanneswilm commented on Oct 13, 2025

    @johanneswilm
    Contributor

    @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.

  5. tats-u commented on Oct 13, 2025

    @tats-u
    Author

    @johanneswilm Something like isConfirmed.
    false only in 2–5. true otherwide (including when you input or delete some text without help of IME).

  6. tats-u commented on Oct 13, 2025

    @tats-u
    Author

    Chrome's example (s i → し → 市 → 死 → Confirm)
    Image

    It should be false in 5, 11 ,17 , and 23. However it should be true in 29.

  7. tats-u commented on Oct 13, 2025

    @tats-u
    Author

    I created a new issue related to it to uievents.
    You might as well choose one and close the other.

  8. johanneswilm commented on Oct 13, 2025

    @johanneswilm
    Contributor

    @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 false in 5, 11 ,17 , and 23. However it should be true in 29.

    What should it be in 28?

  9. tats-u commented on Oct 13, 2025

    @tats-u
    Author

    onBeforeInput? I have not used it yet and in the first place I do not know when we should use this event instead of onInput. In my opinion true is preferred but I am not so sure.

  10. johanneswilm commented on Oct 13, 2025

    @johanneswilm
    Contributor

    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: beforeInput comes before the DOM change and input comes after the DOM change. A lot of beforeInput events can be cancelled, which means that the browser will then not do the DOM change and there will not be an input event. Some beforeInput events cannot be cancelled (related to composition), which means the DOM change will take place under all circumstances. In that case, the beforeInput event can be used in order to note what the DOM looked like before the DOM change took place.

    The new EditContext API can be used instead of contenteditable in order to get around the issue of these non-cancellable beforeInput.

  11. tats-u commented on Oct 13, 2025

    @tats-u
    Author

    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 existing isComposing. isConfirmed can be simply defined as "!isComposing || [the key input that triggered the event will also trigger compositionend]" if we define isConfirmed there as true.

    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).

  12. johanneswilm commented on Oct 13, 2025

    @johanneswilm
    Contributor

    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 existing isComposing.

    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?

    isConfirmed can be simply defined as "!isComposing || [the key input that triggered the event will also trigger compositionend]" if we define isConfirmed there as true.

    Are you saying that isConfirmed should be equal to false in all cases unless it is the (beforeInput/)input event that is the last during a composition that commits the final text to the DOM?

    What is the reason why we cannot just make isComposing false 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.

  13. tats-u commented on Oct 13, 2025

    @tats-u
    Author

    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 true but 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?

    Image

    ↑Partially committed "日本語" first and then commited "大好き" (Chrome / onKeyDown is filtered out)

    I do not know about onBeforeInput in 78.

    Also, I have just found that input/beforeinput is 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).

  14. masayuki-nakano commented on Oct 21, 2025

    @masayuki-nakano

    This is a dup of w3c/uievents#202

    The isConfirmed is an opposite property of isComposing. So I think it's redundant. And changing inputType value could cause breaking some web apps.

    FYI: Firefox is now dispatches 2 input events around compositionend against a real web-compat issue. One is immediately before compositionend with isComposing=true. Then, the other is immediately after compositionend with isComposing=false. This approach breaks the 1on1 relation between beforeinput and input, but works in the wild for now.

  15. tats-u commented on Oct 21, 2025

    @tats-u
    Author

    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.

  16. 15 remaining items

  17. johanneswilm commented on Mar 12, 2026

    @johanneswilm
    Contributor

    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 Masayuki

  18. smaug---- commented on Mar 12, 2026

    @smaug----

    08: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?

  19. self-assigned this
    on Mar 12, 2026
  20. michael commented on Apr 9, 2026

    @michael

    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.hasCompositionStarted and event.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.hasCompositionStarted and event.hasCompositionEnded this 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?

  21. johanneswilm commented on Apr 9, 2026

    @johanneswilm
    Contributor

    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…

  22. masayuki-nakano commented on Apr 10, 2026

    @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.

    Yes, might be. However, if input event is handled without isComposing, a lot of duplication must happen during a composition because multiple input events are usually fired during a composition. So, unless web apps handle input event 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 isComposing of beforeinput to false because 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.hasCompositionStarted and event.hasCompositionEnded this 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.

  23. michael commented on May 14, 2026

    @michael

    Thank you for your inputs @masayuki-nakano. I'm also still undecided which solution is the best.

    You are right to set isComposing to 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.

  24. johanneswilm commented on Jun 11, 2026

    @johanneswilm
    Contributor

    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.

  25. michael commented on Jun 11, 2026

    @michael

    Wanted to re-state the requirements (without any solution preference, pls correct me if I made any wrong assumption)


    As an editor developer during onbeforeinput or oninput when event.isComposing is true I 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. "市")
  26. ragoulik commented on Jun 11, 2026

    @ragoulik

    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.

  27. removed
    Agenda+Queue this item for discussion at the next WG meeting
    on Jun 11, 2026
  28. tats-u commented on Jun 13, 2026

    @tats-u
    Author
    • 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.

    flowchart 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"]
    
    Loading

    (when typed "i" right after)

    (when typed "i" right after, or pressed Space, or cycled through candidates)

  29. tats-u commented on Jun 13, 2026

    @tats-u
    Author

    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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions