Skip to content

Commit b6173cf

Browse files
Fix touch event handling, improve reliability, and optimize performance (#16048)
* Fix touch event handling, improve reliability, and optimize performance - 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 * formatting * Change files * Update react-native-windows-cd995d7a-4df8-4c73-9b1b-c57b3b886cc6.json * Touch target * addressed pr comments * Crash on invalid index * revert until rn core pr * Update react-native-windows-cd995d7a-4df8-4c73-9b1b-c57b3b886cc6.json * format * snapshots --------- Co-authored-by: Gordon MacMaster <31481849+gmacmaster@users.noreply.github.com> Co-authored-by: Gordon MacMaster <gordomacmaster@gmail.com>
1 parent 3cddef8 commit b6173cf

15 files changed

Lines changed: 405 additions & 152 deletions
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"type": "prerelease",
3+
"comment": "Fix touch event handling, improve reliability, and optimize performance",
4+
"packageName": "react-native-windows",
5+
"email": "gordomacmaster@gmail.com",
6+
"dependentChangeType": "patch"
7+
}

vnext/Microsoft.ReactNative/Fabric/Composition/CompositionContextHelper.cpp

Lines changed: 58 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22
#include "pch.h"
33
#include "CompositionContextHelper.h"
44
#include <algorithm>
5+
#include <cassert>
6+
#include <exception>
7+
#include <vector>
58
#if __has_include("Composition.Experimental.SystemCompositionContextHelper.g.cpp")
69
#include "Composition.Experimental.SystemCompositionContextHelper.g.cpp"
710
#endif
@@ -431,30 +434,43 @@ struct CompVisualImpl {
431434
void InsertAt(
432435
const winrt::Microsoft::ReactNative::Composition::Experimental::IVisual &visual,
433436
uint32_t index) noexcept {
437+
if (index > m_childrenCache.size()) {
438+
std::terminate();
439+
}
434440
auto containerChildren = InnerVisual().as<typename TTypeRedirects::ContainerVisual>().Children();
435441
auto compVisual = TTypeRedirects::CompositionContextHelper::InnerVisual(visual);
436442
if (index == 0) {
437443
containerChildren.InsertAtBottom(compVisual);
438-
return;
444+
} else {
445+
auto insertAfter = containerChildren.First();
446+
for (uint32_t i = 1; i < index; i++)
447+
insertAfter.MoveNext();
448+
containerChildren.InsertAbove(compVisual, insertAfter.Current());
449+
}
450+
if (index >= m_childrenCache.size()) {
451+
m_childrenCache.push_back(visual);
452+
} else {
453+
m_childrenCache.insert(m_childrenCache.begin() + index, visual);
439454
}
440-
auto insertAfter = containerChildren.First();
441-
for (uint32_t i = 1; i < index; i++)
442-
insertAfter.MoveNext();
443-
containerChildren.InsertAbove(compVisual, insertAfter.Current());
444455
}
445456

446457
void Remove(const winrt::Microsoft::ReactNative::Composition::Experimental::IVisual &visual) noexcept {
447458
auto compVisual = TTypeRedirects::CompositionContextHelper::InnerVisual(visual);
448459
auto containerChildren = InnerVisual().as<typename TTypeRedirects::ContainerVisual>().Children();
449460
containerChildren.Remove(compVisual);
461+
auto it = std::find_if(
462+
m_childrenCache.begin(), m_childrenCache.end(), [&visual](const auto &cached) { return cached == visual; });
463+
if (it != m_childrenCache.end()) {
464+
m_childrenCache.erase(it);
465+
}
450466
}
451467

452468
winrt::Microsoft::ReactNative::Composition::Experimental::IVisual GetAt(uint32_t index) noexcept {
453-
auto containerChildren = m_visual.as<typename TTypeRedirects::ContainerVisual>().Children();
454-
auto it = containerChildren.First();
455-
for (uint32_t i = 0; i < index; i++)
456-
it.MoveNext();
457-
return TTypeRedirects::CompositionContextHelper::CreateVisual(it.Current());
469+
if (index < m_childrenCache.size()) {
470+
return m_childrenCache[index];
471+
}
472+
assert(false && "GetAt called with out-of-range index");
473+
return nullptr;
458474
}
459475

460476
void SetClippingPath(ID2D1Geometry *clippingPath) noexcept {
@@ -534,6 +550,7 @@ struct CompVisualImpl {
534550

535551
protected:
536552
TVisual m_visual;
553+
std::vector<winrt::Microsoft::ReactNative::Composition::Experimental::IVisual> m_childrenCache;
537554
};
538555

539556
template <typename TTypeRedirects>
@@ -848,30 +865,43 @@ struct CompScrollerVisual : winrt::implements<
848865
void InsertAt(
849866
const winrt::Microsoft::ReactNative::Composition::Experimental::IVisual &visual,
850867
uint32_t index) noexcept {
868+
if (index > m_childrenCache.size()) {
869+
std::terminate();
870+
}
851871
auto containerChildren = m_contentVisual.Children();
852872
auto compVisual = TTypeRedirects::CompositionContextHelper::InnerVisual(visual);
853873
if (index == 0) {
854874
containerChildren.InsertAtBottom(compVisual);
855-
return;
875+
} else {
876+
auto insertAfter = containerChildren.First();
877+
for (uint32_t i = 1; i < index; i++)
878+
insertAfter.MoveNext();
879+
containerChildren.InsertAbove(compVisual, insertAfter.Current());
880+
}
881+
if (index >= m_childrenCache.size()) {
882+
m_childrenCache.push_back(visual);
883+
} else {
884+
m_childrenCache.insert(m_childrenCache.begin() + index, visual);
856885
}
857-
auto insertAfter = containerChildren.First();
858-
for (uint32_t i = 1; i < index; i++)
859-
insertAfter.MoveNext();
860-
containerChildren.InsertAbove(compVisual, insertAfter.Current());
861886
}
862887

863888
void Remove(const winrt::Microsoft::ReactNative::Composition::Experimental::IVisual &visual) noexcept {
864889
auto compVisual = TTypeRedirects::CompositionContextHelper::InnerVisual(visual);
865890
auto containerChildren = m_contentVisual.Children();
866891
containerChildren.Remove(compVisual);
892+
auto it = std::find_if(
893+
m_childrenCache.begin(), m_childrenCache.end(), [&visual](const auto &cached) { return cached == visual; });
894+
if (it != m_childrenCache.end()) {
895+
m_childrenCache.erase(it);
896+
}
867897
}
868898

869899
winrt::Microsoft::ReactNative::Composition::Experimental::IVisual GetAt(uint32_t index) noexcept {
870-
auto containerChildren = m_visual.as<typename TTypeRedirects::ContainerVisual>().Children();
871-
auto it = containerChildren.First();
872-
for (uint32_t i = 0; i < index; i++)
873-
it.MoveNext();
874-
return TTypeRedirects::CompositionContextHelper::CreateVisual(it.Current());
900+
if (index < m_childrenCache.size()) {
901+
return m_childrenCache[index];
902+
}
903+
assert(false && "GetAt called with out-of-range index");
904+
return nullptr;
875905
}
876906

877907
void Brush(const winrt::Microsoft::ReactNative::Composition::Experimental::IBrush &brush) noexcept {
@@ -1255,6 +1285,12 @@ struct CompScrollerVisual : winrt::implements<
12551285
std::sort(snapPositions.begin(), snapPositions.end());
12561286
snapPositions.erase(std::unique(snapPositions.begin(), snapPositions.end()), snapPositions.end());
12571287

1288+
// Skip reconfiguration if snap points haven't changed
1289+
if (snapPositions == m_previousSnapPositions) {
1290+
return;
1291+
}
1292+
m_previousSnapPositions = snapPositions;
1293+
12581294
std::vector<typename TTypeRedirects::InteractionTrackerInertiaRestingValue> restingValues;
12591295

12601296
for (size_t i = 0; i < snapPositions.size(); ++i) {
@@ -1384,6 +1420,7 @@ struct CompScrollerVisual : winrt::implements<
13841420
winrt::Microsoft::ReactNative::Composition::Experimental::SnapPointsAlignment m_snapToAlignment{
13851421
winrt::Microsoft::ReactNative::Composition::Experimental::SnapPointsAlignment::Near};
13861422
std::vector<float> m_snapToOffsets;
1423+
std::vector<float> m_previousSnapPositions;
13871424
bool m_inertia{false};
13881425
bool m_custom{false};
13891426
bool m_interacting{false};
@@ -1410,6 +1447,7 @@ struct CompScrollerVisual : winrt::implements<
14101447
typename TTypeRedirects::SpriteVisual m_contentVisual{nullptr};
14111448
typename TTypeRedirects::InteractionTracker m_interactionTracker{nullptr};
14121449
typename TTypeRedirects::VisualInteractionSource m_visualInteractionSource{nullptr};
1450+
std::vector<winrt::Microsoft::ReactNative::Composition::Experimental::IVisual> m_childrenCache;
14131451
};
14141452
using WindowsCompScrollerVisual = CompScrollerVisual<WindowsTypeRedirects>;
14151453
using MicrosoftCompScrollerVisual = CompScrollerVisual<MicrosoftTypeRedirects>;

0 commit comments

Comments
 (0)