Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

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

Merged
merged 1 commit into from
Jan 9, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 6 additions & 17 deletions shell/platform/fuchsia/flutter/flatland_external_view_embedder.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -59,19 +54,13 @@ FlatlandExternalViewEmbedder::FlatlandExternalViewEmbedder(
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}});
}
}

Expand Down Expand Up @@ -453,9 +442,9 @@ void FlatlandExternalViewEmbedder::SubmitFrame(
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_);
Expand Down
23 changes: 23 additions & 0 deletions shell/platform/fuchsia/flutter/tests/fakes/scenic/fake_flatland.cc
Original file line number Diff line number Diff line change
Expand Up @@ -853,6 +853,29 @@ void FakeFlatland::SetHitRegions(
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();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -310,6 +310,11 @@ class FakeFlatland
fuchsia::ui::composition::TransformId transform_id,
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;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
#include <zircon/types.h>

#include <algorithm>
#include <cfloat>
#include <cstdint>
#include <optional>
#include <unordered_map>
Expand Down Expand Up @@ -138,6 +139,8 @@ namespace flutter_runner::testing {

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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,10 @@ using fuchsia::scenic::scheduling::FuturePresentationTimes;
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;
Expand Down Expand Up @@ -297,6 +299,16 @@ Matcher<std::shared_ptr<FakeTransform>> IsClipTransformLayer(
/*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;
Expand Down Expand Up @@ -396,15 +408,18 @@ class FlatlandExternalViewEmbedderTest : public ::testing::Test {
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.
Expand Down Expand Up @@ -1476,4 +1491,143 @@ TEST_F(FlatlandExternalViewEmbedderTest, SimpleScene_OverlappingHitRegions) {
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