Open masayuki-nakano opened 6 years ago
So, I think that isComposing of "beforeinput" followed by "compositionend" should be true, but isComposing of "input" event caused by committing composition should be false.
The problem with this is that you cannot detect anymore whether the input that caused compositionend
to happen was itself done while composing.
The problem with this is that you cannot detect anymore whether the input that caused compositionend to happen was itself done while composing.
Is there a particular use case you have in mind where you need to make this distinction?
Another possible solution is, UI Events would change the declaration of InputEvent.isComposing.
It's currently declared as "true if the input event occurs as part of a composition session, i.e., after a compositionstart event and before the corresponding compositionend event.", however, if it's declared as the value of input events which are fired for committing composition is set to false, web apps need to check only input events whose isComposing is false (like running on Firefox and Edge).
@Kovensky What do you think of the proposal that we match the order of WebKit/Blink (and now the spec) but make the last input
event before the compositionend
event, have isComposing
be set to false
?
I think that would allow making the distinction you mentioned and, like @masayuki-nakano mentions, would mean that for many apps that want to ignore input during composition, they can simply ignore input
events when isComposing
is true
without having to track state using compositionstart
and compositionend
.
@garykac?
I remember having issues with Japanese IME input because the Enter
key is typically what is used to trigger compositionend
, but if it comes with isComposing: false
, I can no longer tell if that Enter
key was used to exit composition or to actually write a new line.
This was a few months ago, though, I don't remember if/how that was dealt with.
That's a fair point. I hadn't thought about multi-line input so much.
So with the proposal to make isComposing
false for the last input
event before the (last?) compositionend
the author:
compositionstart
/ compositionend
in order to ignore inputs while composing, butcompositionstart
/ compositionend
in order to differentiate between an input that commits part of the composition, and an input that is not part of composition.Does that sound right?
(I'm actually a bit confused now about if this proposal works in light of multiple compositionend
events. The IMEs I use on Linux and Windows don't allow committing part of a composition string so I never get more than one compositionend
event.)
Currently, DOM composition events do not support of committing a part of composition. In such case, Firefox commits existing composition first, then, restart composition with new range.
Brian, you can check this behavior even with MS-IME.
And also, new line key handling is not defined under UI Events unfortunately. In most cases, Enter key press during composition of Japanese IME just commits composition. However, for example, Enter key press during composition of Korean IME causes both committing composition and inserting a new line. If all browsers support beforeinput, the new line input can be represented with it which should be fired immediately after compositionend. However, now, Firefox dispatches a set of key events (i.e., keydown, keypress and keyup events) after compositionend on Windows, and dispatches only keypress event after compositionend on macOS. So, this issue should be out of scope of this.
Currently, DOM composition events do not support of committing a part of composition. In such case, Firefox commits existing composition first, then, restart composition with new range.
Oh, I see. The spec seems to suggest there should be no extra compositionstart
:
Note that while every composition only has one compositionstart event, it may have several compositionend events.
But I notice neither Chrome nor Firefox do that.
For my reference I am testing using this pen: https://codepen.io/birtles/pen/pZwNPx
Following the stems from Masayuki for MS-IME:
- Open preferences dialog
- Press key assign from "Microsoft IME" to "ATOK"
- Type something which may cause 2 or more clauses.
- Type Space bar to convert.
- Type ArrowDown key.
I get in Firefox:
compositionstart (value: )
input (value: k, isComposing: true)
input (value: こ, isComposing: true)
input (value: こr, isComposing: true)
input (value: これ, isComposing: true)
input (value: これh, isComposing: true)
input (value: これは, isComposing: true)
input (value: これはt, isComposing: true)
input (value: これはて, isComposing: true)
input (value: これはてs, isComposing: true)
input (value: これはてす, isComposing: true)
input (value: これはてすt, isComposing: true)
input (value: これはてすと, isComposing: true)
input (value: これはテスト, isComposing: true)
compositionend (value: これは)
input (value: これは, isComposing: false)
compositionstart (value: これは)
input (value: これはテスト, isComposing: true)
compositionend (value: これはテスト)
input (value: これはテスト, isComposing: false)
In Chrome:
compositionstart (value: )
input (value: k, isComposing: true)
input (value: k, isComposing: true)
input (value: こ, isComposing: true)
input (value: こ, isComposing: true)
input (value: こr, isComposing: true)
input (value: こr, isComposing: true)
input (value: これ, isComposing: true)
input (value: これ, isComposing: true)
input (value: これ, isComposing: true)
input (value: これh, isComposing: true)
input (value: これh, isComposing: true)
input (value: これは, isComposing: true)
input (value: これは, isComposing: true)
input (value: これはt, isComposing: true)
input (value: これはて, isComposing: true)
input (value: これはて, isComposing: true)
input (value: これはてs, isComposing: true)
input (value: これはてs, isComposing: true)
input (value: これはてす, isComposing: true)
input (value: これはてす, isComposing: true)
input (value: これはてすt, isComposing: true)
input (value: これはてすt, isComposing: true)
input (value: これはてすと, isComposing: true)
input (value: これはてすと, isComposing: true)
input (value: これはテスト, isComposing: true)
input (value: これは, isComposing: true)
compositionend (value: これは)
compositionstart (value: これは)
input (value: これはテスト, isComposing: true)
input (value: これはテスト, isComposing: true)
compositionend (value: これはテスト)
Edge:
compositionstart (value: )
input (value: k, isComposing: undefined)
input (value: こ, isComposing: undefined)
input (value: こr, isComposing: undefined)
input (value: これ, isComposing: undefined)
input (value: これh, isComposing: undefined)
input (value: これは, isComposing: undefined)
input (value: これはt, isComposing: undefined)
input (value: これはて, isComposing: undefined)
input (value: これはてs, isComposing: undefined)
input (value: これはてす, isComposing: undefined)
input (value: これはてすt, isComposing: undefined)
input (value: これはてすと, isComposing: undefined)
input (value: これはテスト, isComposing: undefined)
compositionend (value: これはテスト)
If some Korean IMEs allow Enter to do double-duty as committing a composition and entering a newline then it sounds like distinguishing between the two actions is a separate matter as you say. If beforeinput
lets authors recognize a genuine newline (I didn't quite understand this part but would love to know how this work) then that's even better.
So it sounds like the proposal to make the last input event before the compositionend event have isComposing
be set to false could work.
Sorry for the delays responding on that issue. I've been finishing other things up and am just now starting to be able to spend more time on UIEvents.
To summarize my understanding:
input
events generated during composition, but they want the final input
event (effectively, they want to consider the composition as a single input action).compositionend
to know that the previous input
event was the final one.isComposing
to false for this final input
event (just for the last one before compositionend
).Enter
that ends composition and one that adds a newline.Regarding that last concern, I just tested this on Chrome Win and Mac with Korean and Japanese IMEs and noticed the following events after pressing Enter
to accept the composition:
keydown
/ beforeinput
/ c-update
/ input
/ c-end
/ keyup
to close the composition
inputType
for the input
event is insertCompositionText
c-end
/ input
/ keyup
to close the composition. (inputType
is not set)c-end
to close the composition. (inputType
is not set)c-update
/ input
/ c-end
to close the composition and follows it with keydown
/ keypress
/ beforeinput
/ input
/ keyup
for the Enter
key to add a newline
inputType
is insertCompositionText
inputType
is insertParagraph
c-end
/ input
followed by keydown
/ keypress
/ input
/ keyup
. (inputType
is not set)c-end
followed up keydown
/ keypress
/ input
/ keyup
. (inputType
is not set)So (from this limited testing), I don't think there's a problem with distinguishing the final Enter
of a composition from the newline (esp. when inputType
is set).
As for the proposal, other than it makes the spec description more complex, I don't see serious problems with it. But I also don't have a sense of how serious the original problem is.
The tradeoff we're making is between:
isComposing
is set when composing, EXCEPT when..." (ugh)input
events during composition (since they have to add an event handler for compositionend
).But it doesn't seem (to me) like it's very common to need to distinguish between input events that are inside or outside composition. The vast majority of devs won't care. The one case I can think of where someone might want to do this is for devs writing rich text editors, but I don't consider it a problem if they have to write a bit more code to get things done - they're probably handling compositionend
already.
Unless there's a real dev need to fix this, I'd rather keep the definition for isComposing
simple.
Note that while every composition only has one compositionstart event, it may have several compositionend events.
I was wondering where this came from (it's not in the UIEvents spec) and finally found it as a non-normative note in Input Events spec (in Step 5) (https://www.w3.org/TR/input-events-2/).
AIUI, that is wrong. compositionstart
and compositionend
are supposed to be matching pairs. I'm not aware of any situation where there should be multiple compositionend
events in a single composition session.
The UIEvents spec says (https://www.w3.org/TR/uievents/#events-compositionevents):
Conceptually, a composition session consists of one compositionstart event, one or more compositionupdate events, and one compositionend event, with the value of the data attribute persisting between each stage of this event chain during each session.
Thanks for following up on this!
The tradeoff we're making is between:
- Making the spec's description more complex: "isComposing is set when composing, EXCEPT when..." (ugh)
- Making things slightly harder for devs who need to ignore input events during composition (since they have to add an event handler for compositionend).
That priority of constituencies here surely means we should choose the latter (i.e. devs > spec writers).
But it doesn't seem (to me) like it's very common to need to distinguish between input events that are inside or outside composition. The vast majority of devs won't care.
I disagree. @masayuki-nakano has already indicated that it is common to make this distinction in Firefox source. In my work on Web apps too, I frequently need to make this distinction. Some common examples include:
- Making the spec's description more complex: "
isComposing
is set when composing, EXCEPT when..." (ugh)
How about: "isComposing
is true when the text is still composing by composition" or something similar? I'm trying to say, when isComposing
is true, its data may be updated/replaced during current composition. So, the data hasn't been fixed yet.
- Making things slightly harder for devs who need to ignore
input
events during composition (since they have to add an event handler forcompositionend
).
I have no idea what the use-case of ignoring all input caused by composition. I suggested current isComposing
but I intended to make input
event useful for suggesting use-case because I didn't realize the event order difference between browsers (input
vs. compositionend
).
The tradeoff we're making is between:
Making the spec's description more complex: "isComposing is set when composing, EXCEPT when..." (ugh) Making things slightly harder for devs who need to ignore input events during composition (since they have to add an event handler for compositionend).
That priority of constituencies here surely means we should choose the latter (i.e. devs > spec writers).
I phrased that poorly. My concern is not for the spec writer (who only has to write it once), but for anyone who has to read the spec. A more complex spec is more complex for anyone who has to read it or any follow-on documentation (like MDN) that are based on the spec.
But it doesn't seem (to me) like it's very common to need to distinguish between input events that are inside or outside composition. The vast majority of devs won't care.
I disagree. @masayuki-nakano has already indicated that it is common to make this distinction in Firefox source.
But needing to make this distinction in FF code isn't a good reason to expose this to the web platform.
In my work on Web apps too, I frequently need to make this distinction. Some common examples include:
- If you are triggering a search as the user types, you don't normally want to do that search until the string has been committed since, in the case of Japanese, you'll be searching on the hiragana instead of the kanji so it will likely be wasted work and distracting to the user.
- If you're doing validation on data as it is typed, it's quite common that the uncommitted string will be invalid while the committed string will be valid and you don't want to annoy the user with "data invalid" UI while they're still composing. (As a concrete example, consider an app that checks an input station name is valid. While the string is uncommitted it will likely be incomplete or in hiragana but once committed it will typically be valid.)
- If you're doing auto-save based on user input, it would likely be wasted work to save before the string is committed.
These are compelling examples. Thanks for taking the time to write them up. These are good cases for motivating this change.
Would it be sufficient to phrase this as something like:
"During composition, the isComposing
attribute should be true
for all input
events except for the final one that updates the DOM."
AUIU, if the user cancels composition, the final input
would be a delete to remove the previous
candidate. I think this is fine. I can't think of any other cases that might be problematic.
Does the final beforeinput
event need to have isComposing
set in the same manner? My instinct is "yes, it should", but I'm open to arguments why is doesn't need to be.
Thanks for following up on this.
But it doesn't seem (to me) like it's very common to need to distinguish between input events that are inside or outside composition. The vast majority of devs won't care.
I disagree. @masayuki-nakano has already indicated that it is common to make this distinction in Firefox source.
But needing to make this distinction in FF code isn't a good reason to expose this to the web platform.
Yes, sorry, by FF code I meant Firefox front-end code, i.e. the JS used for the FF user interface. While it's not entirely representative of typical Web content, for the purposes of handling input events I think it's reasonably close.
Would it be sufficient to phrase this as something like: "During composition, the isComposing attribute should be true for all input events except for the final one that updates the DOM."
I'm not familiar with the correct terminology here but that seems to match the intention at least. @masayuki-nakano does this sound right? The idea behind this language is that it then covers the cancel case too.
Does the final beforeinput event need to have isComposing set in the same manner? My instinct is "yes, it should", but I'm open to arguments why is doesn't need to be.
@masayuki-nakano what do you think about this? ↑
Yes, sorry, by FF code I meant Firefox front-end code, i.e. the JS used for the FF user interface. While it's not entirely representative of typical Web content, for the purposes of handling input events I think it's reasonably close.
Firefox's front-end is one of the biggest web-applications in the world even though it uses some special API to access sensitive area. So, I believe that if some spec changes make Firefox's front-end more complicated, that means that web apps in the world are also made complicated if they need to implement similar feature.
Does the final beforeinput event need to have isComposing set in the same manner? My instinct is "yes, it should", but I'm open to arguments why is doesn't need to be.
@masayuki-nakano what do you think about this? ↑
Honestly, I'm still not sure, I'm still thinking this...
From a point of view of web browser, browser has not been handled native commit event yet at dispatching beforeinput
event. I.e., there is a composition in the editor. So, setting false to isComposing of beforeinput
may make sense.
On the other hand, beforeinput
is not useful event if there were no inputType
attribute value since web apps cannot know what will happen without the attribute. So, in other words, I have no idea how web apps use only isComposing
information of beforeinput
.
So, most important thing is, which value can prevent bugs of web apps...
Today Masayuki and I discussed whether or not beforeinput
should also set isComposing
to false on the last event before updating the DOM. I'll summarize what I recall and Masayuki can correct me where I'm wrong.
Argument for making isComposing
true:
I think Masayuki was originally concerned that setting it to false
would lead to bugs in Web apps. For example, while in a Japanese IME the final composition string is typically equal to the committed string, in Chinese IMEs it can differ (e.g. the string being composed might be all alphabetic and at the moment it is committed, it is converted to Chinese characters).
If Web apps are waiting for isComposing === false
and then taking some action that results in the DOM being updated and the composition canceled then they might arrive at a state where the string being composed remains, and not the final string that should have been committed.
(I'm a bit vague about this concern but hopefully Masayuki can clarify this point.)
Argument for making isComposing
false:
beforeinput
and input
are a conceptual set, so if input
has isComposing === false
then conceptually one would expect the corresponding beforeinput
event to also have isComposing === false
.
Despite the concern mentioned above, given that the beforeinput
events with inputType === 'insertFromComposition'
are cancelable, there's probably no extra harm having apps trigger other actions at this point--at least from the UA's point of view, they need to be able to safely handle canceling at this point anyway.
(I'm a bit vague about this concern but hopefully Masayuki can clarify this point.)
No, you understand my arguing points perfectly.
So, beforeinput
is NOT useful only with the fact that it's fired since beforeinput
is fired before the change affects the value of focused editor. So, inputType
is really important for beforeinput
event listeners. I.e., I have no idea how important that isComposing
is referred by beforeinput
listeners. If a event listener listens both input
and beforeinput
events, isComposing
of beforeinput
may be referred. However, I have no idea what such listeners need to do.
I ran into this again, but this time not even with input
or beforeinput
events, but with keydown
events which is supposed to fire even earlier than beforeinput
(need to watch the state of modifier keys so input
events are not low level enough).
In Safari, compositionend
fires even before the keydown
with the Enter that commits the IME input is fired, which causes isComposing
to be wrong there too. The only way I have to tell if that Enter key was actually an input to the IME is to look at the deprecated which
property and check if it's 229
or 13
.
@Kovensky I'm not sure I understand what you're suggesting here. Are you endorsing the importance of this issue or suggesting a particular value of isComposing
?
It is the importance of this issue, although this kinda looks like a Safari bug instead. The value of isComposing
is true
, by definition, before compositionend
; the problem here is that Safari is emitting compositionend
way before when it's supposed to be emitted. It's not even just that it's before input
, it's happening even before keydown
.
But it doesn't seem (to me) like it's very common to need to distinguish between input events that are inside or outside composition. The vast majority of devs won't care.
Most web apps don't do text editing at all, and those 99% won't care, I agree. But what we need to look at are the 1% (or less) of devs working on editing apps. Without such editing apps, there is no content on the web so even if the percentage is low, it does matter for the web overall.
Those web apps doing text editing need to be concerned with the difference between composing and non-composing as they are prohibited from changing the DOM around the composition while it takes place. So as long as that is clearly communicated in some way, I think everything is fine.
If Web apps are waiting for
isComposing
=== false and then taking some action that results in the DOM being updated and the composition canceled then they might arrive at a state where the string being composed remains, and not the final string that should have been committed.
If isComposing
is set to false
, while it really should be true
and it does cause IMEs to be canceled half-way, then the web apps will stop checking that attribute and will be looking for some other indication of whether the composition has finished.
That is not really ideal as we are then moving away from the idea that you can build an editor by listening to beforeinput
events and go back to the kind of patchwork that editors need to go through today.
Some web apps want to be able to ignore the intermediate input events generated during composition, but they want the final input event (effectively, they want to consider the composition as a single input action).
I believe when we originally wrote the level 2 input events spec, our information was that the last input event may not give us information about the contents and placement of the entire composition string. And also at that start, when a composition is initiated on a pre-existing string, we would not know where that string is. This is why we added the cancelable beforeinput
events with inputtypes deleteByComposition
(before composition) and insertFromComposition
(after composition).
Without those events, as in level 1, the only important piece of information we can listen for is when the composition has finished. Once that has happened, we diff the DOM and find out what has changed, based on that make a guess on what the user wanted to enter/delete, and then act upon that.
So unless something has changed so that one can always know where the entire string was placed based on the information of the last input event during composition and what has changed, I don't know if this is of much help to webapps.
Even if browser only supports Input Events Level 1 (like Firefox and Chrome), input
events during composition has insertCompositionText
as inputType
value. Therefore, even if InputEvent.isComposing
is set to false
during composition, web apps can distinguish whether every change is caused by IME or not.
I came across this as a problem for my own use case where I discovered that Safari/Chrome were triggering compositionend
before keydown
including for the ENTER keypress which concluded composition - resulting in a KeyboardEvent
where the code is "Enter" and isComposing
is false
- but that enter keypress was - in fact - used to conclude a composition.
I don't have the level of expertise to contribute towards a solution here, but it seems that there was some productive conversation in this thread which dried out and didn't end up leading anywhere. Is this viewed as a non-issue? What should developers be doing to avoid triggering incorrect events on keyboard events which are actually due to international character composition?
Yes, it would be good to reach consensus on this. I believe we were proposing to change the value of isComposing
for input
events (and perhaps beforeinput
) but that will depend on how willing Chrome/Safari are to make that change.
For what it's worth I also often encounter this issue, e.g. this code which is precisely the sort of code Masayuki mentioned at this beginning of this issue.
It seems like there are good arguments here for Chromium and Safari converging to Firefox's behavior and updating the spec accordingly.
@mustaqahmed, would you be the right owner for this for Chrome, or do you know who could make that call?
@annevk is this something you'd be able to comment on for Safari?
How often are these events used? I'd be a bit worried about web compatibility, but perhaps with Chromium and WebKit changing this around the same time that can be mitigated somewhat.
cc @whsieh
@garykac: do you know who could answer the Chrome question above?
How often are these events used?
Pretty often. Apart from the issues linked to this one, there are plenty of blogs of people stumbling due to this, e.g.:
Just trying to distinguish an Enter keypress to commit a selection from a regular Enter keypress (e.g. to close a modal) is really hard in Safari. (The most reliable way being to use the deprecated keyCode
property.)
Currently, UI Events declares event order of "compositionend" and "input" as:
At https://w3c.github.io/uievents/#events-composition-input-events
However, Firefox and Edge's order is:
I.e., "input" event is fired after "compositionend".
On the other hand, Chrome and Safari dispatches as the spec declared.
I tried to make Firefox take the same event order as the draft in https://bugzilla.mozilla.org/show_bug.cgi?id=1305387. However, after changing the order, I needed following change in some places even in Firefox's UI: https://hg.mozilla.org/try/rev/9dbba7ff1d6f7f72a33dccc93fdda60f04d9942e
Then, I feel that the event order of Chrome, Safari and the spec does not make sense for web application developers. The reason is, if all browsers use the event order of Firefox and Edge, isComposing value of "input" event becomes false. So, if a web application needs to do something at every input except during composition, web application needs to listen to only input event if it's run on Firefox or Edge. I.e., can be implemented like:
On the other hand, it's run on Chrome or Safari, needs to be implemented like:
I believe that the behavior of Chrome, Safari and UI Events may make web applications not aware of IME.
From point of view of browser developer, this behavior is easier to implement actually since browser does not check whether it's followed by compositionend event when it tries to fire "input" event at every composition change. However, usefulness for web application developers must be more important than easier to implement by browsers.
What do you think, @garykac ?