Skip to content

Commit

Permalink
[fuchsia][scenic] Use infinite hit region (flutter#38647)
Browse files Browse the repository at this point in the history
fxbug.dev/118729
  • Loading branch information
jaeheon authored and Ricardo Amador committed Jan 25, 2023
1 parent 9119549 commit 7d46cd5
Show file tree
Hide file tree
Showing 5 changed files with 195 additions and 21 deletions.
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

0 comments on commit 7d46cd5

Please sign in to comment.