Skip to content

web: Implement basic IME #19896

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open

Conversation

kjarosh
Copy link
Member

@kjarosh kjarosh commented Mar 23, 2025

This patch implements IME preediting and committing on web. It does not
implement moving the cursor and proper positioning yet.

@kjarosh kjarosh added A-web Area: Web & Extensions text Issues relating to text rendering/input input Issues relating to user input in Flash content T-compat Type: Compatibility with Flash Player ime Issues related to Input Method Editor labels Mar 23, 2025
@kjarosh kjarosh added the waiting-on-review Waiting on review from a Ruffle team member label Mar 23, 2025
@danielhjacobs
Copy link
Contributor

This isn't working on Android when I try to type in an EditText with the virtual keyboard.

@danielhjacobs
Copy link
Contributor

A virtual keyboard like the one on Android isComposing.

@danielhjacobs
Copy link
Contributor

To explain the current logic, which I guess may need comments.

  1. If you type a single character using the virtual keyboard, that character fires a keydown and then keyup event into the focused EditText.
  2. If you backspace or delete a single character using the virtual keyboard, that backspace/delete fires a keydown and then keyup event into the focused EditText.
  3. If you paste a string using the virtual keyboard, each character in that string in sequence fires a keydown and then keyup event into the focused EditText.

@danielhjacobs
Copy link
Contributor

I'm guessing the IME logic can be used to support this more properly, but landing this PR without IME support would regress the virtual keyboard.

@danielhjacobs
Copy link
Contributor

danielhjacobs commented Mar 24, 2025

Maybe we can do exactly this but also keydown and keyup the event.data character(s) on compositionend.

@kjarosh kjarosh force-pushed the web-basic-ime branch 2 times, most recently from a00d15d to 2af747a Compare March 29, 2025 21:07
@kjarosh
Copy link
Member Author

kjarosh commented Mar 29, 2025

@danielhjacobs can you check if the current code works properly? It does for me

@danielhjacobs
Copy link
Contributor

Tried with GBoard and it worked perfectly. With Samsung Keyboard it's unfortunately a different story, see recording.

Screen_Recording_20250329_171917_Chrome.online-video-cutter.com.mp4

@kjarosh
Copy link
Member Author

kjarosh commented Mar 29, 2025

It seems that this keyboard uses IME for inputting all text. Without implementing IME on web we cannot have both IME preview and IME input working :/ This PR breaks IME preview, but fixes IME input.

@kjarosh kjarosh changed the title web: Ignore IME composing events when inputting text web: Implement basic IME Mar 30, 2025
@kjarosh
Copy link
Member Author

kjarosh commented Mar 30, 2025

Okay, as IME on web is a mess, I've decided to implement basic IME mechanics (including preediting) on web.

@danielhjacobs @n0samu You can test it out here: https://kjarosh.github.io/ruffle/pr19896/

@kjarosh kjarosh added A-input Area: Input handling and removed A-web Area: Web & Extensions input Issues relating to user input in Flash content labels Mar 31, 2025
This patch implements IME preediting and committing on web. It does not
implement moving the cursor and proper positioning yet.
@danielhjacobs
Copy link
Contributor

As stated on Discord, but putting here for future people, this is now working with Samsung Keyboard.

@kjarosh
Copy link
Member Author

kjarosh commented Apr 1, 2025

@jmousy Could you check if it works properly? It's available at https://kjarosh.github.io/ruffle/pr19896/

@jmousy
Copy link
Contributor

jmousy commented Apr 2, 2025

This is a summary of the chat on Discord.
Tested across OS and keyboard software on the same text and flash file.

  • '*' is cursor
  • 'Default' means using a regular hardware keyboard.
  • This is the result when you type "가나다" (rkskek, ㄱ + ㅏ + ㄴ + ㅏ + ㄷ + ㅏ).
  • Tested with the following flash files: 흥해라편의점.zip
  • Windows: Windows 10 24H2 & Google Chrome 135
  • macOS: macOS 15.3.2 & Google Chrome 135
  • iOS: iPhone 13 Pro iOS 18.4 & Safari
  • Android: Samsung Galaxy Z Fold 6 & Android 14 & One UI 6.1.1 & Samsung Internet
  • Linux: Ubuntu 24.04.2 LTS & Firefox latest (with VMware virtual machine)
OS Keyboard Type Result Other Issue
iOS Default ㄱㅏㄴㅏㄷㅏ*
iOS GBoard The text you entered appears in the following order and then disappears: ㄱ-가-간-{empty}-낟-{empty}
Android Samsung Keyboard 가나다*
Android GBoard 가나다* If you try to type "123가나다", it will be entered as "123ㄱㅏ나다".
Windows Default 가나다* If you click on the screen after entering text "가나다", an extra "다" is entered: "가나다다"
macOS Default ㄱ가나다*ㅏㄷㅏㄴㅏ The cursor will be positioned after "다" not the end. Also, if you type only "ㄱ", it will output "ㄱㄱ".
Linux Default 가나다*

@jmousy
Copy link
Contributor

jmousy commented Apr 4, 2025

Below is the output from the link below, as requested by Daniel Jacobs on Discord:
https://codepen.io/danieljacobs/pen/ByaMLpL

Input text: '가나다' (rkskek)

iOS 18.4 (default keyboard)
keydown - key: ㄱ, data: N/A, inputType: N/A, isComposing: N/A
keypress - key: ㄱ, data: N/A, inputType: N/A, isComposing: N/A
beforeinput - key: N/A, data: ㄱ, inputType: insertText, isComposing: N/A
input - key: N/A, data: ㄱ, inputType: insertText, isComposing: N/A
keyup - key: ㄱ, data: N/A, inputType: N/A, isComposing: N/A
keydown - key: ㅏ, data: N/A, inputType: N/A, isComposing: N/A
keypress - key: ㅏ, data: N/A, inputType: N/A, isComposing: N/A
beforeinput - key: N/A, data: ㅏ, inputType: insertText, isComposing: N/A
input - key: N/A, data: ㅏ, inputType: insertText, isComposing: N/A
keyup - key: ㅏ, data: N/A, inputType: N/A, isComposing: N/A
keydown - key: ㄴ, data: N/A, inputType: N/A, isComposing: N/A
keypress - key: ㄴ, data: N/A, inputType: N/A, isComposing: N/A
beforeinput - key: N/A, data: ㄴ, inputType: insertText, isComposing: N/A
input - key: N/A, data: ㄴ, inputType: insertText, isComposing: N/A
keyup - key: ㄴ, data: N/A, inputType: N/A, isComposing: N/A
keydown - key: ㅏ, data: N/A, inputType: N/A, isComposing: N/A
keypress - key: ㅏ, data: N/A, inputType: N/A, isComposing: N/A
beforeinput - key: N/A, data: ㅏ, inputType: insertText, isComposing: N/A
input - key: N/A, data: ㅏ, inputType: insertText, isComposing: N/A
keyup - key: ㅏ, data: N/A, inputType: N/A, isComposing: N/A
keydown - key: ㄷ, data: N/A, inputType: N/A, isComposing: N/A
keypress - key: ㄷ, data: N/A, inputType: N/A, isComposing: N/A
beforeinput - key: N/A, data: ㄷ, inputType: insertText, isComposing: N/A
input - key: N/A, data: ㄷ, inputType: insertText, isComposing: N/A
keyup - key: ㄷ, data: N/A, inputType: N/A, isComposing: N/A
keydown - key: ㅏ, data: N/A, inputType: N/A, isComposing: N/A
keypress - key: ㅏ, data: N/A, inputType: N/A, isComposing: N/A
beforeinput - key: N/A, data: ㅏ, inputType: insertText, isComposing: N/A
input - key: N/A, data: ㅏ, inputType: insertText, isComposing: N/A
keyup - key: ㅏ, data: N/A, inputType: N/A, isComposing: N/A
Android 14 (samsung keyboard)
keydown - key: Unidentified, data: N/A, inputType: N/A, isComposing: N/A
compositionstart - key: N/A, data: N/A, inputType: N/A, isComposing: N/A
compositionupdate - key: N/A, data: ㄱ, inputType: N/A, isComposing: N/A
beforeinput - key: N/A, data: ㄱ, inputType: insertCompositionText, isComposing: true
input - key: N/A, data: ㄱ, inputType: insertCompositionText, isComposing: true
keyup - key: Unidentified, data: N/A, inputType: N/A, isComposing: true
keydown - key: Unidentified, data: N/A, inputType: N/A, isComposing: true
compositionupdate - key: N/A, data: 가, inputType: N/A, isComposing: N/A
beforeinput - key: N/A, data: 가, inputType: insertCompositionText, isComposing: true
input - key: N/A, data: 가, inputType: insertCompositionText, isComposing: true
keyup - key: Unidentified, data: N/A, inputType: N/A, isComposing: true
keydown - key: Unidentified, data: N/A, inputType: N/A, isComposing: true
compositionupdate - key: N/A, data: 간, inputType: N/A, isComposing: N/A
beforeinput - key: N/A, data: 간, inputType: insertCompositionText, isComposing: true
input - key: N/A, data: 간, inputType: insertCompositionText, isComposing: true
keyup - key: Unidentified, data: N/A, inputType: N/A, isComposing: true
keydown - key: Unidentified, data: N/A, inputType: N/A, isComposing: true
compositionupdate - key: N/A, data: 가, inputType: N/A, isComposing: N/A
beforeinput - key: N/A, data: 가, inputType: insertCompositionText, isComposing: true
input - key: N/A, data: 가, inputType: insertCompositionText, isComposing: true
compositionend - key: N/A, data: 가, inputType: N/A, isComposing: N/A
keyup - key: Unidentified, data: N/A, inputType: N/A, isComposing: N/A
keydown - key: Unidentified, data: N/A, inputType: N/A, isComposing: N/A
compositionstart - key: N/A, data: N/A, inputType: N/A, isComposing: N/A
compositionupdate - key: N/A, data: 나, inputType: N/A, isComposing: N/A
beforeinput - key: N/A, data: 나, inputType: insertCompositionText, isComposing: true
input - key: N/A, data: 나, inputType: insertCompositionText, isComposing: true
keyup - key: Unidentified, data: N/A, inputType: N/A, isComposing: true
keydown - key: Unidentified, data: N/A, inputType: N/A, isComposing: true
compositionupdate - key: N/A, data: 낟, inputType: N/A, isComposing: N/A
beforeinput - key: N/A, data: 낟, inputType: insertCompositionText, isComposing: true
input - key: N/A, data: 낟, inputType: insertCompositionText, isComposing: true
keyup - key: Unidentified, data: N/A, inputType: N/A, isComposing: true
keydown - key: Unidentified, data: N/A, inputType: N/A, isComposing: true
compositionupdate - key: N/A, data: 나, inputType: N/A, isComposing: N/A
beforeinput - key: N/A, data: 나, inputType: insertCompositionText, isComposing: true
input - key: N/A, data: 나, inputType: insertCompositionText, isComposing: true
compositionend - key: N/A, data: 나, inputType: N/A, isComposing: N/A
keyup - key: Unidentified, data: N/A, inputType: N/A, isComposing: N/A
keydown - key: Unidentified, data: N/A, inputType: N/A, isComposing: N/A
compositionstart - key: N/A, data: N/A, inputType: N/A, isComposing: N/A
compositionupdate - key: N/A, data: 다, inputType: N/A, isComposing: N/A
beforeinput - key: N/A, data: 다, inputType: insertCompositionText, isComposing: true
input - key: N/A, data: 다, inputType: insertCompositionText, isComposing: true
keyup - key: Unidentified, data: N/A, inputType: N/A, isComposing: true
compositionend - key: N/A, data: 다, inputType: N/A, isComposing: N/A
Android (GBoard)
keydown - key: Unidentified, data: N/A, inputType: N/A, isComposing: N/A
compositionstart - key: N/A, data: N/A, inputType: N/A, isComposing: N/A
compositionupdate - key: N/A, data: ㄱ, inputType: N/A, isComposing: N/A
beforeinput - key: N/A, data: ㄱ, inputType: insertCompositionText, isComposing: true
input - key: N/A, data: ㄱ, inputType: insertCompositionText, isComposing: true
keyup - key: Unidentified, data: N/A, inputType: N/A, isComposing: true
keydown - key: Unidentified, data: N/A, inputType: N/A, isComposing: true
compositionupdate - key: N/A, data: 가, inputType: N/A, isComposing: N/A
beforeinput - key: N/A, data: 가, inputType: insertCompositionText, isComposing: true
input - key: N/A, data: 가, inputType: insertCompositionText, isComposing: true
keyup - key: Unidentified, data: N/A, inputType: N/A, isComposing: true
keydown - key: Unidentified, data: N/A, inputType: N/A, isComposing: true
compositionupdate - key: N/A, data: 간, inputType: N/A, isComposing: N/A
beforeinput - key: N/A, data: 간, inputType: insertCompositionText, isComposing: true
input - key: N/A, data: 간, inputType: insertCompositionText, isComposing: true
keyup - key: Unidentified, data: N/A, inputType: N/A, isComposing: true
keydown - key: Unidentified, data: N/A, inputType: N/A, isComposing: true
compositionupdate - key: N/A, data: 가나, inputType: N/A, isComposing: N/A
beforeinput - key: N/A, data: 가나, inputType: insertCompositionText, isComposing: true
input - key: N/A, data: 가나, inputType: insertCompositionText, isComposing: true
keyup - key: Unidentified, data: N/A, inputType: N/A, isComposing: true
keydown - key: Unidentified, data: N/A, inputType: N/A, isComposing: true
compositionupdate - key: N/A, data: 가낟, inputType: N/A, isComposing: N/A
beforeinput - key: N/A, data: 가낟, inputType: insertCompositionText, isComposing: true
input - key: N/A, data: 가낟, inputType: insertCompositionText, isComposing: true
keyup - key: Unidentified, data: N/A, inputType: N/A, isComposing: true
keydown - key: Unidentified, data: N/A, inputType: N/A, isComposing: true
compositionupdate - key: N/A, data: 가나다, inputType: N/A, isComposing: N/A
beforeinput - key: N/A, data: 가나다, inputType: insertCompositionText, isComposing: true
input - key: N/A, data: 가나다, inputType: insertCompositionText, isComposing: true
keyup - key: Unidentified, data: N/A, inputType: N/A, isComposing: true
compositionend - key: N/A, data: 가나다, inputType: N/A, isComposing: N/A
Windows 11
keydown - key: Process, data: N/A, inputType: N/A, isComposing: N/A
compositionstart - key: N/A, data: N/A, inputType: N/A, isComposing: N/A
compositionupdate - key: N/A, data: ㄱ, inputType: N/A, isComposing: N/A
beforeinput - key: N/A, data: ㄱ, inputType: insertCompositionText, isComposing: true
input - key: N/A, data: ㄱ, inputType: insertCompositionText, isComposing: true
keyup - key: Process, data: N/A, inputType: N/A, isComposing: true
keyup - key: r, data: N/A, inputType: N/A, isComposing: true
keydown - key: Process, data: N/A, inputType: N/A, isComposing: true
compositionupdate - key: N/A, data: 가, inputType: N/A, isComposing: N/A
beforeinput - key: N/A, data: 가, inputType: insertCompositionText, isComposing: true
input - key: N/A, data: 가, inputType: insertCompositionText, isComposing: true
keyup - key: Process, data: N/A, inputType: N/A, isComposing: true
keyup - key: k, data: N/A, inputType: N/A, isComposing: true
keydown - key: Process, data: N/A, inputType: N/A, isComposing: true
compositionupdate - key: N/A, data: 간, inputType: N/A, isComposing: N/A
beforeinput - key: N/A, data: 간, inputType: insertCompositionText, isComposing: true
input - key: N/A, data: 간, inputType: insertCompositionText, isComposing: true
keyup - key: Process, data: N/A, inputType: N/A, isComposing: true
keyup - key: s, data: N/A, inputType: N/A, isComposing: true
keydown - key: Process, data: N/A, inputType: N/A, isComposing: true
compositionupdate - key: N/A, data: 가, inputType: N/A, isComposing: N/A
beforeinput - key: N/A, data: 가, inputType: insertCompositionText, isComposing: true
input - key: N/A, data: 가, inputType: insertCompositionText, isComposing: true
compositionend - key: N/A, data: 가, inputType: N/A, isComposing: N/A
compositionstart - key: N/A, data: N/A, inputType: N/A, isComposing: N/A
compositionupdate - key: N/A, data: 나, inputType: N/A, isComposing: N/A
beforeinput - key: N/A, data: 나, inputType: insertCompositionText, isComposing: true
input - key: N/A, data: 나, inputType: insertCompositionText, isComposing: true
keyup - key: Process, data: N/A, inputType: N/A, isComposing: true
keyup - key: k, data: N/A, inputType: N/A, isComposing: true
keydown - key: Process, data: N/A, inputType: N/A, isComposing: true
compositionupdate - key: N/A, data: 낟, inputType: N/A, isComposing: N/A
beforeinput - key: N/A, data: 낟, inputType: insertCompositionText, isComposing: true
input - key: N/A, data: 낟, inputType: insertCompositionText, isComposing: true
keyup - key: Process, data: N/A, inputType: N/A, isComposing: true
keyup - key: e, data: N/A, inputType: N/A, isComposing: true
keydown - key: Process, data: N/A, inputType: N/A, isComposing: true
compositionupdate - key: N/A, data: 나, inputType: N/A, isComposing: N/A
beforeinput - key: N/A, data: 나, inputType: insertCompositionText, isComposing: true
input - key: N/A, data: 나, inputType: insertCompositionText, isComposing: true
compositionend - key: N/A, data: 나, inputType: N/A, isComposing: N/A
compositionstart - key: N/A, data: N/A, inputType: N/A, isComposing: N/A
compositionupdate - key: N/A, data: 다, inputType: N/A, isComposing: N/A
beforeinput - key: N/A, data: 다, inputType: insertCompositionText, isComposing: true
input - key: N/A, data: 다, inputType: insertCompositionText, isComposing: true
keyup - key: Process, data: N/A, inputType: N/A, isComposing: true
keyup - key: k, data: N/A, inputType: N/A, isComposing: true
compositionend - key: N/A, data: 다, inputType: N/A, isComposing: N/A
beforeinput - key: N/A, data: 다, inputType: insertText, isComposing: N/A
input - key: N/A, data: 다, inputType: insertText, isComposing: N/A
Ubuntu 24.04
keydown - key: Process, data: N/A, inputType: N/A, isComposing: N/A
compositionstart - key: N/A, data: N/A, inputType: N/A, isComposing: N/A
compositionupdate - key: N/A, data: ㄱ, inputType: N/A, isComposing: N/A
beforeinput - key: N/A, data: ㄱ, inputType: insertCompositionText, isComposing: true
input - key: N/A, data: ㄱ, inputType: insertCompositionText, isComposing: true
keyup - key: r, data: N/A, inputType: N/A, isComposing: true
keydown - key: Process, data: N/A, inputType: N/A, isComposing: true
compositionupdate - key: N/A, data: 가, inputType: N/A, isComposing: N/A
beforeinput - key: N/A, data: 가, inputType: insertCompositionText, isComposing: true
input - key: N/A, data: 가, inputType: insertCompositionText, isComposing: true
keyup - key: k, data: N/A, inputType: N/A, isComposing: true
keydown - key: Process, data: N/A, inputType: N/A, isComposing: true
compositionupdate - key: N/A, data: 간, inputType: N/A, isComposing: N/A
beforeinput - key: N/A, data: 간, inputType: insertCompositionText, isComposing: true
input - key: N/A, data: 간, inputType: insertCompositionText, isComposing: true
keyup - key: s, data: N/A, inputType: N/A, isComposing: true
keydown - key: Process, data: N/A, inputType: N/A, isComposing: true
compositionupdate - key: N/A, data: N/A, inputType: N/A, isComposing: N/A
beforeinput - key: N/A, data: N/A, inputType: insertCompositionText, isComposing: true
input - key: N/A, data: N/A, inputType: insertCompositionText, isComposing: true
compositionend - key: N/A, data: N/A, inputType: N/A, isComposing: N/A
input - key: N/A, data: N/A, inputType: insertCompositionText, isComposing: N/A
beforeinput - key: N/A, data: 가, inputType: insertText, isComposing: N/A
input - key: N/A, data: 가, inputType: insertText, isComposing: N/A
compositionstart - key: N/A, data: N/A, inputType: N/A, isComposing: N/A
compositionupdate - key: N/A, data: 나, inputType: N/A, isComposing: N/A
beforeinput - key: N/A, data: 나, inputType: insertCompositionText, isComposing: true
input - key: N/A, data: 나, inputType: insertCompositionText, isComposing: true
keyup - key: k, data: N/A, inputType: N/A, isComposing: true
keydown - key: Process, data: N/A, inputType: N/A, isComposing: true
compositionupdate - key: N/A, data: 낟, inputType: N/A, isComposing: N/A
beforeinput - key: N/A, data: 낟, inputType: insertCompositionText, isComposing: true
input - key: N/A, data: 낟, inputType: insertCompositionText, isComposing: true
keyup - key: e, data: N/A, inputType: N/A, isComposing: true
keydown - key: Process, data: N/A, inputType: N/A, isComposing: true
compositionupdate - key: N/A, data: N/A, inputType: N/A, isComposing: N/A
beforeinput - key: N/A, data: N/A, inputType: insertCompositionText, isComposing: true
input - key: N/A, data: N/A, inputType: insertCompositionText, isComposing: true
compositionend - key: N/A, data: N/A, inputType: N/A, isComposing: N/A
input - key: N/A, data: N/A, inputType: insertCompositionText, isComposing: N/A
beforeinput - key: N/A, data: 나, inputType: insertText, isComposing: N/A
input - key: N/A, data: 나, inputType: insertText, isComposing: N/A
compositionstart - key: N/A, data: N/A, inputType: N/A, isComposing: N/A
compositionupdate - key: N/A, data: 다, inputType: N/A, isComposing: N/A
beforeinput - key: N/A, data: 다, inputType: insertCompositionText, isComposing: true
input - key: N/A, data: 다, inputType: insertCompositionText, isComposing: true
keyup - key: k, data: N/A, inputType: N/A, isComposing: true
compositionupdate - key: N/A, data: N/A, inputType: N/A, isComposing: N/A
beforeinput - key: N/A, data: N/A, inputType: insertCompositionText, isComposing: true
input - key: N/A, data: N/A, inputType: insertCompositionText, isComposing: true
compositionupdate - key: N/A, data: 다, inputType: N/A, isComposing: N/A
beforeinput - key: N/A, data: 다, inputType: insertCompositionText, isComposing: true
compositionend - key: N/A, data: 다, inputType: N/A, isComposing: N/A
input - key: N/A, data: 다, inputType: insertCompositionText, isComposing: N/A
macOS 15.4
keydown - key: ㄱ, data: N/A, inputType: N/A, isComposing: N/A
compositionstart - key: N/A, data: N/A, inputType: N/A, isComposing: N/A
compositionupdate - key: N/A, data: ㄱ, inputType: N/A, isComposing: N/A
beforeinput - key: N/A, data: ㄱ, inputType: insertCompositionText, isComposing: true
input - key: N/A, data: ㄱ, inputType: insertCompositionText, isComposing: true
keyup - key: ㄱ, data: N/A, inputType: N/A, isComposing: true
keydown - key: ㅏ, data: N/A, inputType: N/A, isComposing: true
compositionupdate - key: N/A, data: 가, inputType: N/A, isComposing: N/A
beforeinput - key: N/A, data: 가, inputType: insertCompositionText, isComposing: true
input - key: N/A, data: 가, inputType: insertCompositionText, isComposing: true
keyup - key: ㅏ, data: N/A, inputType: N/A, isComposing: true
keydown - key: ㄴ, data: N/A, inputType: N/A, isComposing: true
compositionupdate - key: N/A, data: 간, inputType: N/A, isComposing: N/A
beforeinput - key: N/A, data: 간, inputType: insertCompositionText, isComposing: true
input - key: N/A, data: 간, inputType: insertCompositionText, isComposing: true
keyup - key: ㄴ, data: N/A, inputType: N/A, isComposing: true
keydown - key: ㅏ, data: N/A, inputType: N/A, isComposing: true
compositionupdate - key: N/A, data: 가, inputType: N/A, isComposing: N/A
beforeinput - key: N/A, data: 가, inputType: insertCompositionText, isComposing: true
input - key: N/A, data: 가, inputType: insertCompositionText, isComposing: true
compositionend - key: N/A, data: 가, inputType: N/A, isComposing: N/A
compositionstart - key: N/A, data: N/A, inputType: N/A, isComposing: N/A
compositionupdate - key: N/A, data: 나, inputType: N/A, isComposing: N/A
beforeinput - key: N/A, data: 나, inputType: insertCompositionText, isComposing: true
input - key: N/A, data: 나, inputType: insertCompositionText, isComposing: true
keyup - key: ㅏ, data: N/A, inputType: N/A, isComposing: true
keydown - key: ㄷ, data: N/A, inputType: N/A, isComposing: true
compositionupdate - key: N/A, data: 낟, inputType: N/A, isComposing: N/A
beforeinput - key: N/A, data: 낟, inputType: insertCompositionText, isComposing: true
input - key: N/A, data: 낟, inputType: insertCompositionText, isComposing: true
keyup - key: ㄷ, data: N/A, inputType: N/A, isComposing: true
keydown - key: ㅏ, data: N/A, inputType: N/A, isComposing: true
compositionupdate - key: N/A, data: 나, inputType: N/A, isComposing: N/A
beforeinput - key: N/A, data: 나, inputType: insertCompositionText, isComposing: true
input - key: N/A, data: 나, inputType: insertCompositionText, isComposing: true
compositionend - key: N/A, data: 나, inputType: N/A, isComposing: N/A
compositionstart - key: N/A, data: N/A, inputType: N/A, isComposing: N/A
compositionupdate - key: N/A, data: 다, inputType: N/A, isComposing: N/A
beforeinput - key: N/A, data: 다, inputType: insertCompositionText, isComposing: true
input - key: N/A, data: 다, inputType: insertCompositionText, isComposing: true
keyup - key: ㅏ, data: N/A, inputType: N/A, isComposing: true
compositionend - key: N/A, data: 다, inputType: N/A, isComposing: N/A

@danielhjacobs
Copy link
Contributor

danielhjacobs commented Apr 4, 2025

Explanation of issues:

iOS default keyboard Because we clear the input for each character typed, the characters do not combine, as the only input event that fires is an insertText for each individual character (all six) and there are no composition events or advanced text events. We'd need to properly handle the advanced text events anyway.
iOS GBoard Unknown issue as I don't know what events it fires. Probably relates at least partially to clearing the input for each character typed.
Android Samsung Keyboard No issue, every input event occurs during composition and the data at compositionend is correct, containing all the entered text.
Android GBoard For the initially mentioned input, all input events happen during composition and the data at compositionend is correct. I'd need to see the events that fire when entering 1 + 2 + 3 + ㄱ + ㅏ + ㄴ + ㅏ + ㄷ + ㅏ to know the issue there.
Windows default keyboard Because we clear the input for each character typed, the deleteContentBackward event that is supposed to fire before a final input event duplicates the last combined character does not fire. Even if it did, we don't handle deleteContentBackward.
macOS default keyboard Unsure, based on the listed events I would expect everything to work correctly. If you ignore the order, macOS seems to be typing everything twice.
Linux default keyboard No issue, there is a non-composing insertText input event containing 가 that occurs following a compositionend event with no data, a non-composing insertText input event containing 나 that occurs following a compositionend event with no data, and a compositionend event at the end containing 다 as data.

Copy link
Contributor

@danielhjacobs danielhjacobs left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

JS changes approved. Functionality approved as it's a definite improvement despite not being perfect. Rust changes seem fine, I'm not confident enough to approve them myself though.

@danielhjacobs
Copy link
Contributor

I'm about 99.9% sure we need to stop clearing the hidden input for each character typed in the future.

@danielhjacobs
Copy link
Contributor

danielhjacobs commented Apr 24, 2025

I have a theory for what Mac is doing wrong, and if I'm right I wonder if this would help:

this.virtualKeyboard.addEventListener("keydown", this.ignoreComposingKeyEvents.bind(this));
this.virtualKeyboard.addEventListener("keyup", this.ignoreComposingKeyEvents.bind(this));
ignoreComposingKeyEvents(event: KeyboardEvent) {
    if (event.isComposing) {
        event.preventDefault();
        event.stopPropagation();
        return;
    }
}

My theory is maybe Mac attempts to fire events for composing key events, causing the keyup/keydown to write text too.

@kjarosh
Copy link
Member Author

kjarosh commented Apr 24, 2025

I'm about 99.9% sure we need to stop clearing the hidden input for each character typed in the future.

Me too. Moreover, I'm 97% sure we should just synchronize the text field with the HTML input and issue Flash events based on changes.

But that's a separate issue from IME, as IME in Flash behaves differently to normal input (and it gets underlined). Even with full text synchronization we should translate IME events to Flash.

Copy link
Contributor

@danielhjacobs danielhjacobs left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't have perfect knowledge of Rust but I'm comfortable enough to approve this PR after it's been sitting for weeks. The web-side code seems fine. As mentioned, it's not perfect but it doesn't make things any worse and it can't be perfect unless Rust and the web start talking to each other a lot more when it comes to EditTexts.

@danielhjacobs
Copy link
Contributor

Note, the tsx files weren't being linted so it's entirely possible that when this gets rebased it ends up with lint failures.

@kjarosh
Copy link
Member Author

kjarosh commented May 22, 2025

BTW I'm still trying to write a test for it, but the process is tedious. I'll try my best to include it here

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-input Area: Input handling ime Issues related to Input Method Editor newsworthy T-compat Type: Compatibility with Flash Player text Issues relating to text rendering/input waiting-on-review Waiting on review from a Ruffle team member
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants