Catch errors thrown while handling pointer events (#119577)

diff --git a/packages/flutter/lib/src/gestures/binding.dart b/packages/flutter/lib/src/gestures/binding.dart
index 383badd..023b0d5 100644
--- a/packages/flutter/lib/src/gestures/binding.dart
+++ b/packages/flutter/lib/src/gestures/binding.dart
@@ -287,9 +287,18 @@
   void _handlePointerDataPacket(ui.PointerDataPacket packet) {
     // We convert pointer data to logical pixels so that e.g. the touch slop can be
     // defined in a device-independent manner.
-    _pendingPointerEvents.addAll(PointerEventConverter.expand(packet.data, window.devicePixelRatio));
-    if (!locked) {
-      _flushPointerEventQueue();
+    try {
+      _pendingPointerEvents.addAll(PointerEventConverter.expand(packet.data, window.devicePixelRatio));
+      if (!locked) {
+        _flushPointerEventQueue();
+      }
+    } catch (error, stack) {
+      FlutterError.reportError(FlutterErrorDetails(
+        exception: error,
+        stack: stack,
+        library: 'gestures library',
+        context: ErrorDescription('while handling a pointer data packet'),
+      ));
     }
   }
 
diff --git a/packages/flutter/test/gestures/gesture_binding_test.dart b/packages/flutter/test/gestures/gesture_binding_test.dart
index 223d0d1..328dd4b 100644
--- a/packages/flutter/test/gestures/gesture_binding_test.dart
+++ b/packages/flutter/test/gestures/gesture_binding_test.dart
@@ -35,14 +35,20 @@
     return _instance!;
   }
 
-  HandleEventCallback? callback;
+  HandleEventCallback? onHandlePointerEvent;
+
+  @override
+  void handlePointerEvent(PointerEvent event) {
+    onHandlePointerEvent?.call(event);
+    super.handlePointerEvent(event);
+  }
+
+  HandleEventCallback? onHandleEvent;
 
   @override
   void handleEvent(PointerEvent event, HitTestEntry entry) {
     super.handleEvent(event, entry);
-    if (callback != null) {
-      callback?.call(event);
-    }
+    onHandleEvent?.call(event);
   }
 }
 
@@ -58,7 +64,7 @@
     );
 
     final List<PointerEvent> events = <PointerEvent>[];
-    binding.callback = events.add;
+    binding.onHandleEvent = events.add;
 
     GestureBinding.instance.platformDispatcher.onPointerDataPacket?.call(packet);
     expect(events.length, 2);
@@ -76,7 +82,7 @@
     );
 
     final List<PointerEvent> events = <PointerEvent>[];
-    binding.callback = events.add;
+    binding.onHandleEvent = events.add;
 
     GestureBinding.instance.platformDispatcher.onPointerDataPacket?.call(packet);
     expect(events.length, 3);
@@ -101,7 +107,7 @@
     GestureBinding.instance.pointerRouter.addGlobalRoute(pointerRouterEvents.add);
 
     final List<PointerEvent> events = <PointerEvent>[];
-    binding.callback = events.add;
+    binding.onHandleEvent = events.add;
 
     GestureBinding.instance.platformDispatcher.onPointerDataPacket?.call(packet);
     expect(events.length, 3);
@@ -126,7 +132,7 @@
     );
 
     final List<PointerEvent> events = <PointerEvent>[];
-    binding.callback = events.add;
+    binding.onHandleEvent = events.add;
 
     GestureBinding.instance.platformDispatcher.onPointerDataPacket?.call(packet);
     expect(events.length, 2);
@@ -143,7 +149,7 @@
     );
 
     final List<PointerEvent> events = <PointerEvent>[];
-    binding.callback = (PointerEvent event) {
+    binding.onHandleEvent = (PointerEvent event) {
       events.add(event);
       if (event is PointerDownEvent) {
         binding.cancelPointer(event.pointer);
@@ -389,7 +395,7 @@
     );
 
     final List<PointerEvent> events = <PointerEvent>[];
-    binding.callback = events.add;
+    binding.onHandleEvent = events.add;
 
     binding.platformDispatcher.onPointerDataPacket?.call(packet);
     expect(events.length, 3);
@@ -397,4 +403,28 @@
     expect(events[1], isA<PointerPanZoomUpdateEvent>());
     expect(events[2], isA<PointerPanZoomEndEvent>());
   });
+
+  test('Error handling', () {
+    const ui.PointerDataPacket packet = ui.PointerDataPacket(
+      data: <ui.PointerData>[
+        ui.PointerData(change: ui.PointerChange.down),
+        ui.PointerData(change: ui.PointerChange.up),
+      ],
+    );
+
+    final List<String> events = <String>[];
+    binding.onHandlePointerEvent = (PointerEvent event) { throw Exception('zipzapzooey $event'); };
+    FlutterError.onError = (FlutterErrorDetails details) { events.add(details.toString()); };
+    try {
+      GestureBinding.instance.platformDispatcher.onPointerDataPacket?.call(packet);
+      expect(events.length, 1);
+      expect(events[0], contains('while handling a pointer data\npacket')); // The default stringifying behavior uses 65 character wrapWidth.
+      expect(events[0], contains('zipzapzooey'));
+      expect(events[0], contains('PointerDownEvent'));
+      expect(events[0], isNot(contains('PointerUpEvent'))); // Failure happens on the first message, remaining messages aren't processed.
+    } finally {
+      binding.onHandlePointerEvent = null;
+      FlutterError.onError = FlutterError.presentError;
+    }
+  });
 }