From abe3412d63fae77e0bf72ca68ba88322b498e23d Mon Sep 17 00:00:00 2001 From: Collin Schneide <27441618+FaithfulAudio@users.noreply.github.com> Date: Sun, 26 Jul 2026 00:05:38 -0700 Subject: [PATCH] fix(pointer): null-check the capturing component view before notifying OnPointerCaptureLost CapturePointer and releasePointerCapture look the capturing component up by its cached m_pointerCapturingComponentTag and dereference the result unguarded. That tag can outlive the component it names: when list/ScrollView virtualization recycles the capturing row mid-pan, componentViewDescriptorWithTag returns a descriptor whose .view is null, so winrt::get_self(...)->OnPointerCaptureLost() dereferences null and terminates the process with 0xc0000005. Null-check targetComponentView at both sites. Skipping the notify loses no state transition: CapturePointer overwrites the stale tag immediately below, and releasePointerCapture clears it via the existing m_capturedPointers.empty() branch. main twin of #16334 (0.83-stable). --- ...ative-windows-capture-null-guard-main.json | 1 + .../Composition/CompositionEventHandler.cpp | 21 +++++++++++++++---- 2 files changed, 18 insertions(+), 4 deletions(-) create mode 100644 change/react-native-windows-capture-null-guard-main.json diff --git a/change/react-native-windows-capture-null-guard-main.json b/change/react-native-windows-capture-null-guard-main.json new file mode 100644 index 00000000000..dd867f51aa1 --- /dev/null +++ b/change/react-native-windows-capture-null-guard-main.json @@ -0,0 +1 @@ +{"type":"prerelease","dependentChangeType":"patch","email":"collindanielschneide@gmail.com","packageName":"react-native-windows","comment":"Null-check the capturing component view before notifying OnPointerCaptureLost (crash when the capturing component was unmounted)"} diff --git a/vnext/Microsoft.ReactNative/Fabric/Composition/CompositionEventHandler.cpp b/vnext/Microsoft.ReactNative/Fabric/Composition/CompositionEventHandler.cpp index dda0804692d..d4e1f4c5b0c 100644 --- a/vnext/Microsoft.ReactNative/Fabric/Composition/CompositionEventHandler.cpp +++ b/vnext/Microsoft.ReactNative/Fabric/Composition/CompositionEventHandler.cpp @@ -1510,8 +1510,16 @@ bool CompositionEventHandler::CapturePointer( auto targetComponentView = fabricuiManager->GetViewRegistry().componentViewDescriptorWithTag(m_pointerCapturingComponentTag).view; - winrt::get_self(targetComponentView) - ->OnPointerCaptureLost(); + // Guard against a stale capturing tag. If the previously-capturing component + // was unmounted (e.g. list/ScrollView virtualization recycled it during a pan) + // without releasing capture, componentViewDescriptorWithTag returns a + // descriptor whose .view is null - and the unguarded get_self(...) call then + // dereferences null and crashes the process (0xc0000005). Skip the notify when + // the view is gone; the tag is overwritten just below. + if (targetComponentView) { + winrt::get_self(targetComponentView) + ->OnPointerCaptureLost(); + } } } @@ -1540,8 +1548,13 @@ bool CompositionEventHandler::releasePointerCapture(PointerId pointerId, faceboo auto targetComponentView = fabricuiManager->GetViewRegistry().componentViewDescriptorWithTag(m_pointerCapturingComponentTag).view; - winrt::get_self(targetComponentView) - ->OnPointerCaptureLost(); + // Same stale-tag null-view guard as CapturePointer above: a pointer release + // after the capturing component was unmounted would otherwise dereference a + // null view and crash (0xc0000005). + if (targetComponentView) { + winrt::get_self(targetComponentView) + ->OnPointerCaptureLost(); + } } if (m_capturedPointers.empty()) {