Make pointer events ignore invalid views (#51925)

With this PR, Flutter will now ignore pointer events for an invalid view, i.e. views that have not been add or have been removed.

[C++, Objective-C, Java style guides]: https://github.com/flutter/engine/blob/main/CONTRIBUTING.md#style
diff --git a/lib/ui/window/pointer_data_packet_converter.cc b/lib/ui/window/pointer_data_packet_converter.cc
index 5cf2005..45b05a0 100644
--- a/lib/ui/window/pointer_data_packet_converter.cc
+++ b/lib/ui/window/pointer_data_packet_converter.cc
@@ -11,7 +11,8 @@
 
 namespace flutter {
 
-PointerDataPacketConverter::PointerDataPacketConverter() {}
+PointerDataPacketConverter::PointerDataPacketConverter(const Delegate& delegate)
+    : delegate_(delegate) {}
 
 PointerDataPacketConverter::~PointerDataPacketConverter() = default;
 
@@ -39,6 +40,10 @@
 void PointerDataPacketConverter::ConvertPointerData(
     PointerData pointer_data,
     std::vector<PointerData>& converted_pointers) {
+  // Ignores pointer events with an invalid view ID.
+  if (!delegate_.ViewExists(pointer_data.view_id)) {
+    return;
+  }
   if (pointer_data.signal_kind == PointerData::SignalKind::kNone) {
     switch (pointer_data.change) {
       case PointerData::Change::kCancel: {
diff --git a/lib/ui/window/pointer_data_packet_converter.h b/lib/ui/window/pointer_data_packet_converter.h
index e903e4e..146ade2 100644
--- a/lib/ui/window/pointer_data_packet_converter.h
+++ b/lib/ui/window/pointer_data_packet_converter.h
@@ -75,7 +75,26 @@
 ///
 class PointerDataPacketConverter {
  public:
-  PointerDataPacketConverter();
+  // Used by PointerDataPacketConverter to query the system status.
+  //
+  // Typically RuntimeController.
+  class Delegate {
+   public:
+    Delegate() = default;
+
+    virtual ~Delegate() = default;
+
+    // Returns true if the specified view exists.
+    virtual bool ViewExists(int64_t view_id) const = 0;
+  };
+
+  //----------------------------------------------------------------------------
+  /// @brief      Create a PointerDataPacketConverter.
+  ///
+  /// @param[in]  delegate   A delegate to fulfill the query to the app state.
+  ///                        The delegate must exist throughout the lifetime
+  ///                        of this class. Typically `RuntimeController`.
+  explicit PointerDataPacketConverter(const Delegate& delegate);
   ~PointerDataPacketConverter();
 
   //----------------------------------------------------------------------------
@@ -85,6 +104,8 @@
   ///             pointer transitions. This method will fill out that
   ///             information and attempt to correct pointer transitions.
   ///
+  ///             Pointer data with invalid view IDs will be ignored.
+  ///
   /// @param[in]  packet                   The raw pointer packet sent from
   ///                                      embedding.
   ///
@@ -95,6 +116,9 @@
   std::unique_ptr<PointerDataPacket> Convert(const PointerDataPacket& packet);
 
  private:
+  const Delegate& delegate_;
+
+  // A map from pointer device ID to the state of the pointer.
   std::map<int64_t, PointerState> states_;
 
   int64_t pointer_ = 0;
diff --git a/lib/ui/window/pointer_data_packet_converter_unittests.cc b/lib/ui/window/pointer_data_packet_converter_unittests.cc
index 69f798d..4f6a37f 100644
--- a/lib/ui/window/pointer_data_packet_converter_unittests.cc
+++ b/lib/ui/window/pointer_data_packet_converter_unittests.cc
@@ -5,12 +5,34 @@
 #include "flutter/lib/ui/window/pointer_data_packet_converter.h"
 
 #include <cstring>
+#include <unordered_set>
 
 #include "gtest/gtest.h"
 
 namespace flutter {
 namespace testing {
 
+namespace {
+
+constexpr int64_t kImplicitViewId = 0;
+
+}
+
+class TestDelegate : public PointerDataPacketConverter::Delegate {
+ public:
+  // |PointerDataPacketConverter::Delegate|
+  bool ViewExists(int64_t view_id) const override {
+    return views_.count(view_id) != 0;
+  }
+
+  void AddView(int64_t view_id) { views_.insert(view_id); }
+
+  void RemoveView(int64_t view_id) { views_.erase(view_id); }
+
+ private:
+  std::unordered_set<int64_t> views_;
+};
+
 void CreateSimulatedPointerData(PointerData& data,  // NOLINT
                                 PointerData::Change change,
                                 int64_t device,
@@ -45,7 +67,7 @@
   data.platformData = 0;
   data.scroll_delta_x = 0.0;
   data.scroll_delta_y = 0.0;
-  data.view_id = 0;
+  data.view_id = kImplicitViewId;
 }
 
 void CreateSimulatedMousePointerData(PointerData& data,  // NOLINT
@@ -85,7 +107,7 @@
   data.platformData = 0;
   data.scroll_delta_x = scroll_delta_x;
   data.scroll_delta_y = scroll_delta_y;
-  data.view_id = 0;
+  data.view_id = kImplicitViewId;
 }
 
 void CreateSimulatedTrackpadGestureData(PointerData& data,  // NOLINT
@@ -144,7 +166,9 @@
 }
 
 TEST(PointerDataPacketConverterTest, CanConvertPointerDataPacket) {
-  PointerDataPacketConverter converter;
+  TestDelegate delegate;
+  delegate.AddView(kImplicitViewId);
+  PointerDataPacketConverter converter(delegate);
   auto packet = std::make_unique<PointerDataPacket>(6);
   PointerData data;
   CreateSimulatedPointerData(data, PointerData::Change::kAdd, 0, 0.0, 0.0, 0);
@@ -193,7 +217,9 @@
 }
 
 TEST(PointerDataPacketConverterTest, CanSynthesizeDownAndUp) {
-  PointerDataPacketConverter converter;
+  TestDelegate delegate;
+  delegate.AddView(kImplicitViewId);
+  PointerDataPacketConverter converter(delegate);
   auto packet = std::make_unique<PointerDataPacket>(4);
   PointerData data;
   CreateSimulatedPointerData(data, PointerData::Change::kAdd, 0, 0.0, 0.0, 0);
@@ -244,7 +270,9 @@
 }
 
 TEST(PointerDataPacketConverterTest, CanUpdatePointerIdentifier) {
-  PointerDataPacketConverter converter;
+  TestDelegate delegate;
+  delegate.AddView(kImplicitViewId);
+  PointerDataPacketConverter converter(delegate);
   auto packet = std::make_unique<PointerDataPacket>(7);
   PointerData data;
   CreateSimulatedPointerData(data, PointerData::Change::kAdd, 0, 0.0, 0.0, 0);
@@ -299,7 +327,9 @@
 }
 
 TEST(PointerDataPacketConverterTest, AlwaysForwardMoveEvent) {
-  PointerDataPacketConverter converter;
+  TestDelegate delegate;
+  delegate.AddView(kImplicitViewId);
+  PointerDataPacketConverter converter(delegate);
   auto packet = std::make_unique<PointerDataPacket>(4);
   PointerData data;
   CreateSimulatedPointerData(data, PointerData::Change::kAdd, 0, 0.0, 0.0, 0);
@@ -336,7 +366,9 @@
 }
 
 TEST(PointerDataPacketConverterTest, CanWorkWithDifferentDevices) {
-  PointerDataPacketConverter converter;
+  TestDelegate delegate;
+  delegate.AddView(kImplicitViewId);
+  PointerDataPacketConverter converter(delegate);
   auto packet = std::make_unique<PointerDataPacket>(12);
   PointerData data;
   CreateSimulatedPointerData(data, PointerData::Change::kAdd, 0, 0.0, 0.0, 0);
@@ -433,7 +465,9 @@
 }
 
 TEST(PointerDataPacketConverterTest, CanSynthesizeAdd) {
-  PointerDataPacketConverter converter;
+  TestDelegate delegate;
+  delegate.AddView(kImplicitViewId);
+  PointerDataPacketConverter converter(delegate);
   auto packet = std::make_unique<PointerDataPacket>(2);
   PointerData data;
   CreateSimulatedPointerData(data, PointerData::Change::kDown, 0, 330.0, 450.0,
@@ -478,7 +512,9 @@
 
 TEST(PointerDataPacketConverterTest, CanHandleThreeFingerGesture) {
   // Regression test https://github.com/flutter/flutter/issues/20517.
-  PointerDataPacketConverter converter;
+  TestDelegate delegate;
+  delegate.AddView(kImplicitViewId);
+  PointerDataPacketConverter converter(delegate);
   PointerData data;
   std::vector<PointerData> result;
   // First finger down.
@@ -558,7 +594,9 @@
       PointerData::SignalKind::kScale,
   };
   for (const PointerData::SignalKind& kind : signal_kinds) {
-    PointerDataPacketConverter converter;
+    TestDelegate delegate;
+    delegate.AddView(kImplicitViewId);
+    PointerDataPacketConverter converter(delegate);
     auto packet = std::make_unique<PointerDataPacket>(6);
     PointerData data;
     CreateSimulatedMousePointerData(data, PointerData::Change::kAdd,
@@ -666,7 +704,9 @@
 }
 
 TEST(PointerDataPacketConverterTest, CanConvertTrackpadGesture) {
-  PointerDataPacketConverter converter;
+  TestDelegate delegate;
+  delegate.AddView(kImplicitViewId);
+  PointerDataPacketConverter converter(delegate);
   auto packet = std::make_unique<PointerDataPacket>(3);
   PointerData data;
   CreateSimulatedTrackpadGestureData(data, PointerData::Change::kPanZoomStart,
@@ -717,7 +757,10 @@
 }
 
 TEST(PointerDataPacketConverterTest, CanConvertViewId) {
-  PointerDataPacketConverter converter;
+  TestDelegate delegate;
+  delegate.AddView(100);
+  delegate.AddView(200);
+  PointerDataPacketConverter converter(delegate);
   auto packet = std::make_unique<PointerDataPacket>(2);
   PointerData data;
   CreateSimulatedPointerData(data, PointerData::Change::kAdd, 0, 0.0, 0.0, 0);
diff --git a/runtime/runtime_controller.cc b/runtime/runtime_controller.cc
index ad4fe09..6a607ed 100644
--- a/runtime/runtime_controller.cc
+++ b/runtime/runtime_controller.cc
@@ -23,7 +23,10 @@
 
 RuntimeController::RuntimeController(RuntimeDelegate& p_client,
                                      const TaskRunners& task_runners)
-    : client_(p_client), vm_(nullptr), context_(task_runners) {}
+    : client_(p_client),
+      vm_(nullptr),
+      context_(task_runners),
+      pointer_data_packet_converter_(*this) {}
 
 RuntimeController::RuntimeController(
     RuntimeDelegate& p_client,
@@ -43,7 +46,8 @@
       isolate_create_callback_(p_isolate_create_callback),
       isolate_shutdown_callback_(p_isolate_shutdown_callback),
       persistent_isolate_data_(std::move(p_persistent_isolate_data)),
-      context_(p_context) {}
+      context_(p_context),
+      pointer_data_packet_converter_(*this) {}
 
 std::unique_ptr<RuntimeController> RuntimeController::Spawn(
     RuntimeDelegate& p_client,
@@ -207,6 +211,10 @@
   return platform_configuration->RemoveView(view_id);
 }
 
+bool RuntimeController::ViewExists(int64_t view_id) const {
+  return platform_data_.viewport_metrics_for_views.count(view_id) != 0;
+}
+
 bool RuntimeController::SetViewportMetrics(int64_t view_id,
                                            const ViewportMetrics& metrics) {
   TRACE_EVENT0("flutter", "SetViewportMetrics");
@@ -360,7 +368,9 @@
     TRACE_EVENT0("flutter", "RuntimeController::DispatchPointerDataPacket");
     std::unique_ptr<PointerDataPacket> converted_packet =
         pointer_data_packet_converter_.Convert(packet);
-    platform_configuration->DispatchPointerDataPacket(*converted_packet);
+    if (converted_packet->GetLength() != 0) {
+      platform_configuration->DispatchPointerDataPacket(*converted_packet);
+    }
     return true;
   }
 
diff --git a/runtime/runtime_controller.h b/runtime/runtime_controller.h
index 89f3eaf..93da070 100644
--- a/runtime/runtime_controller.h
+++ b/runtime/runtime_controller.h
@@ -49,7 +49,8 @@
 /// `RuntimeController` and flushed to the Dart VM when the isolate becomes
 /// ready before the entrypoint function. See `PlatformData`.
 ///
-class RuntimeController : public PlatformConfigurationClient {
+class RuntimeController : public PlatformConfigurationClient,
+                          PointerDataPacketConverter::Delegate {
  public:
   /// A callback that's invoked after this `RuntimeController` attempts to
   /// add a view to the Dart isolate.
@@ -723,6 +724,9 @@
 
   bool FlushRuntimeStateToIsolate();
 
+  // |PointerDataPacketConverter::Delegate|
+  bool ViewExists(int64_t view_id) const override;
+
   // |PlatformConfigurationClient|
   std::string DefaultRouteName() override;
 
diff --git a/shell/common/input_events_unittests.cc b/shell/common/input_events_unittests.cc
index d8ee75f..a0a22d1 100644
--- a/shell/common/input_events_unittests.cc
+++ b/shell/common/input_events_unittests.cc
@@ -19,6 +19,12 @@
 // returns the time of that frame.
 using Generator = std::function<UnitlessTime(int)>;
 
+namespace {
+
+constexpr int64_t kImplicitViewId = 0;
+
+}
+
 //----------------------------------------------------------------------------
 /// Simulate n input events where the i-th one is delivered at delivery_time(i).
 ///
@@ -124,7 +130,9 @@
     for (int i = 0, j = 0; i < num_events; j += 1) {
       double t = j * frame_time;
       while (i < num_events && delivery_time(i) <= t) {
-        ShellTest::DispatchFakePointerData(shell.get());
+        // Use a different x every time for the pointer data converter to
+        // generate non-empty events.
+        ShellTest::DispatchFakePointerData(shell.get(), /*x=*/i);
         i += 1;
       }
       ShellTest::VSyncFlush(shell.get(), &will_draw_new_frame);
@@ -182,6 +190,7 @@
   data.platformData = 0;
   data.scroll_delta_x = 0.0;
   data.scroll_delta_y = 0.0;
+  data.view_id = kImplicitViewId;
 }
 
 TEST_F(ShellTest, MissAtMostOneFrameForIrregularInputEvents) {
diff --git a/shell/common/shell_test.cc b/shell/common/shell_test.cc
index 90dcf35..6683399 100644
--- a/shell/common/shell_test.cc
+++ b/shell/common/shell_test.cc
@@ -258,8 +258,12 @@
   latch.Wait();
 }
 
-void ShellTest::DispatchFakePointerData(Shell* shell) {
+void ShellTest::DispatchFakePointerData(Shell* shell, double x) {
   auto packet = std::make_unique<PointerDataPacket>(1);
+  packet->SetPointerData(0, PointerData{
+                                .change = PointerData::Change::kHover,
+                                .physical_x = x,
+                            });
   DispatchPointerData(shell, std::move(packet));
 }
 
diff --git a/shell/common/shell_test.h b/shell/common/shell_test.h
index c11ad11..b5f832a 100644
--- a/shell/common/shell_test.h
+++ b/shell/common/shell_test.h
@@ -110,7 +110,9 @@
 
   static void PumpOneFrame(Shell* shell);
   static void PumpOneFrame(Shell* shell, FrameContent frame_content);
-  static void DispatchFakePointerData(Shell* shell);
+  // Dispatch a PointerHoverEvent with the specified `x` as the pointer
+  // position.
+  static void DispatchFakePointerData(Shell* shell, double x);
   static void DispatchPointerData(Shell* shell,
                                   std::unique_ptr<PointerDataPacket> packet);
   // Declare |UnreportedTimingsCount|, |GetNeedsReportTimings| and
diff --git a/shell/platform/embedder/tests/embedder_unittests.cc b/shell/platform/embedder/tests/embedder_unittests.cc
index 1e1075f..2b03e51 100644
--- a/shell/platform/embedder/tests/embedder_unittests.cc
+++ b/shell/platform/embedder/tests/embedder_unittests.cc
@@ -3534,7 +3534,7 @@
   builder.SetSoftwareRendererConfig();
   builder.SetDartEntrypoint("pointer_data_packet_view_id");
 
-  fml::AutoResetWaitableEvent ready_latch, count_latch, message_latch;
+  fml::AutoResetWaitableEvent ready_latch, add_view_latch, message_latch;
   context.AddNativeCallback(
       "SignalNativeTest",
       CREATE_NATIVE_ENTRY(
@@ -3550,9 +3550,31 @@
 
   auto engine = builder.LaunchEngine();
   ASSERT_TRUE(engine.is_valid());
-
   ready_latch.Wait();
 
+  // Add view 2
+  FlutterWindowMetricsEvent metrics = {};
+  metrics.struct_size = sizeof(FlutterWindowMetricsEvent);
+  metrics.width = 800;
+  metrics.height = 600;
+  metrics.pixel_ratio = 1.0;
+  metrics.view_id = 2;
+
+  FlutterAddViewInfo info = {};
+  info.struct_size = sizeof(FlutterAddViewInfo);
+  info.view_id = 2;
+  info.view_metrics = &metrics;
+  info.add_view_callback = [](const FlutterAddViewResult* result) {
+    EXPECT_TRUE(result->added);
+    fml::AutoResetWaitableEvent* add_view_latch =
+        reinterpret_cast<fml::AutoResetWaitableEvent*>(result->user_data);
+    add_view_latch->Signal();
+  };
+  info.user_data = &add_view_latch;
+  ASSERT_EQ(FlutterEngineAddView(engine.get(), &info), kSuccess);
+  add_view_latch.Wait();
+
+  // Send a pointer event for view 2
   FlutterPointerEvent pointer_event = {};
   pointer_event.struct_size = sizeof(FlutterPointerEvent);
   pointer_event.phase = FlutterPointerPhase::kAdd;