Make RenderUiKitView reject absorbed touch events (#28666)

When a touch event that is in the bounds of a RenderUiKitView is absorbed by another render object,
the RenderUiKitView's handleEvent is not called for that object. On the platform side, the touch event hits the FlutterTouchInterceptingView which is waiting for a framework decision that never arrived on whether to reject or accept the gesture.

This change fixes the issue by having RenderUiKitView register a global PointerRoute, that is used to reject absorbed touch events.
diff --git a/packages/flutter/lib/src/rendering/platform_view.dart b/packages/flutter/lib/src/rendering/platform_view.dart
index 189cedf..d816939 100644
--- a/packages/flutter/lib/src/rendering/platform_view.dart
+++ b/packages/flutter/lib/src/rendering/platform_view.dart
@@ -323,6 +323,8 @@
 
   _UiKitViewGestureRecognizer _gestureRecognizer;
 
+  PointerEvent _lastPointerDownEvent;
+
   @override
   void performResize() {
     size = constraints.biggest;
@@ -349,13 +351,41 @@
 
   @override
   void handleEvent(PointerEvent event, HitTestEntry entry) {
-    if (event is PointerDownEvent) {
-      _gestureRecognizer.addPointer(event);
+    if (event is! PointerDownEvent) {
+      return;
     }
+    _gestureRecognizer.addPointer(event);
+    _lastPointerDownEvent = event;
+  }
+
+  // This is registered as a global PointerRoute while the render object is attached.
+  void _handleGlobalPointerEvent(PointerEvent event) {
+    if (event is! PointerDownEvent) {
+      return;
+    }
+    final Offset localOffset = globalToLocal(event.position);
+    if(!(Offset.zero & size).contains(localOffset)) {
+      return;
+    }
+    if (event != _lastPointerDownEvent) {
+      // The pointer event is in the bounds of this render box, but we didn't get it in handleEvent.
+      // This means that the pointer event was absorbed by a different render object.
+      // Since on the platform side the FlutterTouchIntercepting view is seeing all events that are
+      // within its bounds we need to tell it to reject the current touch sequence.
+      _viewController.rejectGesture();
+    }
+    _lastPointerDownEvent = null;
+  }
+
+  @override
+  void attach(PipelineOwner owner) {
+    super.attach(owner);
+    GestureBinding.instance.pointerRouter.addGlobalRoute(_handleGlobalPointerEvent);
   }
 
   @override
   void detach() {
+    GestureBinding.instance.pointerRouter.removeGlobalRoute(_handleGlobalPointerEvent);
     _gestureRecognizer.reset();
     super.detach();
   }
diff --git a/packages/flutter/test/widgets/platform_view_test.dart b/packages/flutter/test/widgets/platform_view_test.dart
index 3eb017a..4b4ecae 100644
--- a/packages/flutter/test/widgets/platform_view_test.dart
+++ b/packages/flutter/test/widgets/platform_view_test.dart
@@ -1364,6 +1364,36 @@
       expect(viewsController.gesturesRejected[currentViewId + 1], 0);
     });
 
+    testWidgets('UiKitView rejects gestures absorbed by siblings', (WidgetTester tester) async {
+      final int currentViewId = platformViewsRegistry.getNextPlatformViewId();
+      final FakeIosPlatformViewsController viewsController = FakeIosPlatformViewsController();
+      viewsController.registerViewType('webview');
+
+      await tester.pumpWidget(
+        Stack(
+          alignment: Alignment.topLeft,
+          children: <Widget>[
+            const UiKitView(viewType: 'webview', layoutDirection: TextDirection.ltr),
+            Container(
+              color: const Color.fromARGB(255, 255, 255, 255),
+              width: 100,
+              height: 100,
+            ),
+          ],
+        )
+      );
+
+      // First frame is before the platform view was created so the render object
+      // is not yet in the tree.
+      await tester.pump();
+
+      final TestGesture gesture = await tester.startGesture(const Offset(50.0, 50.0));
+      await gesture.up();
+
+      expect(viewsController.gesturesRejected[currentViewId + 1], 1);
+      expect(viewsController.gesturesAccepted[currentViewId + 1], 0);
+    });
+
     testWidgets('AndroidView rebuilt with same gestureRecognizers', (WidgetTester tester) async {
       final FakeIosPlatformViewsController viewsController = FakeIosPlatformViewsController();
       viewsController.registerViewType('webview');