Fix touch event handling, improve reliability, and optimize performance#16015
Fix touch event handling, improve reliability, and optimize performance#16015gmacmaster wants to merge 6 commits intomicrosoft:mainfrom
Conversation
- Fix touch/pen pointer device type detection and screenPoint coordinates - Fix touch cancel to include all active touches per W3C spec - Synthesize touch-cancel for stale pointers and releases outside views - Fix TextInput pointer message translation (use mouse-style messages for RichEdit) - Fix ShouldSubmit modifier key checks (altDown, ctrlKey) - Add null safety to RootComponentView() for island teardown - Fix Pressability hover timeout and tabIndex focusable mapping - Cache event path to root to avoid repeated tree walks - Use unordered_set for pointer capture tracking - Eliminate O(n²) hit testing by caching visual children - Skip snap scroll reconfiguration when unchanged - Improve TextInput reliability: thread-safe loading, null safety, use-after-free fix - Fix Timing data race and remove duplicate image error allocation - Use unordered_set for animated node and component registry lookups - Clean up dead code in ScrollView and simplify Modal event emitter init
| return event; | ||
| } | ||
|
|
||
| bool CompositionEventHandler::IsPointerWithinInitialTree(const ActiveTouch &activeTouch) noexcept { |
There was a problem hiding this comment.
This now does a live hitTest instead of checking the stored [touch.target] against the ancestor chain.
Finger drift during a tap will suppress onClick. Test with Pressable on touch devices.
| } else { | ||
| msg = WM_POINTERDOWN; | ||
| wParam = PointerPointToPointerWParam(pp); | ||
| if (IsDoubleClick()) { |
There was a problem hiding this comment.
please also test touch long-press text selection and pen barrel button with real hardware.
acoates-ms
left a comment
There was a problem hiding this comment.
Generally, a great set of changes. Thanks for the PR!
| } else { | ||
| msg = WM_POINTERUP; | ||
| wParam = PointerPointToPointerWParam(pp); | ||
| msg = WM_LBUTTONUP; |
There was a problem hiding this comment.
Shouldn't we be sending pointer events to richedit? I imagine there could be cases where richedit could get confused by multiple pointers if we are sending both touch and mouse as mouse events?
Just wondering on the reasoning for dropping the pointer events?
| const winrt::Microsoft::ReactNative::Composition::Experimental::IVisual &visual, | ||
| uint32_t index) noexcept { | ||
| assert(index <= m_childrenCache.size()); | ||
| if (index > m_childrenCache.size()) { |
There was a problem hiding this comment.
I think I'd rather just crash here if we are getting an invalid index input. Otherwise, it's an incorrect behavior that people might start relying on.
| void InsertAt( | ||
| const winrt::Microsoft::ReactNative::Composition::Experimental::IVisual &visual, | ||
| uint32_t index) noexcept { | ||
| assert(index <= m_childrenCache.size()); |
There was a problem hiding this comment.
Crash rather than hiding error
| onStartShouldSetResponder: (): boolean => { | ||
| const {disabled} = this._config; | ||
| return !disabled ?? true; | ||
| return disabled !== true; // falsy/undefined disabled => responder allowed |
There was a problem hiding this comment.
This looks like this is from upstream? Can you make a PR with this in react-native rather than adding behavior differences in windows?
| if (delayHoverOut > 0) { | ||
| event.persist(); | ||
| this._hoverInDelayTimeout = setTimeout(() => { | ||
| this._hoverOutDelayTimeout = setTimeout(() => { |
There was a problem hiding this comment.
This looks like this is from upstream? Can you make a PR with this in react-native rather than adding behavior differences in windows?
|
|
||
| if (tabIndex !== undefined) { | ||
| processedProps.focusable = !tabIndex; | ||
| processedProps.focusable = tabIndex >= 0; |
There was a problem hiding this comment.
This looks like this is from upstream? Can you make a PR with this in react-native rather than adding behavior differences in windows?
|
/azp run |
|
Azure Pipelines successfully started running 1 pipeline(s). |
Performance Test ResultsBranch: ❌ Regressions DetectedFlatList
✅ Passed147 scenario(s) across 27 suite(s) — no regressionsSectionList
TouchableOpacity
ScrollView
TouchableHighlight
Pressable
Modal
Image
ActivityIndicator
Switch
Button
TextInput
View
Text
SectionList.native-perf-test.ts
FlatList.native-perf-test.ts
TouchableHighlight.native-perf-test.ts
TouchableOpacity.native-perf-test.ts
Pressable.native-perf-test.ts
ScrollView.native-perf-test.ts
ActivityIndicator.native-perf-test.ts
TextInput.native-perf-test.ts
Switch.native-perf-test.ts
Button.native-perf-test.ts
Modal.native-perf-test.ts
Image.native-perf-test.ts
View.native-perf-test.ts
Text.native-perf-test.ts
|
Description
This PR bundles several touch stability and performance improvements for the Windows React Native Fabric renderer: it adds a m_childrenCache to avoid O(n) WinRT iterator traversal on every GetAt, hardens the codebase against a null RootComponentView, converts m_capturedPointers / m_children / m_componentNames to std::unordered_set for O(1) operations, and adds snap-point change detection in CompScrollerVisual to skip redundant reconfiguration.
Also brings over issues found in #16009
Type of Change
Why
Touch functionality often drops/misses touches from the user
What
What changes were made to the codebase to solve the bug, add the functionality, etc. that you specified above.
Changelog
Should this change be included in the release notes: yes
Fix touch event handling, improve reliability, and optimize performance
Important Files Changed
Sequence Diagram
sequenceDiagram participant W as WinRT Input participant EH as CompositionEventHandler participant RC as RootComponentView* participant FUI as FabricUIManager participant JS as JS EventEmitter W->>EH: PointerPressed/Moved/Released EH->>EH: RootComponentView() → raw ptr (null-safe) alt rootView == nullptr EH-->>W: early return (no crash) else rootView valid EH->>RC: hitTest(clientPoint, ptLocal) RC-->>EH: targetTag EH->>FUI: GetViewRegistry().componentViewDescriptorWithTag(targetTag) FUI-->>EH: targetComponentView EH->>EH: DispatchTouchEvent(eventType, pointerId, ...) loop for each activeTouch EH->>RC: hitTest (null-checked) EH->>EH: handler λ [this, &activeTouch, &pointerEvent] note over EH: IsPointerWithinInitialTree() walks parent chain EH->>JS: onPointerDown/Move/Up/Cancel + onClick/onAuxClick end loop for each uniqueEventEmitter EH->>JS: onTouchStart/Move/End/Cancel end endMicrosoft Reviewers: Open in CodeFlow