[fuchsia][scenic] Use infinite hit region (#38647)

fxbug.dev/118729
diff --git a/shell/platform/fuchsia/flutter/flatland_external_view_embedder.cc b/shell/platform/fuchsia/flutter/flatland_external_view_embedder.cc
index 4d8d948..13fb038 100644
--- a/shell/platform/fuchsia/flutter/flatland_external_view_embedder.cc
+++ b/shell/platform/fuchsia/flutter/flatland_external_view_embedder.cc
@@ -13,11 +13,6 @@
 namespace flutter_runner {
 namespace {
 
-// Since the flatland hit-region can be transformed (rotated, scaled or
-// translated), we must ensure that the size of the hit-region will not cause
-// overflows on operations (like FLT_MAX would).
-constexpr float kMaxHitRegionSize = 1'000'000.f;
-
 void AttachClipTransformChild(
     FlatlandConnection* flatland,
     FlatlandExternalViewEmbedder::ClipTransform* parent_clip_transform,
@@ -59,19 +54,13 @@
   if (intercept_all_input) {
     input_interceptor_transform_ = flatland_->NextTransformId();
     flatland_->flatland()->CreateTransform(*input_interceptor_transform_);
+    flatland_->flatland()->SetInfiniteHitRegion(
+        *input_interceptor_transform_,
+        fuchsia::ui::composition::HitTestInteraction::SEMANTICALLY_INVISIBLE);
 
     flatland_->flatland()->AddChild(root_transform_id_,
                                     *input_interceptor_transform_);
     child_transforms_.emplace_back(*input_interceptor_transform_);
-
-    // Attach full-screen hit testing shield. Note that since the hit-region
-    // may be transformed (translated, rotated), we do not want to set
-    // width/height to FLT_MAX. This will cause a numeric overflow.
-    flatland_->flatland()->SetHitRegions(
-        *input_interceptor_transform_,
-        {{{0, 0, kMaxHitRegionSize, kMaxHitRegionSize},
-          fuchsia::ui::composition::HitTestInteraction::
-              SEMANTICALLY_INVISIBLE}});
   }
 }
 
@@ -453,9 +442,9 @@
       flatland_layer_index++;
     }
 
-    // Set up the input interceptor at the top of the
-    // scene, if applicable.  It will capture all input, and any unwanted input
-    // will be reinjected into embedded views.
+    // Set up the input interceptor at the top of the scene, if applicable. It
+    // will capture all input, and any unwanted input will be reinjected into
+    // embedded views.
     if (input_interceptor_transform_.has_value()) {
       flatland_->flatland()->AddChild(root_transform_id_,
                                       *input_interceptor_transform_);
diff --git a/shell/platform/fuchsia/flutter/tests/fakes/scenic/fake_flatland.cc b/shell/platform/fuchsia/flutter/tests/fakes/scenic/fake_flatland.cc
index 4c78208..8322275 100644
--- a/shell/platform/fuchsia/flutter/tests/fakes/scenic/fake_flatland.cc
+++ b/shell/platform/fuchsia/flutter/tests/fakes/scenic/fake_flatland.cc
@@ -853,6 +853,29 @@
   transform->hit_regions = std::move(regions);
 }
 
+void FakeFlatland::SetInfiniteHitRegion(
+    fuchsia::ui::composition::TransformId transform_id,
+    fuchsia::ui::composition::HitTestInteraction hit_test) {
+  if (transform_id.value == 0) {
+    // TODO(fxb/85619): Raise a FlatlandError here
+    FML_CHECK(false)
+        << "FakeFlatland::SetTranslation: TransformId 0 is invalid.";
+    return;
+  }
+
+  auto found_transform = pending_graph_.transform_map.find(transform_id.value);
+  if (found_transform == pending_graph_.transform_map.end()) {
+    // TODO(fxb/85619): Raise a FlatlandError here
+    FML_CHECK(false) << "FakeFlatland::SetTranslation: TransformId "
+                     << transform_id.value << " does not exist.";
+    return;
+  }
+
+  auto& transform = found_transform->second;
+  ZX_ASSERT(transform);
+  transform->hit_regions = {kInfiniteHitRegion};
+}
+
 void FakeFlatland::Clear() {
   parents_map_.clear();
   pending_graph_.Clear();
diff --git a/shell/platform/fuchsia/flutter/tests/fakes/scenic/fake_flatland.h b/shell/platform/fuchsia/flutter/tests/fakes/scenic/fake_flatland.h
index 94ca657..ac70c46 100644
--- a/shell/platform/fuchsia/flutter/tests/fakes/scenic/fake_flatland.h
+++ b/shell/platform/fuchsia/flutter/tests/fakes/scenic/fake_flatland.h
@@ -311,6 +311,11 @@
       std::vector<fuchsia::ui::composition::HitRegion> regions) override;
 
   // |fuchsia::ui::composition::Flatland|
+  void SetInfiniteHitRegion(
+      fuchsia::ui::composition::TransformId transform_id,
+      fuchsia::ui::composition::HitTestInteraction hit_test) override;
+
+  // |fuchsia::ui::composition::Flatland|
   void Clear() override;
 
   // |fuchsia::ui::composition::Flatland|
diff --git a/shell/platform/fuchsia/flutter/tests/fakes/scenic/fake_flatland_types.h b/shell/platform/fuchsia/flutter/tests/fakes/scenic/fake_flatland_types.h
index 40f1648..531c709 100644
--- a/shell/platform/fuchsia/flutter/tests/fakes/scenic/fake_flatland_types.h
+++ b/shell/platform/fuchsia/flutter/tests/fakes/scenic/fake_flatland_types.h
@@ -16,6 +16,7 @@
 #include <zircon/types.h>
 
 #include <algorithm>
+#include <cfloat>
 #include <cstdint>
 #include <optional>
 #include <unordered_map>
@@ -138,6 +139,8 @@
 
 constexpr static fuchsia::ui::composition::TransformId kInvalidTransformId{0};
 constexpr static fuchsia::ui::composition::ContentId kInvalidContentId{0};
+constexpr static fuchsia::ui::composition::HitRegion kInfiniteHitRegion = {
+    .region = {-FLT_MAX, -FLT_MAX, FLT_MAX, FLT_MAX}};
 
 // Convenience structure which allows clients to easily create a valid
 // `ViewCreationToken` / `ViewportCreationToken` pair for use with Flatland
diff --git a/shell/platform/fuchsia/flutter/tests/flatland_external_view_embedder_unittests.cc b/shell/platform/fuchsia/flutter/tests/flatland_external_view_embedder_unittests.cc
index 57e740e..46d2fd3 100644
--- a/shell/platform/fuchsia/flutter/tests/flatland_external_view_embedder_unittests.cc
+++ b/shell/platform/fuchsia/flutter/tests/flatland_external_view_embedder_unittests.cc
@@ -38,8 +38,10 @@
 using fuchsia::scenic::scheduling::PresentReceivedInfo;
 using ::testing::_;
 using ::testing::AllOf;
+using ::testing::Contains;
 using ::testing::ElementsAre;
 using ::testing::Eq;
+using ::testing::Field;
 using ::testing::FieldsAre;
 using ::testing::IsEmpty;
 using ::testing::Matcher;
@@ -297,6 +299,16 @@
       /*content*/ _,
       /*hit_regions*/ _));
 }
+
+Matcher<std::shared_ptr<FakeTransform>> IsInputShield() {
+  return Pointee(AllOf(
+      // Must not clip the hit region.
+      Field("clip_bounds", &FakeTransform::clip_bounds, Eq(std::nullopt)),
+      // Hit region must be "infinite".
+      Field("hit_regions", &FakeTransform::hit_regions,
+            Contains(flutter_runner::testing::kInfiniteHitRegion))));
+}
+
 fuchsia::ui::composition::OnNextFrameBeginValues WithPresentCredits(
     uint32_t additional_present_credits) {
   fuchsia::ui::composition::OnNextFrameBeginValues values;
@@ -396,15 +408,18 @@
     fuchsia::ui::composition::FlatlandHandle flatland =
         fake_flatland_.ConnectFlatland(session_subloop_->dispatcher());
 
-    auto test_name =
+    const auto test_name =
         ::testing::UnitTest::GetInstance()->current_test_info()->name();
+    const auto max_frames_in_flight = 1;
+    const auto vsync_offset = fml::TimeDelta::Zero();
     return std::make_shared<FlatlandConnection>(
-        std::move(test_name), std::move(flatland), []() { FAIL(); },
-        [](auto...) {}, 1, fml::TimeDelta::Zero());
+        std::move(test_name), std::move(flatland),
+        /*error_callback=*/[] { FAIL(); }, /*ofpe_callback=*/[](auto...) {},
+        max_frames_in_flight, vsync_offset);
   }
 
   // Primary loop and subloop for the FakeFlatland instance to process its
-  // messages.  The subloop allocates it's own zx_port_t, allowing us to use a
+  // messages.  The subloop allocates its own zx_port_t, allowing us to use a
   // separate port for each end of the message channel, rather than sharing a
   // single one.  Dual ports allow messages and responses to be intermingled,
   // which is how production code behaves; this improves test realism.
@@ -1476,4 +1491,143 @@
                   fuchsia::ui::composition::HitTestInteraction::DEFAULT)})}));
 }
 
+TEST_F(FlatlandExternalViewEmbedderTest, ViewportCoveredWithInputInterceptor) {
+  fuchsia::ui::composition::ParentViewportWatcherPtr parent_viewport_watcher;
+  fuchsia::ui::views::ViewportCreationToken viewport_creation_token;
+  fuchsia::ui::views::ViewCreationToken view_creation_token;
+  fuchsia::ui::views::ViewRef view_ref;
+  auto view_creation_token_status = zx::channel::create(
+      0u, &viewport_creation_token.value, &view_creation_token.value);
+  ASSERT_EQ(view_creation_token_status, ZX_OK);
+  auto view_ref_pair = scenic::ViewRefPair::New();
+  view_ref_pair.view_ref.Clone(&view_ref);
+
+  // Create the `FlatlandExternalViewEmbedder` and pump the message loop until
+  // the initial scene graph is setup.
+  FlatlandExternalViewEmbedder external_view_embedder(
+      std::move(view_creation_token),
+      fuchsia::ui::views::ViewIdentityOnCreation{
+          .view_ref = std::move(view_ref_pair.view_ref),
+          .view_ref_control = std::move(view_ref_pair.control_ref),
+      },
+      fuchsia::ui::composition::ViewBoundProtocols{},
+      parent_viewport_watcher.NewRequest(), flatland_connection(),
+      fake_surface_producer(),
+      /*intercept_all_input=*/true  // Enables the interceptor.
+  );
+  flatland_connection()->Present();
+  loop().RunUntilIdle();
+  fake_flatland().FireOnNextFrameBeginEvent(WithPresentCredits(1u));
+  loop().RunUntilIdle();
+  EXPECT_THAT(fake_flatland().graph(),
+              IsFlutterGraph(parent_viewport_watcher, viewport_creation_token,
+                             view_ref, {IsInputShield()}));
+
+  // Create the view before drawing the scene.
+  const SkSize child_view_size_signed = SkSize::Make(256.f, 512.f);
+  const fuchsia::math::SizeU child_view_size{
+      static_cast<uint32_t>(child_view_size_signed.width()),
+      static_cast<uint32_t>(child_view_size_signed.height())};
+  auto [child_view_token, child_viewport_token] = ViewTokenPair::New();
+  const uint32_t child_view_id = child_viewport_token.value.get();
+
+  const int kOpacity = 200;
+  const float kOpacityFloat = 200 / 255.0f;
+  const fuchsia::math::VecF kScale{3.0f, 4.0f};
+
+  auto matrix = SkMatrix::I();
+  matrix.setScaleX(kScale.x);
+  matrix.setScaleY(kScale.y);
+
+  auto mutators_stack = flutter::MutatorsStack();
+  mutators_stack.PushOpacity(kOpacity);
+  mutators_stack.PushTransform(matrix);
+
+  flutter::EmbeddedViewParams child_view_params(matrix, child_view_size_signed,
+                                                mutators_stack);
+  external_view_embedder.CreateView(
+      child_view_id, []() {},
+      [](fuchsia::ui::composition::ContentId,
+         fuchsia::ui::composition::ChildViewWatcherHandle) {});
+  const SkRect child_view_occlusion_hint = SkRect::MakeLTRB(1, 2, 3, 4);
+  const fuchsia::math::Inset child_view_inset{
+      static_cast<int32_t>(child_view_occlusion_hint.top()),
+      static_cast<int32_t>(child_view_occlusion_hint.right()),
+      static_cast<int32_t>(child_view_occlusion_hint.bottom()),
+      static_cast<int32_t>(child_view_occlusion_hint.left())};
+  external_view_embedder.SetViewProperties(
+      child_view_id, child_view_occlusion_hint, /*hit_testable=*/false,
+      /*focusable=*/false);
+
+  // We must take into account the effect of DPR on the view scale.
+  const float kDPR = 2.0f;
+  const float kInvDPR = 1.f / kDPR;
+
+  // Draw the scene. The scene graph shouldn't change yet.
+  const SkISize frame_size_signed = SkISize::Make(512, 512);
+  const fuchsia::math::SizeU frame_size{
+      static_cast<uint32_t>(frame_size_signed.width()),
+      static_cast<uint32_t>(frame_size_signed.height())};
+  DrawFrameWithView(
+      external_view_embedder, frame_size_signed, kDPR, child_view_id,
+      child_view_params,
+      [](SkCanvas* canvas) {
+        const SkSize canvas_size = SkSize::Make(canvas->imageInfo().width(),
+                                                canvas->imageInfo().height());
+        SkPaint rect_paint;
+        rect_paint.setColor(SK_ColorGREEN);
+        canvas->translate(canvas_size.width() / 4.f,
+                          canvas_size.height() / 2.f);
+        canvas->drawRect(SkRect::MakeWH(canvas_size.width() / 32.f,
+                                        canvas_size.height() / 32.f),
+                         rect_paint);
+      },
+      [](SkCanvas* canvas) {
+        const SkSize canvas_size = SkSize::Make(canvas->imageInfo().width(),
+                                                canvas->imageInfo().height());
+        SkPaint rect_paint;
+        rect_paint.setColor(SK_ColorRED);
+        canvas->translate(canvas_size.width() * 3.f / 4.f,
+                          canvas_size.height() / 2.f);
+        canvas->drawRect(SkRect::MakeWH(canvas_size.width() / 32.f,
+                                        canvas_size.height() / 32.f),
+                         rect_paint);
+      });
+  EXPECT_THAT(fake_flatland().graph(),
+              IsFlutterGraph(parent_viewport_watcher, viewport_creation_token,
+                             view_ref, {IsInputShield()}));
+
+  // Pump the message loop.  The scene updates should propagate to flatland.
+  loop().RunUntilIdle();
+  fake_flatland().FireOnNextFrameBeginEvent(WithPresentCredits(1u));
+  loop().RunUntilIdle();
+
+  EXPECT_THAT(
+      fake_flatland().graph(),
+      IsFlutterGraph(
+          parent_viewport_watcher, viewport_creation_token, view_ref, /*layers*/
+          {IsImageLayer(
+               frame_size, kFirstLayerBlendMode,
+               {IsHitRegion(
+                   /* x */ 128.f,
+                   /* y */ 256.f,
+                   /* width */ 16.f,
+                   /* height */ 16.f,
+                   /* hit_test */
+                   fuchsia::ui::composition::HitTestInteraction::DEFAULT)}),
+           IsViewportLayer(child_view_token, child_view_size, child_view_inset,
+                           {0, 0}, kScale, kOpacityFloat),
+           IsImageLayer(
+               frame_size, kUpperLayerBlendMode,
+               {IsHitRegion(
+                   /* x */ 384.f,
+                   /* y */ 256.f,
+                   /* width */ 16.f,
+                   /* height */ 16.f,
+                   /* hit_test */
+                   fuchsia::ui::composition::HitTestInteraction::DEFAULT)}),
+           IsInputShield()},
+          {kInvDPR, kInvDPR}));
+}
+
 }  // namespace flutter_runner::testing