Visitar URL original
fix(textbox): Fix IME and touch editing on Android browsers by ajpinedam · Pull Request #25804 · unoplatform/uno · GitHub
Skip to content

fix(textbox): Fix IME and touch editing on Android browsers - #25804

Merged
ajpinedam merged 2 commits into
unoplatform:masterfrom
ajpinedam:dev/anpi/fix.wasm.android.textbox.input
Nov 3, 2026
Merged

ajpinedam merged 2 commits into
unoplatform:masterfrom
ajpinedam:dev/anpi/fix.wasm.android.textbox.input

Conversation

@ajpinedam

@ajpinedam ajpinedam commented Oct 29, 2026 •

Copy link
Copy Markdown
Contributor

GitHub Issue: closes unoplatform/kahua-private#548

PR Type:

🐞 Bugfix

What changed? 🚀

On Skia WebAssembly, a TextBox on an Android tablet (Chrome) misbehaved with the soft keyboard and with touch:

  • IME composition repeated text, lost spaces and desynced the caret.
  • Double-tapping near punctuation selected the wrong word or moved the caret to the end.
  • Quick taps on adjacent lines counted as a double-tap.

Testing on a real Galaxy Tab A8 (Chrome 155, Gboard 18.5 and Samsung Keyboard) turned up more bugs, fixed here too.

The PR has two commits, each of which builds on its own:

  • fix(wasm): IME composition, soft-keyboard keys and edits the browser applied (WebAssembly). It includes the TextBoxCore/RichEditBox IME-host changes those need.
  • fix(textbox): touch selection under the Android and iOS conventions, on every Skia host.

IME composition (WebAssembly)

  • The hidden <input>/<textarea> is now the source of truth for composition text in every browser. The browser applies the text and the TextBox follows it (textAlreadyApplied), as on Skia Android. A composition that doesn't change the text (an IME re-selecting a word) is ignored.
  • compositionend no longer swallows the next input event. That event is a separate edit, such as Gboard's space after a word.
  • A TextBox edit made during a composition (a TextChanging filter, MaxLength) is reconciled when the composition ends, instead of restarting the IME.
  • A multi-line TextBox's textarea uses \n where the TextBox uses \r. Text that differs only by that no longer counts as a TextBox edit, which used to send the caret to 0 mid-sentence.
  • The input's own selection restore, after its value is assigned, no longer hijacks the next caret move by the keyboard. On Android Chrome, emptying the value fires no selectionchange.

Soft-keyboard keys (WebAssembly)

  • Samsung Keyboard sends Backspace, Delete, arrows and Enter with an empty KeyboardEvent.code.
    • Unmodified code-less Backspace/Delete are now applied by the browser, like other soft-keyboard keys. A deletion the TextBox made itself left the keyboard's copy of the text stale, so its next suggestion replaced the wrong range.
    • Other code-less named keys are mapped from key, so arrows and Enter reach the TextBox. Character keys aren't, since FromKey would turn - or . into numpad keys.
  • An Android Enter raises a single KeyUp.

Text the browser applied (WebAssembly)

  • It is now undoable as one typing run, like keys the TextBox handles. An edit the TextBox refuses records no undo step.
  • Its text change puts the caret where the input left it, without a SelectionChanged at 0 first.
  • A read-only TextBox refuses it.
  • When the TextBox rejects an edit (MaxLength, BeforeTextChanging), the caret goes back to where the edit started instead of 0.
  • App exceptions thrown from these browser callbacks are logged and reported through Application.UnhandledException instead of escaping into the DOM handlers.

Touch selection (Android and iOS touch conventions, all Skia hosts)

  • A double-tap selects the word under the finger, including when the finger is on the trailing half of a letter next to punctuation. The second tap may land on the insertion handle.
  • Two quick taps count as a double-tap only within 24 DIP and on the same line, so taps on adjacent lines move the caret.
  • The insertion handle's thumb hangs over the line below the caret. A tap there that comes as quickly after the tap that placed the caret as a double-tap's second tap moves the caret to that line. A later tap on the handle keeps the caret and shows the paste flyout, as on native Android.
  • A touch just below or above the text (the box's padding, under the last line) uses the nearest line by X instead of the end of the text. This covers taps, long-press, double-tap and the iOS caret drag. One helper now does this line clamp for touch, the selection-handle drag and the caret drag.
  • The Desktop convention and mouse input are unchanged.

Validation

  • Runtime, Skia desktop: 1457 tests across Given_TextBox, Given_RichEditBox, Given_TextBlock, Given_PasswordBox and Given_ImeSessionCoordinator. 4 fail, and all 4 also fail on this machine without these changes: When_Multiline_Pointer_Tap, When_Pointer_RightClick_Selection, TextBlock.When_IsTextSelectionEnabled_CRLF, and RichEditBox.When_Idle_Caret_Stops_Blinking_After_Timeout (checked on the base commit). The When_Touch_* and gripper tests (71) also pass on the touch commit itself.
  • Runtime, Skia WebAssembly (headless Chrome): 119/119 across the keyboard source, every Given_TextBox.When_Browser* and When_Touch* test, Given_RichEditBox.When_Wasm* and Given_AccessibleTextBox (which runs with accessibility on). On the IME commit alone, the 87 browser and accessibility tests pass.
  • Tests fail before the fix: every new test failed without its fix. This was checked with a desktop build with the fix disabled, WebAssembly builds with the C# fixes reverted, and the compiled runtime JS patched (or swapped for the previous one) to undo the TS fixes.
  • Real tablet:
    • Typing, Backspace, suggestions and re-composition with Samsung Keyboard and Gboard match what Chrome's own fields do on the same device.
    • The touch cases were checked with touches sent over Chrome remote debugging, not a physical finger.
    • Gboard never composed on this device, even in Chrome's own fields, so Samsung Keyboard exercised the composition path.

Known follow-ups, not in this PR

  • TextCompositionStarted/Changed are raised before Text contains the composition, on WebAssembly and on Skia Android.
  • Not yet run on a real device: Safari, Firefox and Samsung Internet.
  • Before merge: a manual pass with desktop CJK IMEs (Microsoft IME Japanese in Chrome on Windows, Pinyin in Safari on macOS).

PR Checklist ✅

🤖 Generated with Claude Code

@github-actions github-actions Bot added platform/wasm 🌐 Categorizes an issue or PR as relevant to the WebAssembly platform area/skia ✏️ Categorizes an issue or PR as relevant to Skia labels Oct 29, 2026
@unodevops

Copy link
Copy Markdown
Contributor

🤖 Your WebAssembly Sample App stage site is ready! Visit it here:

@nventive-devops

Copy link
Copy Markdown
Contributor

The build 242113 found UI Test snapshots differences: skia-linux-screenshots: 118, skia-windows-screenshots: 2458

Details
  • skia-linux-screenshots: 118 changed over 2458

    🚨🚨 Comparison Details (first 20) 🚨🚨
    • CalendarView_Theming.png-dark
    • CalendarView_Theming.png
    • ButtonClippingTestsControl.png-dark
    • ButtonClippingTestsControl.png
    • Buttons.png-dark
    • Buttons.png
    • DisplayInformation.png-dark
    • ClipboardTests.png-dark
    • DoubleImageBrushInList.png-dark
    • DoubleImageBrushInList.png
    • CompositionEffectBrush.png-dark
    • CompositionEffectBrush.png
    • CompositionNineGridBrush_Source_Changes.png-dark
    • CompositionNineGridBrush_Source_Changes.png
    • Examples.png
    • ImageBrushInList.png-dark
    • ImageBrushInList.png
    • ImageBrushAlignmentXY.png-dark
    • ImageBrushAlignmentXY.png
    • DynamicFont.png-dark
  • skia-windows-screenshots: 2458 changed over 2492

    🚨🚨 Comparison Details (first 20) 🚨🚨
    • AnimatedIconPage.png-dark
    • Accessibility_ScreenReader.png-dark
    • AndroidWindowInsets.png-dark
    • AnimatedVisualsTestbedPage.png
    • AppBar_KeyBoard.png
    • AppWindowClosing.png-dark
    • AppBarToggleButtonTest.png
    • ApplicationViewSpanningRectsPage.png
    • AppWindowPresenters.png
    • ApplicationViewSpanningRectsPage.png-dark
    • AppWindowPositionAndSize.png
    • Arrange_Performance01.png
    • AppWindowPositionAndSize.png-dark
    • ArcSegment.png
    • Attributed_text_Supserscript.png-dark
    • ArcSegment.png-dark
    • AuthenticationBroker_Demo.png-dark
    • AutoBorderStretchwithbottommargin.png
    • AuthenticationBroker_Cancel.png
    • AutoBorderStretchwithleftmargin.png

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

Thanks, this is thorough work, and replacing the fixed 600 ms delays with stamped touch times is a nice improvement to the tests. Inline comments cover the specific points. Two broader ones:

Validation scope. This replaces the composition path for every browser, not just Android: all WebAssembly compositions now report the text as already applied, and composition start is reported later. The evidence is synthetic events in Chromium's order plus Samsung Keyboard on one tablet, since Gboard never composed there. Could you do a quick manual pass with a desktop CJK IME before merge, e.g. Microsoft IME Japanese in Chrome on Windows and Pinyin in Safari on macOS?

Splitting the commit. The single commit bundles the WebAssembly IME rewrite with the touch-selection changes, which affect every Skia host, native Android and iOS included. Splitting touch selection into its own commit would make a regression in either easier to bisect.

This review was done by reading the code; I didn't build or run it locally.

@ajpinedam
ajpinedam force-pushed the dev/anpi/fix.wasm.android.textbox.input branch from 85fd5c4 to 4cca259 Compare November 2, 2026 18:03
Copilot AI balanced review requested due to automatic review settings November 2, 2026 18:03
@ajpinedam

Copy link
Copy Markdown
Contributor Author

Thanks for the review. On the two broader points:

Splitting the commit. Done. The branch is now two commits, and each builds on its own:

  • a6c92ef fix(wasm): browser IME composition, soft-keyboard keys and edits the browser applied. It includes the TextBoxCore/RichEditBox IME-host changes those need.
  • 4cca259 fix(textbox): touch selection under the Android and iOS conventions, on every Skia host.

The fixes for your inline comments are folded into the commit each one belongs to.

Validation scope. Not done yet. The manual pass with desktop CJK IMEs (Microsoft IME Japanese in Chrome on Windows, Pinyin in Safari on macOS) is still to do, and will happen before merge. I'll post the results here.

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

Active issues leave composition completion reporting stale text and splitting RichEditBox undo history, while wrapped-line hit testing can misclassify repeated taps and prevent word selection.

2 open issues
What changed in this PR

This PR fixes browser-driven text editing on Skia WebAssembly and refines mobile-style touch selection across Skia hosts.

Changes:

  • Makes browser input authoritative for IME text, with selection reconciliation, undo tracking, and callback exception handling.
  • Handles code-less soft-keyboard keys and prevents duplicate Enter notifications.
  • Refines touch word selection, multi-tap detection, and caret placement, with regression tests.
File Description
src/​Uno.UI/​UI/​Xaml/​Documents/​ParsedTextExtensions.cs Adds shared vertical clamping helpers.
src/​Uno.UI/​UI/​Xaml/​Controls/​TextBoxCore/​TextBoxCore.pointers.cs Refines touch hit testing and multi-tap detection.
src/​Uno.UI/​UI/​Xaml/​Controls/​TextBoxCore/​TextBoxCore.Input.cs Tracks native typing and handles insertion-handle taps.
src/​Uno.UI/​UI/​Xaml/​Controls/​TextBoxCore/​TextBoxCore.IME.cs Tracks platform composition ranges and filter edits.
src/​Uno.UI/​UI/​Xaml/​Controls/​TextBoxCore/​TextBoxCore.cs Clears platform-apply state after input processing.
src/​Uno.UI/​UI/​Xaml/​Controls/​TextBoxCore/​TextBoxCore.CaretDrag.cs Reuses shared line-clamping geometry.
src/​Uno.UI/​UI/​Xaml/​Controls/​TextBox/​ImeSessionCoordinator.cs Forwards composition start positions.
src/​Uno.UI/​UI/​Xaml/​Controls/​TextBox/​IImeSessionHost.cs Extends the composition-update contract.
src/​Uno.UI/​UI/​Xaml/​Controls/​TextBox/​Extensions/​ImeCompositionEventArgs.cs Carries the platform composition start index.
src/​Uno.UI/​UI/​Xaml/​Controls/​TextBlock/​TextBlock.cs Adapts to the gripper-tap return contract.
src/​Uno.UI/​UI/​Xaml/​Controls/​RichEditBox/​RichEditBox.uno.cs Adapts gripper-tap handling.
src/​Uno.UI/​UI/​Xaml/​Controls/​RichEditBox/​RichEditBox.IME.cs Tracks platform-applied composition ranges.
src/​Uno.UI/​UI/​Xaml/​Controls/​Primitives/​TextSelectionGripperPresenter.cs Shares clamping and adjusts flyout requests.
src/​Uno.UI.RuntimeTests/​Microsoft/​UI/​Xaml/​Input/​Given_BrowserKeyboardInputSource.cs Tests code-less key mapping.
src/​Uno.UI.RuntimeTests/​Microsoft/​UI/​Xaml/​Controls/​Given_TextBox.skia.cs Adds browser editing and touch regressions.
src/​Uno.UI.RuntimeTests/​Microsoft/​UI/​Xaml/​Controls/​Given_RichEditBox.Ime.skia.cs Updates browser composition lifecycle tests.
src/​Uno.UI.RuntimeTests/​Microsoft/​UI/​Xaml/​Controls/​Given_ImeSessionCoordinator_RootOwnership.skia.cs Updates the test host signature.
src/​Uno.UI.RuntimeTests/​Microsoft/​UI/​Xaml/​Automation/​Given_AccessibleTextBox.cs Tests soft-keyboard handling through semantic inputs.
src/​Uno.UI.Runtime.WebAssembly.Browser/​UI/​Xaml/​Controls/​TextBox/​WasmImeTextBoxExtension.cs Reports browser-applied compositions and catches callback failures.
src/​Uno.UI.Runtime.WebAssembly.Browser/​UI/​Xaml/​Controls/​TextBox/​BrowserInvisibleTextBoxViewExtension.cs Applies native edits and restores rejected-edit selections.
src/​Uno.UI.Runtime.WebAssembly.Browser/​ts/​Runtime/​BrowserInvisibleTextBoxViewExtension.ts Reworks DOM composition, selection, and keyboard handling.
src/​Uno.UI.Runtime.WebAssembly.Browser/​Devices/​Input/​BrowserKeyboardInputSource.cs Maps named keys when physical codes are absent.

Review effort: Balanced

@unodevops

Copy link
Copy Markdown
Contributor

🤖 Your WebAssembly Sample App stage site is ready! Visit it here:

@nventive-devops

Copy link
Copy Markdown
Contributor

The build 242930 found UI Test snapshots differences: skia-linux-screenshots: 2458, skia-windows-screenshots: 2458

Details
  • skia-linux-screenshots: 2458 changed over 2492

    🚨🚨 Comparison Details (first 20) 🚨🚨
    • AnimatedBackVisualSourceSamplePage.png-dark
    • AnimatedVisualsTestbedPage.png
    • ApplicationViewSpanningRectsPage.png
    • AppWindowFeatures.png
    • AppWindowTitleBar Properties.png
    • AuthenticationBroker_Cancel.png-dark
    • AutoBorderStretchwithleftmargin.png
    • AutomationProperties_Landmark.png-dark
    • AutoSizedPathCentered.png
    • AutoSizedTopLeft.png-dark
    • AutoSuggestBox_Keyboard.png
    • AutoSuggestBox_Reason.png-dark
    • BasicAutoSuggestBox.png-dark
    • BasicAutoSuggestBox.png
    • Battery.png-dark
    • Battery.png
    • BezierSegment.png-dark
    • BezierSegment.png
    • BitmapIcon_Monochromatic.png-dark
    • BitmapIcon_Monochromatic.png
  • skia-windows-screenshots: 2458 changed over 2492

    🚨🚨 Comparison Details (first 20) 🚨🚨
    • AnimatedIconPage.png-dark
    • AnimatedVisualsTestbedPage.png-dark
    • AppBarButtonWithIconTest.png
    • ApplicationViewSizing.png-dark
    • AppWindowClosing.png-dark
    • AppWindowPresenters.png
    • ArcSegment.png
    • Attributed_text_Supserscript.png
    • AutoBorderStretchwithleftmargin.png
    • AutoBorderStretchwithrightmargin.png-dark
    • AutomationProperties_Landmark.png
    • AutomationProperties_Name.png-dark
    • AutoSuggestBox_Icons.png-dark
    • AutoSuggestBox_Icons.png
    • BasicEnability.png-dark
    • BasicEnability.png
    • BenchmarkDotNet.png-dark
    • BenchmarkDotNet.png
    • BitmapIcon_Generic.png-dark
    • BitmapIcon_Generic.png

@ajpinedam
ajpinedam force-pushed the dev/anpi/fix.wasm.android.textbox.input branch from 4cca259 to 1d78ae4 Compare November 2, 2026 21:15
@nventive-devops

Copy link
Copy Markdown
Contributor

The build 242987 found UI Test snapshots differences: skia-linux-screenshots: 2458, skia-windows-screenshots: 2458

Details
  • skia-linux-screenshots: 2458 changed over 2492

    🚨🚨 Comparison Details (first 20) 🚨🚨
    • AnimatedBackVisualSourceSamplePage.png
    • AnimatedVisualsTestbedPage.png-dark
    • AppBar_KeyBoard.png
    • ApplicationViewSpanningRectsPage.png
    • AppWindowPositionAndSize.png
    • Arrange_Performance01.png
    • AuthenticationBroker_Cancel.png-dark
    • AutoBorderStretchwithrightmargin.png
    • AutomationProperties_Name.png-dark
    • AutomationProperties_Name.png
    • AutoSuggestBox_BitmapIcon.png-dark
    • AutoSuggestBox_BitmapIcon.png
    • BadgeNotificationTests.png-dark
    • BadgeNotificationTests.png
    • Basics_Automated.png-dark
    • Basics_Automated.png
    • BindableDrawerLayout_ChangePane.png-dark
    • BindableDrawerLayout_ChangePane.png
    • BitmapImage_vs_SvgImageSource.png-dark
    • BitmapImage_vs_SvgImageSource.png
  • skia-windows-screenshots: 2458 changed over 2492

    🚨🚨 Comparison Details (first 20) 🚨🚨
    • AndroidWindowInsets.png-dark
    • AppBarButtonTest.png-dark
    • AppBar_KeyBoard.png-dark
    • ApplicationViewSizing.png
    • AppWindowFeatures.png-dark
    • AppWindowTitleBar Properties.png-dark
    • ArcSegment.png
    • Attributed_text_Supserscript.png-dark
    • AuthenticationBroker_Demo.png
    • AutoBorderStretchwithbottommargin.png-dark
    • AutomationProperties_AutomationId.png-dark
    • AutomationProperties_AutomationId.png
    • AutoSizedPathCentered.png-dark
    • AutoSizedPathCentered.png
    • AutoSuggestBox_BitmapIcon.png-dark
    • AutoSuggestBox_BitmapIcon.png
    • AutoSuggestBox_SoftKeboard.png-dark
    • AutoSuggestBox_SoftKeboard.png
    • BasicAutoSuggestBox.png-dark
    • BasicAutoSuggestBox.png

@ajpinedam
ajpinedam force-pushed the dev/anpi/fix.wasm.android.textbox.input branch from 1d78ae4 to 9331393 Compare November 3, 2026 00:58
@ajpinedam
ajpinedam enabled auto-merge November 3, 2026 01:31
@nventive-devops

Copy link
Copy Markdown
Contributor

The build 243021 found UI Test snapshots differences: skia-linux-screenshots: 2458, skia-windows-screenshots: 2458

Details
  • skia-linux-screenshots: 2458 changed over 2492

    🚨🚨 Comparison Details (first 20) 🚨🚨
    • AnimatedBackVisualSourceSamplePage.png-dark
    • AnimatedIconPage.png
    • AppBarToggleButtonTest.png-dark
    • ApplicationViewSizing.png-dark
    • AppWindowClosing.png
    • AppWindowPresenters.png
    • Arrange_Performance01.png
    • Attributed_text_Supserscript.png
    • AutoBorderStretchwithleftmargin.png-dark
    • AutoBorderStretchwithleftmargin.png
    • AutomationProperties_Name.png-dark
    • AutomationProperties_Name.png
    • AutoSuggestBox_Keyboard.png-dark
    • AutoSuggestBox_Keyboard.png
    • BasicAutoSuggestBox.png-dark
    • BasicAutoSuggestBox.png
    • Basics_Automated.png-dark
    • Basics_Automated.png
    • BenchmarkDotNet.png-dark
    • BenchmarkDotNet.png
  • skia-windows-screenshots: 2458 changed over 2492

    🚨🚨 Comparison Details (first 20) 🚨🚨
    • AndroidWindowInsets.png-dark
    • AppBarButtonTest.png-dark
    • AppBar_KeyBoard.png-dark
    • ApplicationViewSpanningRectsPage.png-dark
    • AppWindowClosing.png
    • AppWindowTitleBar Properties.png-dark
    • Arrange_Performance01.png
    • Attributed_text_Supserscript.png-dark
    • AuthenticationBroker_Demo.png
    • AutoBorderStretchwithbottommargin.png-dark
    • AutomationPropertiesExtensions_Role.png-dark
    • AutomationPropertiesExtensions_Role.png
    • AutoSuggestBoxChosenSuggestion.png-dark
    • AutoSuggestBoxChosenSuggestion.png
    • AutoSuggestBox_Reason.png-dark
    • AutoSuggestBox_Reason.png
    • BasicAcrylicBrushTest.png-dark
    • BasicAcrylicBrushTest.png
    • BitmapIcon_Sizing.png-dark
    • BitmapIcon_Sizing.png

@ajpinedam
ajpinedam force-pushed the dev/anpi/fix.wasm.android.textbox.input branch from 9331393 to 6d4c191 Compare November 3, 2026 11:41
@nventive-devops

Copy link
Copy Markdown
Contributor

The build 243187 found UI Test snapshots differences: skia-linux-screenshots: 2458, skia-windows-screenshots: 2458

Details
  • skia-linux-screenshots: 2458 changed over 2492

    🚨🚨 Comparison Details (first 20) 🚨🚨
    • AndroidWindowInsets.png-dark
    • AnimatedIconPage.png-dark
    • AnimatedIconPage.png
    • AppBarButtonTest.png
    • AppBarToggleButtonTest.png-dark
    • AppBar_KeyBoard.png
    • ApplicationViewSizing.png-dark
    • Application_DispatcherShutdownMode.png-dark
    • AppWindowClosing.png
    • AppWindowPresenters.png
    • AppWindowTitleBar Properties.png-dark
    • AppXamlDefinedResources.png
    • Attributed_text_FontSize_Changing.png-dark
    • Arrange_Performance01.png
    • AuthenticationBroker_Cancel.png
    • Attributed_text_Supserscript.png-dark
    • AutomationProperties_AutomationId.png
    • AuthenticationBroker_Demo.png-dark
    • AutomationProperties_Landmark.png-dark
    • AuthenticationBroker_Demo.png
  • skia-windows-screenshots: 2458 changed over 2492

    🚨🚨 Comparison Details (first 20) 🚨🚨
    • AppBarButtonWithIconTest.png
    • AppBar_KeyBoard.png
    • ApplicationViewSpanningRectsPage.png-dark
    • AppWindowClosing.png-dark
    • AppWindowPositionAndSize.png
    • AppXamlDefinedResources.png-dark
    • Attributed_text_Simple.png-dark
    • AuthenticationBroker_Demo.png-dark
    • AutoBorderStretchwithbottommargin.png
    • AutoBorderStretchwithleftmargin.png-dark
    • AutomationPropertiesExtensions_Role.png-dark
    • AutomationPropertiesExtensions_Role.png
    • AutomationProperties_StructureAndProps.png-dark
    • AutomationProperties_StructureAndProps.png
    • AutoSuggestBox_Description.png-dark
    • AutoSuggestBox_Description.png
    • BadgeNotificationTests.png-dark
    • BadgeNotificationTests.png
    • BasicAutoSuggestBox.png
    • BasicEnability.png-dark

@ajpinedam
ajpinedam merged commit ea115be into unoplatform:master Nov 3, 2026
47 checks passed
@ajpinedam
ajpinedam deleted the dev/anpi/fix.wasm.android.textbox.input branch November 3, 2026 15:23
@ajpinedam

Copy link
Copy Markdown
Contributor Author

@Mergifyio backport servicing/6.8

@mergify

mergify Bot commented Nov 3, 2026 •

Copy link
Copy Markdown
Contributor

backport servicing/6.8

✅ Backports have been created

Details

Cherry-pick of e1991c2 has failed:

On branch mergify/bp/servicing/6.8/pr-25804
Your branch is up to date with 'origin/servicing/6.8'.

You are currently cherry-picking commit e1991c2.
  (fix conflicts and run "git cherry-pick --continue")
  (use "git cherry-pick --skip" to skip this patch)
  (use "git cherry-pick --abort" to cancel the cherry-pick operation)

Changes to be committed:
	modified:   src/Uno.UI.Runtime.Skia.WebAssembly.Browser/Devices/Input/BrowserKeyboardInputSource.cs
	modified:   src/Uno.UI.RuntimeTests/Tests/Windows_UI_Xaml_Input/Given_BrowserKeyboardInputSource.cs
	modified:   src/Uno.UI/UI/Xaml/Controls/TextBox/Extensions/ImeCompositionEventArgs.skia.cs

Unmerged paths:
  (use "git add/rm <file>..." as appropriate to mark resolution)
	both modified:   src/Uno.UI.Runtime.Skia.WebAssembly.Browser/UI/Xaml/Controls/TextBox/BrowserInvisibleTextBoxViewExtension.cs
	both modified:   src/Uno.UI.Runtime.Skia.WebAssembly.Browser/UI/Xaml/Controls/TextBox/WasmImeTextBoxExtension.cs
	both modified:   src/Uno.UI.Runtime.Skia.WebAssembly.Browser/ts/Runtime/BrowserInvisibleTextBoxViewExtension.ts
	deleted by us:   src/Uno.UI.RuntimeTests/Microsoft/UI/Xaml/Automation/Given_AccessibleTextBox.cs
	deleted by us:   src/Uno.UI.RuntimeTests/Microsoft/UI/Xaml/Controls/Given_ImeSessionCoordinator_RootOwnership.skia.cs
	deleted by us:   src/Uno.UI.RuntimeTests/Microsoft/UI/Xaml/Controls/Given_RichEditBox.Ime.skia.cs
	both modified:   src/Uno.UI.RuntimeTests/Tests/Windows_UI_Xaml_Controls/Given_TextBox.skia.cs
	deleted by us:   src/Uno.UI/UI/Xaml/Controls/RichEditBox/RichEditBox.IME.cs
	deleted by us:   src/Uno.UI/UI/Xaml/Controls/TextBox/IImeSessionHost.cs
	deleted by us:   src/Uno.UI/UI/Xaml/Controls/TextBox/ImeSessionCoordinator.cs
	both modified:   src/Uno.UI/UI/Xaml/Controls/TextBox/TextBox.IME.skia.cs
	both modified:   src/Uno.UI/UI/Xaml/Controls/TextBox/TextBox.skia.cs
	deleted by us:   src/Uno.UI/UI/Xaml/Controls/TextBoxCore/TextBoxCore.cs

Cherry-pick of 6d4c191 has failed:

On branch mergify/bp/servicing/6.8/pr-25804
Your branch is ahead of 'origin/servicing/6.8' by 1 commit.
  (use "git push" to publish your local commits)

You are currently cherry-picking commit 6d4c191.
  (fix conflicts and run "git cherry-pick --continue")
  (use "git cherry-pick --skip" to skip this patch)
  (use "git cherry-pick --abort" to cancel the cherry-pick operation)

Changes to be committed:
	modified:   src/Uno.UI.RuntimeTests/Tests/Windows_UI_Xaml_Controls/Given_TextBox.skia.cs
	new file:   src/Uno.UI/UI/Xaml/Documents/ParsedTextExtensions.cs

Unmerged paths:
  (use "git add/rm <file>..." as appropriate to mark resolution)
	both modified:   src/Uno.UI/UI/Xaml/Controls/Primitives/TextSelectionGripperPresenter.skia.cs
	deleted by us:   src/Uno.UI/UI/Xaml/Controls/RichEditBox/RichEditBox.uno.cs
	both modified:   src/Uno.UI/UI/Xaml/Controls/TextBlock/TextBlock.cs
	both modified:   src/Uno.UI/UI/Xaml/Controls/TextBox/TextBox.skia.cs
	deleted by us:   src/Uno.UI/UI/Xaml/Controls/TextBoxCore/TextBoxCore.CaretDrag.cs
	deleted by us:   src/Uno.UI/UI/Xaml/Controls/TextBoxCore/TextBoxCore.pointers.cs

To fix up this pull request, you can check it out locally. See documentation: https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/reviewing-changes-in-pull-requests/checking-out-pull-requests-locally

agneszitte added a commit that referenced this pull request Nov 4, 2026
…orts-6.7

fix(input): Tap order, iOS touch text editing and Android browser IME (backport #25818, #25619, #25804)
@mikernet

Copy link
Copy Markdown
Contributor

@ajpinedam This overlaps with #24502, an earlier fix for the same Android soft keyboard sync that was approved in September but hadn't merged. I ran its tests against master after this PR and found a few bugs that are still there, including one this PR introduced: TextCompositionChanged is now raised before its text is in Text, so a handler using the event's range reads text that isn't there yet. Details and a proposal for a smaller follow-up on top of this design are in #24502 (comment). Would you take a look?

mikernet added a commit to mikernet/uno that referenced this pull request Nov 12, 2026
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.
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.
mikernet added a commit to Singulink/uno that referenced this pull request Nov 14, 2026
Port of unoplatform#26434 (for unoplatform#26432), commits
faba3ed, 096daef, 8e2644f, b8d2189, edce6fa and
6939ef4, onto the unoplatform#25804 IME design this branch takes
from servicing/6.8 (unoplatform#25984).

- TextCompositionChanged for text the platform applied (WebAssembly,
  Skia Android) waits for the platform's text sync, and isn't raised
  for an update the TextBox refused. The Changed and no-commit Ended
  ranges are clamped to the text. Skia Android reports where its
  composing region starts.
- A composition ends when the hidden input is replaced or detached.
- An edit a BeforeTextChanging handler or a read-only TextBox refuses
  is written back to the input at once, ending the composition.
  Unlike master, this branch does the same for an edit MaxLength
  refuses or a TextChanging handler changes (see below).
- The hidden input is read-only with the TextBox.
- The caret goes back at once after a refused edit.

6.8 differences:
- Master's TextBoxCore.IME.cs, .Input.cs and .cs changes go to
  TextBox.IME.skia.cs, TextBox.skia.cs and TextBox.cs; events are raised
  on the TextBox itself. There is no RichEditBox IME, partial commit or
  CompositionCanceled on 6.8, so those parts are left out.
- Skia Android syncs through a new TextBox.UpdateTextFromNative, the
  6.8 counterpart of master's IImeSessionHost.UpdateTextFromNative. It
  raises the held TextCompositionChanged, and compares the editable's
  \n line breaks as the TextBox's \r, as master's
  IsNativeEchoOfCurrentText does.
- The read-only input follows TextBox.IsReadOnly through
  OnIsReadonlyChangedPartial; UpdateProperties, which only sets the
  enter key hint on 6.8, now also sets readOnly or re-enters the input.

With this branch's port of unoplatform#26005 (be85699):
- A refused platform sync puts the composition range back on the text
  that is there (unoplatform#26005), and raises TextCompositionChanged
  for that range only when it isn't the last range raised. The refused
  update's own range is never raised (unoplatform#26434).
- While a composition holds text the TextBox refused past MaxLength
  (reconciled when it ends), the hidden input's selectionchange isn't
  reported: its offsets are in that text, not in the TextBox's. Without
  this, the input's caret replaced the one edce6fa restores at once.
- ApplyNativeInput keeps unoplatform#26005's handling of a refused
  edit's pending selection.

Like Android's own LengthFilter (and this branch before the catch-up),
a composition edit the TextBox doesn't keep as the keyboard applied it
(MaxLength, a TextChanging filter, as well as BeforeTextChanging and
read-only) writes the TextBox's text back at once and ends the
composition, on WebAssembly and on Skia Android. Master keeps a
MaxLength or TextChanging refusal in the input until the composition
ends, so a CJK conversion can bring it back within the limit. Gboard
composes every Latin word, though, so the refused letters stayed in the
input for the whole word: its suggestions were built on them, and
Backspace deleted them before it reached the TextBox's text. A
conversion past the limit is lost here, as with Android's LengthFilter.
When_Browser_Ime_Composes_With_TextChanging_Filter (unoplatform#25804)
and the two conversion tests of unoplatform#26434 now expect that,
and When_Browser_Ime_Composes_Word_Past_MaxLength_Then_Input_Keeps_The_Limit
covers the Gboard case.

Adds tests for a refused Skia Android sync, \n line breaks in its sync,
and a refused composition followed by another before the dispatcher
runs.
mikernet added a commit to Singulink/uno that referenced this pull request Nov 14, 2026
Port of the review round of unoplatform#26434 (commits 0e475cdb9e
and 73279a2cf1 there) onto this branch's write-back.

- Narrower rule: the input or the native editable takes the TextBox's
  text back at once, ending the composition, only when the TextBox
  refused the edit or kept text of another length (MaxLength, a length
  limit). A change that keeps the length (an upper-case TextChanging
  filter) waits for the end of the composition again, as
  unoplatform#25804 made it, so an IME can still convert it:
  When_Browser_Ime_Composes_With_TextChanging_Filter is back to
  servicing/6.8's version. TextBox.MustReturnTextToNative holds the
  rule, and compares without allocating.
- Skia Android's EndBatchEdit no longer reads ActiveTextBox again after
  the sync, which runs app handlers that may move focus or clear it
  (a null reference in the IME callback): it writes back only to the
  TextBox it synced, and only after the IME changed the text. The
  write-back is one batch for the listeners, so a TextCompositionEnded
  handler that sets the text again is written after it.
- The comments no longer describe this as what Android's LengthFilter
  does, and the remaining ones about the end-of-composition
  reconciliation say which edits it still covers.
- When_Browser_Ime_Composes_Past_MaxLength and
  When_Browser_Ime_Composition_Coerced_Shorter_Than_Its_Start check the
  input right after the update, and that the browser's late
  compositionend changes nothing.

TextBox.UpdateTextFromNative already kept the TextBox's caret on a
refused sync (the applied check of unoplatform#26005), unlike master's;
When_IME_Platform_Sync_Refused_Then_Caret_Stays covers it here too.
6.8 has no RichEditBox IME, so the RichEditBox part doesn't apply.
mikernet added a commit to mikernet/uno that referenced this pull request Nov 14, 2026
A composition edit the TextBox refused stayed in the hidden input until
the composition ended, unless a BeforeTextChanging handler or a
read-only TextBox refused it: an edit MaxLength refused, or that a
length limit put back, was left for the end of the composition, so that
a CJK conversion could still bring it back within the limit. Gboard
composes every Latin word, though, so the letters typed past MaxLength
stayed in the input for the whole word: the keyboard built its
suggestions on them, and Backspace deleted them before it reached the
TextBox's text.

This replaces that trade-off. When the TextBox keeps text of another
length than the input's (MaxLength, a length limit), or refuses a change
of the input's text and keeps its own previous text, its text goes back
to the input at once and the composition ends, so the keyboard builds
its next keystrokes and suggestions on the text the TextBox kept. The
input now reports its value at the previous sync with each edit, so the
same text reported again (a caret move inside the preedit) doesn't count
as a refusal while a same-length filter keeps the two texts apart. Kana
typed past a limit is cut at the limit rather than converted later. A
change that keeps the length (an upper-case filter) still waits for the
end of the composition, as unoplatform#25804 made it, so an IME can
still convert it. ImeSessionCoordinator.MustReturnTextToNative holds the
rule.

RichEditBox gets the same rule, so an edit it refuses or cuts to its
text limit is written back, and its CharacterCasing waits for the end of
the composition. ApplyNativeInput no longer has to tell a refusal from a
handler's put-back text, so its result and the text change count go.

Tests: When_Browser_Ime_Converts_Past_MaxLength and
When_Browser_Ime_Converts_Past_TextChanging_Limit become
When_Browser_Ime_Kana_Composes_Past_MaxLength and
When_Browser_Ime_Kana_Composes_Past_TextChanging_Limit;
When_Browser_Ime_Composes_Word_Past_MaxLength_Then_Input_Keeps_The_Limit
covers the Gboard case and
Given_RichEditBox.When_Wasm_Composition_Past_MaxLength_Is_Written_Back
the RichEditBox, with
When_Wasm_Composition_Past_MaxLength_In_The_Middle_Keeps_The_Caret and
When_Wasm_Composition_With_Upper_Casing_Stays_Open.
When_Browser_Ime_Filtered_Composition_Reported_Again_Stays_Open,
When_Browser_Ime_Same_Length_Replacement_Is_Refused and the table test
When_Native_Text_Is_Compared_To_The_Kept_Text cover the rule's clauses.
When_Browser_Ime_Composes_Past_MaxLength and
When_Browser_Ime_Composition_Coerced_Shorter_Than_Its_Start now check
the input right after the update, and that the browser's late
compositionend changes nothing.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/skia ✏️ Categorizes an issue or PR as relevant to Skia 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.

7 participants