[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