Visitar URL original
fix(wasm): Raise Enter once per press in a TextBox by mikernet · Pull Request #25174 · unoplatform/uno · GitHub
Skip to content

fix(wasm): Raise Enter once per press in a TextBox - #25174

Open
mikernet wants to merge 6 commits into
unoplatform:masterfrom
mikernet:dev/mikernet/wasm-enter-raised-once
Open

mikernet wants to merge 6 commits into
unoplatform:masterfrom
mikernet:dev/mikernet/wasm-enter-raised-once

Conversation

@mikernet

@mikernet mikernet commented Oct 10, 2026 •

Copy link
Copy Markdown
Contributor

GitHub Issue: closes #25173

Related: #24502 changes the same handlers of the hidden input. Whichever of the two lands second needs a small rebase, which I will take care of. This branch is rebased on #25804, which changed them too (see below).

PR Type:

🐞 Bugfix

What changed? 🚀

Enter in a TextBox that does not accept returns reaches managed code through up to three DOM events: its keydown (through BrowserKeyboardInputSource), the beforeinput of the line break the browser then sets out to insert (through OnEnterKeyPressed), and its keyup. With a hardware keyboard the first two each raised KeyDown, and since OnEnterKeyPressed raises the release too (#24575) KeyUp came twice as well. One press gave KeyDown, KeyDown, KeyUp, KeyUp.

Each press is now raised once:

  • The beforeinput handler leaves a key alone that BrowserKeyboardInputSource raises from its keydown. It does so for a keydown that has the code it tells the key by (Enter or NumpadEnter) and, since fix(textbox): Fix IME and touch editing on Android browsers #25804, which maps a key without a code by its name, for one without a code named Enter. A soft keyboard's Enter whose keydown is unidentified is still raised from the beforeinput.
  • The keyup handler does not raise a key the beforeinput handler raised. When BrowserKeyboardInputSource would raise that keyup as Enter's release (it has a code, or none and is named Enter), the handler stops it from reaching the source.
  • The keyup the handler raises the key for is kept from BrowserKeyboardInputSource too, which fix(textbox): Fix IME and touch editing on Android browsers #25804 already does on master.

On master, #25804 also brought a double Enter: the keyboard input source raises a soft keyboard's Enter from its code-less keydown named Enter, and the line break that follows raises it again (KeyDown, KeyDown, KeyUp, KeyUp). 53e146e fixes that along with the rest, as When_SoftKeyboard_Enter_Has_No_Code shows.

The presses of Enter that are down are tracked by the code of their key (Enter, NumpadEnter, or none for a soft keyboard's):

  • which ones had a keydown seen as Enter's
  • which ones the line break raised, release included

A keydown without a code counts as a possible press of Enter unless it names another key (a soft keyboard's Backspace, say).

A press can outlive the hidden input it started on, so its entries are not tied to the input. They last until the keyup of the key, wherever that goes, or until its next keydown.

BrowserKeyboardInputSource now reports the keydown and keyup events that reach the document to the hidden input's code:

  • It records the presses of Enter it raises itself.
  • It does not raise a release that was already raised with its press.

A press therefore stays a single one in these cases:

  • Another key goes down before Enter is released.
  • The main and the numpad Enter overlap.
  • A handler moves focus off the TextBox before the keyup, which then goes to the document.
  • The press starts on another control (or on a TextBox that accepts returns) and is released on a TextBox. That TextBox now gets the release only.

The managed side is unchanged. For a key pressed and released on a TextBox, the change only stops the duplicate presses and releases described above.

The browser and device checks below were made before the rebase on #25804, on the product code of 7360f2a (unchanged up to b2a91d2, the commits after it only add tests and comments), against master as it was in mid-October. They weren't rerun at the rebased head; the replayed DOM events in the tests are.

A trusted Enter press sent through the DevTools protocol (Chrome 154, then Chrome 155 at b2a91d2: Input.dispatchKeyEvent, every keydown, beforeinput and keyup checked to be trusted) to a TextBox gave KeyDown, KeyDown, KeyUp, KeyUp on that master and KeyDown, KeyUp with the change, and the same went for the Enter of the numeric keypad and for a PasswordBox. A held Enter gave a KeyDown for each repeat and one KeyUp. The same press without a code, which is how Chrome on Android sends the action key of the soft keyboard, gave one KeyDown and one KeyUp for Enter on both, between the KeyDown and the KeyUp of an unidentified key. Since #25804, the keyboard input source names that press Enter, and with the replayed events current master gives two of each (see above).

An application built from that code was compared with one built from that master on devices:

  • Android 15 emulator (Chrome 124, Gboard, real taps): Gboard's Enter gives one KeyDown and one KeyUp for Enter on both. The emulator's hardware Enter gives one of each with the change and two of each on master.
  • iOS 26.5 in the iPhone 17 simulator (Safari; a simulator, not a physical device), tapped by hand: the Return key of the soft keyboard gives one KeyDown and one KeyUp for Enter on both, in a TextBox that does not accept returns. In one that accepts returns, both give one of each and one new line per press.

An earlier backport of this change on a 6.8 build was checked on Android and iOS devices (Next and Done) and behaved as it should.

Tests

Given_TextBox (new file Given_TextBox.BrowserEnterKey.cs), Skia WASM, replaying the DOM events on the hidden input:

  • When_Hardware_Keyboard_Enter_Pressed: keydown, beforeinput, keyup with a code.
  • When_Hardware_Keyboard_Enter_Moves_Focus_On_Release: the same press with a KeyUp handler that moves focus to the next of three fields; it ends on the second.
  • When_Hardware_Keyboard_Enter_Is_Named_On_Release_Only: a keydown reported as the IME's, then a keyup of Enter with a code.
  • When_Hardware_Keyboard_Enter_Is_Named_On_Release_After_Line_Break: the same with a beforeinput in between.
  • When_SoftKeyboard_Enter_Is_Named_On_Release_After_Line_Break: an unidentified keydown, a beforeinput, then a keyup named Enter without a code.
  • When_Another_Key_Goes_Down_Before_Enter_Named_On_Release_After_Line_Break_Is_Released: a keydown reported as the IME's and its beforeinput, then another key going down (one the IME takes, and a modifier) before the keyup of Enter; Enter is still raised once.
  • When_Hardware_Keyboard_Enter_Follows_Line_Break_Without_Key_Events: a line break on its own, another key typed, then a press of Enter in both hardware keyboard shapes; each Enter is raised.
  • When_Enter_Named_On_Release_After_Line_Break_Moves_Focus_Off_The_TextBox: the KeyDown of a line-break-raised Enter moves focus to a button, so the keyup goes to the document, or never comes. The button gets no release for it, and the next press of the key is raised in full. A third row does this for a soft keyboard, whose keyup has no code.
  • When_Enter_And_Numpad_Enter_Presses_Overlap: both Enter keys down at once; two presses are raised, not three.
  • When_Enter_Named_On_Release_Only_Follows_Hardware_Keyboard_Enter_That_Moved_Focus: a hardware Enter moves focus off the TextBox and is released on the document; an Enter the next TextBox only learns of by its keyup (a soft keyboard's, and one with a code) is raised.
  • When_Hardware_Keyboard_Enter_Pressed_On_Another_Control_Is_Released_On_The_TextBox: the keydown goes to the document and moves focus to the TextBox, which gets the line break and the keyup; it sees the release only.
  • When_Hardware_Keyboard_Enter_Is_Held: the repeats of a held Enter raise KeyDown each, and one KeyUp.
  • When_Hardware_Keyboard_Enter_Release_Is_Lost: a press of Enter whose keyup never comes, then another press: one named on its keydown followed by one reported as the IME's, and the other way around, where the line break raised the first press with its release. The second press is raised in full.
  • When_SoftKeyboard_Enter_Has_No_Code_And_TextBox_Accepts_Returns: a press of Enter without a code in a TextBox that accepts returns, which handles the key itself, raises it once.
  • When_SoftKeyboard_Key_Goes_Down_Before_Enter_Named_On_Release_After_Line_Break_Is_Released: an unidentified keydown and its line break, then a soft keyboard's Backspace (no code) going down before the keyup named Enter; Enter is still raised once.
  • When_SoftKeyboard_Enter_Has_No_Code, When_SoftKeyboard_Enter_Is_Named_On_Release_Only, When_SoftKeyboard_Enter_Is_Never_Named, When_SoftKeyboard_Line_Break_Comes_Without_Key_Events, When_SoftKeyboard_Line_Break_Follows_Hardware_Keyboard_Enter, When_SoftKeyboard_Line_Break_Follows_Hardware_Keyboard_Enter_That_Moved_Focus: the soft keyboard shapes, press after press and after a hardware keyboard's.

What fails without which part of the change:

  • On master at 46ea872 (its TypeScript, with this branch's test file except When_SoftKeyboard_Enter_Has_No_Code_And_TextBox_Accepts_Returns), 18 of the 26 cases fail. The 8 that pass are When_Hardware_Keyboard_Enter_Is_Named_On_Release_Only (fix(textbox): Fix IME and touch editing on Android browsers #25804 now keeps that keyup from the keyboard input source), the row of Moves_Focus_Off_The_TextBox whose release is lost, the Process row of When_Hardware_Keyboard_Enter_Follows_Line_Break_Without_Key_Events, When_SoftKeyboard_Enter_Is_Named_On_Release_Only, When_SoftKeyboard_Enter_Is_Never_Named and the three When_SoftKeyboard_Line_Break_* tests. When_SoftKeyboard_Enter_Has_No_Code passed on master before fix(textbox): Fix IME and touch editing on Android browsers #25804 and fails on it now.
  • On this branch rebased on fix(textbox): Fix IME and touch editing on Android browsers #25804, before 53e146e, When_SoftKeyboard_Enter_Has_No_Code and When_SoftKeyboard_Enter_Is_Named_On_Release_After_Line_Break failed: the beforeinput raised again a press the keyboard input source had raised from its keydown, and the source raised a second release for a keyup named Enter after a line break.
  • Without the document keyup check of 53e146e (a code-less keyup named Enter), the soft keyboard row of Moves_Focus_Off_The_TextBox fails: the button gets a second release.
  • Without a9e04bf, When_SoftKeyboard_Key_Goes_Down_Before_Enter_Named_On_Release_After_Line_Break_Is_Released fails: the Backspace drops what the line break recorded, and the keyup raises Enter again.
  • Checked before the rebase: When_Another_Key_Goes_Down_Before_Enter_Named_On_Release_After_Line_Break_Is_Released fails when every keydown clears what the line break recorded.
  • Checked before the rebase: When_Hardware_Keyboard_Enter_Follows_Line_Break_Without_Key_Events fails when a keydown that has the code of Enter is taken for another key.
  • Checked before the rebase: When_Hardware_Keyboard_Enter_Release_Is_Lost fails, in both rows, when no keydown clears what the earlier press left (the input's keydown handler does, and onKeyDownAtDocument does for the line break's entry of a coded keydown).
  • Checked before the rebase: the four tests from Moves_Focus_Off_The_TextBox to Is_Released_On_The_TextBox fail with the state tied to the input, as it was before the second review round.

Each test clears the content it loaded.

Runs: all on Skia WASM in Chrome, with the DOM events replayed (synthetic, untrusted events).

  • At a9e04bf, rebased on master at 46ea872: the 27 cases of this file pass, and Given_TextBox, Given_PasswordBox, Given_BrowserKeyboardInputSource and Given_AccessibleTextBox give 384 passed, 30 inconclusive (clipboard and pointer-hold tests) and 1 failed, Given_TextBox.When_Touch_LongPress_Drag_Release_Does_Not_Open_Flyout_iOS, which fails the same way on a Skia desktop build of master on this machine.
  • At b2a91d2, before the rebase: the 24 cases of this file (19 test methods, 5 of them with two data rows) passed, and Given_TextBox and Given_PasswordBox passed (219 passed, none failed, 30 inconclusive: the clipboard and pointer-hold tests can't run in that setup).
  • Given_TextBox, Given_PasswordBox and Given_Button passed in the second review round (261 tests, none failed).
  • Given_TextBox, Given_PasswordBox and Given_RichEditBox passed in full before the review changes (1326 tests).

Known limitations

  • An Enter that commits an IME composition is still raised as Enter, from its keyup, as before. That shape was only replayed as DOM events, not tried with an IME.
  • A line-break-raised press that is held raises a KeyDown and a KeyUp for each repeat.
  • A soft keyboard's line break that comes without any key event is not raised in one narrow case: a hardware Enter pressed on another control lost its keyup (the page lost focus), and a TextBox was focused next. Any keydown clears that state.
  • Before fix(textbox): Fix IME and touch editing on Android browsers #25804, in a TextBox that accepts returns, the Enter of an Android soft keyboard (Gboard) raised no Enter key events, only a KeyDown and a KeyUp of an unidentified key, because its keydown has no code. fix(textbox): Fix IME and touch editing on Android browsers #25804 maps such a key by its name, and a press named Enter now raises Enter there (When_SoftKeyboard_Enter_Has_No_Code_And_TextBox_Accepts_Returns). I haven't rechecked it with Gboard itself.
  • A press of Enter whose keyup never comes stays recorded until the next keydown of the key. A keyup of the key that comes before that, with no keydown of its own, is taken for the lost one's, and no press is raised for it.

PR Checklist ✅

Copilot AI balanced review requested due to automatic review settings October 10, 2026 14:00
@github-actions github-actions Bot added the platform/wasm 🌐 Categorizes an issue or PR as relevant to the WebAssembly platform label Oct 10, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Changes recommended

Enter tracking fails for overlapping key presses, and the new tests leak focused content into subsequent tests.

2 open findings
What changed in this PR

Fixes duplicate Enter events in WASM text inputs by coordinating DOM keyboard and line-break handlers.

Changes:

  • Tracks which DOM path raised Enter.
  • Suppresses duplicate key releases.
  • Adds WASM runtime coverage for hardware and soft keyboards.

Review verdict: Changes required—one overlapping-key bug and one test-isolation issue.

File Description
Given_TextBox.BrowserEnterKey.cs Adds browser Enter event-sequence tests.
BrowserInvisibleTextBoxViewExtension.ts Deduplicates Enter events across DOM handlers.

🧠 Review effort: Balanced


💡 Add a code-review agent skill for context-aware, tailored reviews. Learn more in the docs.

Copilot AI balanced review requested due to automatic review settings October 10, 2026 15:14
@mikernet
mikernet force-pushed the dev/mikernet/wasm-enter-raised-once branch from c8b88bb to 9fbf2c4 Compare October 10, 2026 15:14

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot AI balanced review requested due to automatic review settings October 10, 2026 16:02
@mikernet
mikernet force-pushed the dev/mikernet/wasm-enter-raised-once branch from 9fbf2c4 to de89baa Compare October 10, 2026 16:02

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟢 Approval recommended

The state transitions and focus-crossing cases are comprehensively covered without unresolved correctness issues.

0 open findings

3 resolved since last review

🧠 Review effort: Balanced

mikernet added a commit to mikernet/uno that referenced this pull request Oct 10, 2026
Brings the Enter handling of the hidden input in line with unoplatform#25174 (7360f2a): the presses of Enter that are down are tracked by the code of their key, and BrowserKeyboardInputSource reports the keydowns and keyups that reach the document. A press stays a single one when another key goes down before its release, when the main and the numpad Enter overlap, when a handler moves focus off the TextBox before the keyup, and when it starts on another control and is released on the TextBox. Also has the handling of a keyup that has a code after a line break, which the earlier backport lacked.

@morning4coffe-dev morning4coffe-dev left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Verified: the diff at head de89baa (3 files), the final TS state of BrowserInvisibleTextBoxViewExtension.ts and BrowserKeyboardInputSource.ts, the managed OnNativeKeyboardEvent (BrowserKeyboardInputSource.cs:31-77, which raises KeyDown/KeyUp for every event that reaches document, including code-less ones), the new 18-method test file, CI, and the review threads; WinUI TextBoxBase.cpp:4504 (Enter blocked from RichEdit when AcceptsReturn is false and wrapping). Code review only, nothing built or run.

Findings, by severity:

  1. Medium: the final code is not validated on a device. The PR body says the iOS/Android soft-keyboard checks ran on a 6.8 backport that "does not have the handling of a keyup with a code after a line break (the second point above)", which was added later. The Chrome 154 DevTools trusted-event results are author-reported and not reproducible from the PR. Request a device or trusted-event run on de89baa (or a note that it was re-run).

  2. Medium (design / maintainability): the Enter state is a 5-part heuristic in BrowserInvisibleTextBoxViewExtension.ts (enterKeyDowns, enterLineBreaks, lastKeyDownEnterCode, the isOtherKey test at :346-350, endEnterPress at :322). It depends on keydown -> beforeinput -> keyup ordering that the browser does not guarantee for IME and soft keyboards. A keyup that never arrives leaves enterKeyDowns("Enter") set until the next Enter keydown or keyup (not a bug on its own, since the next keydown clears it, but worth a comment or a test with a lost keyup). Please confirm the design was weighed against simply preventing the default of the line break, and say why not.

  3. Low: test count. The PR body says "The 23 cases of this file pass". The file has 18 [TestMethod]s, 4 of them with 2 DataRows each, which is 22 cases. Fix the number.

  4. Low: the keyup branch at :402-427 calls stopPropagation for two different reasons (line-break-raised with a code, and OnEnterKeyPressed fallback). Both are correct as read, but a one-line comment at each would help the next reader.

  5. Info: the managed side is unchanged. The release that the document-level path now drops (onKeyUpAtDocument returns true, BrowserKeyboardInputSource.ts) returns before OnNativeKeyboardEvent, so no KeyUp is raised for it. Consistent with the PR's claim.

WinUI check: TextBoxBase.cpp:4504 blocks Enter from RichEdit when AcceptsReturn is false and wrapping is on, and does not consume the key at the framework level as far as I read. The implied semantics (one KeyDown per keydown, one KeyUp per release, repeats raise KeyDown each) are the expected shape. I did not trace the framework KeyDown routing to confirm repeat behavior.

Prior review threads: Copilot's last review on head is "approval recommended, 0 open findings". mikernet replied to each thread with fixes in 82f9bba, 9fbf2c4 and 7360f2a. I did not check the resolved state of each thread.

CI: all completed checks green (Analyze csharp/js/java, CodeQL, Codacy, Check Docs links, Summary, WIP). Mergify Merge Queue and Uno.UI - docs are NEUTRAL. "review" was SKIPPED.

Not validated: the runtime tests (not run here), the device behavior on iOS and Android, the Chrome DevTools trusted-event results, and the IME Enter case (listed as a known limitation).

Copilot AI balanced review requested due to automatic review settings October 16, 2026 10:25
@mikernet

Copy link
Copy Markdown
Contributor Author
  1. I ran trusted events on this head. A temporary runtime test, which is not committed, focused a TextBox, then a PasswordBox, and recorded every KeyDown and KeyUp they got. A driver sent the presses through the DevTools protocol (Input.dispatchKeyEvent, Chrome 155) to the WASM SamplesApp, and a capture listener on the document checked that every keydown, beforeinput and keyup was trusted:

    Press DOM events (all trusted) KeyDown/KeyUp raised
    Enter keydown Enter, beforeinput insertLineBreak, keyup Enter down Enter, up Enter
    Numpad Enter the same with NumpadEnter down Enter, up Enter
    Enter without a code keydown, beforeinput insertLineBreak, keyup down None, down Enter, up Enter, up None
    Enter held (one auto-repeat) keydown, beforeinput, keydown (repeat), beforeinput, keyup down Enter, down Enter, up Enter

    The PasswordBox gave the same. The None events are the code-less keydown and keyup, which the keyboard input source raises as an unidentified key, as before this change.

    On devices, I compared an application built from this head with one built from master (efa2954). The application had the same TypeScript as this head; only tests changed after it.

    On an Android 15 emulator with Chrome 124 and Gboard, with real taps on the field and on Gboard's Enter key:

    • Gboard's Enter in a TextBox that does not accept returns sends keydown Enter with no code (keyCode 13), beforeinput insertLineBreak, then keyup Enter. Each press raises one KeyDown and one KeyUp for Enter, between those of an unidentified key, as on master. The same holds after typing text.
    • The emulator's hardware Enter (code "Enter") gives KeyDown, KeyDown, KeyUp, KeyUp on master and one of each with this PR. A key held into one repeat gives two KeyDowns and one KeyUp.
    • In a TextBox that accepts returns, the hardware Enter gives one KeyDown and one KeyUp for Enter on both. Gboard's Enter gives no Enter key events there on either: its code-less keydown reaches BrowserKeyboardInputSource, which raises it as an unidentified key. That is not from this PR, and the body now lists it as a known limitation.

    On iOS 26.5 in the iPhone 17 simulator (Safari; a simulator, not a physical device), tapping by hand:

    • The Return key of the soft keyboard in a TextBox that does not accept returns raises one KeyDown and one KeyUp for Enter per press, on both builds.
    • In a TextBox that accepts returns, both builds raise one KeyDown and one KeyUp for Enter per press and add one line per press.

    The earlier device checks ran on a 6.8 build with an earlier backport of this change. The 6.8 backport of the final logic has the same press tracking as this head.

  2. The default of the line break is already prevented: the beforeinput handler calls preventDefault, and nothing is inserted. What the tracking decides is which of the three events raises a press. Preventing the default of the keydown instead, so that no line break comes, would only work for a keydown that is raised as Enter, which is one that has the code of Enter. That is the shape that already raises the key from the keydown, and here it costs one lookup in beforeinput. The other shapes would still need the tracking:

    • A soft keyboard's Enter. Chrome on Android sends it with no code, other keyboards as 229/Unidentified. Neither keydown is raised as Enter, so the line break is what raises it. Preventing the code-less one would remove that, and a 229 keydown can't be told from other keys.
    • An Enter typed with an IME on. Its keydown is the IME's. The press is raised by the line break when one comes, or by the keyup when it commits a composition. Either way, the coded keyup must not raise the release again.
    • The releases. Half of the duplicates were KeyUps: OnEnterKeyPressed raises the release along with the press, and the keyup raised it again through BrowserKeyboardInputSource. No default decides which keyup raises a release. That takes knowing whether the release of that press was raised already, including for a keyup that goes somewhere else than its keydown (focus moved by a handler, or a press that started on another control).

    The lost keyup is commented in 71b5cb1. Its entry stays until the next keydown of the key. Until then, a keyup of the key that comes without a keydown of its own is taken for the lost one. When_Hardware_Keyboard_Enter_Release_Is_Lost (b2a91d2) presses Enter and never releases it, then presses it again. It has two rows: a press named on its keydown followed by one reported as the IME's, and the other way around, where the line break raised the first press with its release. The second press is raised in full in both. Both rows fail when no keydown clears what the earlier press left. That clearing happens in the input's keydown handler and, for the enterLineBreaks entry of a coded keydown, in onKeyDownAtDocument as well.

  3. Right: it was 22 (18 methods, 4 of them with two data rows). With the new test it is 24 (19 methods, 5 with two rows), and the body now says so.

  4. Done in 71b5cb1: both stopPropagation calls that this PR adds to the keyup handler have a line saying which release they keep from the document.

  5. No change.

Tests, on Skia WASM in Chrome with this round's commits: the 24 cases of Given_TextBox.BrowserEnterKey.cs pass, and so do Given_TextBox and Given_PasswordBox as a whole: 219 passed, none failed, and 30 were inconclusive, the clipboard and pointer-hold tests that can't run in that setup. With that clearing disabled in both places, both rows of the new test fail.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟢 Approval recommended

The focused event-routing changes have extensive regression coverage and no unresolved blocking findings.

0 open findings

🧠 Review effort: Balanced

@MartinZikmund MartinZikmund added the 7.0 PRs only. For issues, use the Release field. label Nov 4, 2026
@MartinZikmund

Copy link
Copy Markdown
Member

@Mergifyio backport release/stable/7.0

@mergify

mergify Bot commented Nov 4, 2026

Copy link
Copy Markdown
Contributor

backport release/stable/7.0

🟠 Waiting for conditions to match

Details
  • merged [📌 backport requirement]

@mergify

mergify Bot commented Nov 10, 2026

Copy link
Copy Markdown
Contributor

Tick the box to add this pull request to the merge queue (same as @mergifyio queue). If it does not match the queue conditions yet, it enters the queue once it does.

  • Queue this pull request

Enter in a TextBox that does not accept returns reaches managed code
through up to three DOM events: its keydown (through
BrowserKeyboardInputSource), the beforeinput of the line break the
browser then sets out to insert, and its keyup. With a hardware keyboard
the first two each raised KeyDown, and since OnEnterKeyPressed raises
the release too (unoplatform#24575) KeyUp came twice as well: one press gave
KeyDown, KeyDown, KeyUp, KeyUp.

Each press is now raised once:

- The beforeinput handler leaves a key alone that
  BrowserKeyboardInputSource raises from its keydown, which it does for
  a keydown that has the code it tells the key by. A soft keyboard's
  Enter has none and is still raised from the beforeinput.
- The keyup handler does not raise a key the beforeinput handler raised,
  and stops that keyup, when it has a code, before it reaches
  BrowserKeyboardInputSource, which would raise the release again.

The presses of Enter that are down are tracked by the code of their
key, and BrowserKeyboardInputSource reports the keydowns and keyups that
reach the document. A press stays a single one when another key goes
down before its release, when the main and the numpad Enter overlap,
when a handler moves focus off the TextBox before the keyup (which then
goes to the document), and when it starts on another control and is
released on the TextBox, which then only gets the release.
Say what a keyup that never comes leaves behind, and until when: the
next keydown of the key. The stopPropagation calls of the keyup handler
now say which release they keep from BrowserKeyboardInputSource.
A press of Enter whose keyup never comes, as when the page loses focus
while the key is down, is followed by another press: one named on its
keydown by one named on its release only, and the other way around.
The second press is raised in full.
Since unoplatform#25804, BrowserKeyboardInputSource maps a key without a code by its
name, so it raises a soft keyboard's Enter (key "Enter", no code) from its
keydown and keyup. The line break that follows raised the press again, and
a keyup named Enter after a line break raised a second release. The hidden
input now treats such events as ones the source raises.
A soft keyboard sends some keys without a code but with their name
(Samsung Keyboard's Backspace). Its keydown counted as a possible press
of Enter and dropped the line break an Enter still down had raised, so
that Enter's keyup raised it a second time.
@mikernet
mikernet force-pushed the dev/mikernet/wasm-enter-raised-once branch from b2a91d2 to a9e04bf Compare November 12, 2026 22:49
mikernet added a commit to Singulink/uno that referenced this pull request Nov 12, 2026
Port of unoplatform#25174 commit a9e04bf. A soft keyboard sends
some keys without a code but with their name (Samsung Keyboard's
Backspace). Its keydown counted as a possible press of Enter and dropped
the line break an Enter still down had raised, so that Enter's keyup
raised it a second time.
mikernet added a commit to Singulink/uno that referenced this pull request Nov 14, 2026
Takes Uno's servicing/6.8 from 6951c2c up to 5254ace (42 pull
requests). Conflicts, by area:

IME and TextBox (unoplatform#25984, backport of unoplatform#25804; unoplatform#25952, backport of unoplatform#25619;
unoplatform#25976, backport of unoplatform#25818)
- Takes upstream's composition design (unoplatform#25804): the browser's input is
  the source of truth for the text, compositions included. This drops
  the composition parts of the unoplatform#24502 port (90746b8): its
  TypeScript composition handling, the StartIndex/TextChangePending
  event arguments, the IsReadOnly forwarding to the hidden input, and
  its Given_TextBox.BrowserSoftKeyboard tests.
- Keeps the non-IME browser fixes on top of it, in the form their
  master pull requests have on unoplatform#25804: Enter raised once per press and
  tracked by key code (unoplatform#25174, with Given_TextBox.BrowserEnterKey from
  master in place of the Enter tests that lived in the dropped file),
  the iOS refocus on tap (unoplatform#25185), pastes without text and isIOS from
  BrowserInputHelper.
- Moves the unoplatform#26005 MaxLength fixes onto unoplatform#25804's native input path, as
  master has them: a refused browser edit keeps the TextBox's selection,
  TextBoxView.UpdateTextFromNative reports whether the edit went in, and
  the IME keeps its range on the applied text (master be85699, which
  d226897 had ported onto the unoplatform#24502 IME). Adds that commit's browser
  and platform-applied IME tests.
- TextBox.pointers.skia.cs: upstream's file (unoplatform#25619 iOS touch editing,
  unoplatform#25818, unoplatform#25804), with the unoplatform#25126 port (the hold acts when it starts,
  a touch right-tap keeps the caret) in the form master's unoplatform#25126 branch
  has it on unoplatform#25619, including focusing before the iOS hold timer acts
  and its iOS not-focusable test.

Pointers (unoplatform#25976, backport of unoplatform#25818)
- Tapped and RightTapped are raised once the PointerReleased bubbled
  (unoplatform#25818), with the unoplatform#25732 port's window coordinates (2-argument
  routed event args, RawPosition for ContextRequested), as master's
  unoplatform#25732 branch has them.
- The release wrapper of unoplatform#25818 keeps the unoplatform#25126 port's
  StopContextMenuTimer at the start of the release.

DependencyObject (unoplatform#26142, backport of unoplatform#26123)
- Takes upstream, then the code of unoplatform#25839 (97a2f17), which replaces
  this branch's unoplatform#25102 ports (800247f, be41da6, fd40bf9,
  7546379). DependencyObjectStore.Binder.cs, .alc.cs,
  Application.Alc.cs, ResourceDictionary.alc.cs and their tests now
  match unoplatform#25839; this drops 800247f's own
  SingleParentNonFE_Parent_Removes_Value_Then_Next_Parent_Starts_Clean
  test, which unoplatform#25839 doesn't have.

Imaging (unoplatform#25597, unoplatform#25915, unoplatform#25911, unoplatform#25897)
- Keeps this branch's side: it has the later forms of these fixes
  (unoplatform#25124, unoplatform#25559, unoplatform#25633 and unoplatform#26370 ports), where upstream has the
  6.8-only unoplatform#25597 design and earlier snapshots. Upstream's shared
  download, IgnoreImageCache and failed-load fixes are covered by
  SharedImageDecode. Adds upstream's When_Source_Outlives_Brushes test.

ToolTip (unoplatform#26098, backport of unoplatform#25137 and unoplatform#26096)
- Keeps this branch's unoplatform#25137 port and adds unoplatform#26096 in master's form: the
  service releases only a ToolTip it opened, and leaves one the app
  opened during the show delay to the app. Keeps both sides' tests.

Flyout (unoplatform#26062, backport of unoplatform#26060)
- This branch had the same commits already; keeps the "Keep
  non-resizable flyouts in window" port and its tests.

Also removes a helper pair in Given_AccessibleTextBox that both sides
added.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

7.0 PRs only. For issues, use the Release field. platform/wasm 🌐 Categorizes an issue or PR as relevant to the WebAssembly platform

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Skia][WASM] Enter on a hardware keyboard raises KeyDown and KeyUp twice in a TextBox

4 participants