diff --git a/shell/platform/fuchsia/flutter/flatland_external_view_embedder.cc b/shell/platform/fuchsia/flutter/flatland_external_view_embedder.cc index f31aecb683d03..2746db78e76c4 100644 --- a/shell/platform/fuchsia/flutter/flatland_external_view_embedder.cc +++ b/shell/platform/fuchsia/flutter/flatland_external_view_embedder.cc @@ -93,8 +93,9 @@ void FlatlandExternalViewEmbedder::PrerollCompositeEmbeddedView( zx_handle_t handle = static_cast(view_id); FML_CHECK(frame_layers_.count(handle) == 0); - frame_layers_.emplace(std::make_pair(EmbedderLayerId{handle}, - EmbedderLayer(frame_size_, *params))); + frame_layers_.emplace(std::make_pair( + EmbedderLayerId{handle}, + EmbedderLayer(frame_size_, *params, flutter::RTreeFactory()))); frame_composition_order_.push_back(handle); } @@ -125,8 +126,9 @@ void FlatlandExternalViewEmbedder::BeginFrame( frame_dpr_ = device_pixel_ratio; // Create the root layer. - frame_layers_.emplace( - std::make_pair(kRootLayerId, EmbedderLayer(frame_size, std::nullopt))); + frame_layers_.emplace(std::make_pair( + kRootLayerId, + EmbedderLayer(frame_size, std::nullopt, flutter::RTreeFactory()))); frame_composition_order_.push_back(kRootLayerId); } @@ -193,6 +195,19 @@ void FlatlandExternalViewEmbedder::SubmitFrame( } } + // Finish recording SkPictures. + { + TRACE_EVENT0("flutter", "FinishRecordingPictures"); + + for (const auto& surface_index : frame_surface_indices) { + const auto& layer = frame_layers_.find(surface_index.first); + FML_CHECK(layer != frame_layers_.end()); + layer->second.picture = + layer->second.recorder->finishRecordingAsPicture(); + FML_CHECK(layer->second.picture != nullptr); + } + } + // Submit layers and platform views to Scenic in composition order. { TRACE_EVENT0("flutter", "SubmitLayers"); @@ -334,30 +349,43 @@ void FlatlandExternalViewEmbedder::SubmitFrame( ? fuchsia::ui::composition::BlendMode::SRC : fuchsia::ui::composition::BlendMode::SRC_OVER); + // Set hit regions for this layer; these hit regions correspond to the + // portions of the layer on which skia drew content. + { + FML_CHECK(layer->second.rtree); + std::list intersection_rects = + layer->second.rtree->searchNonOverlappingDrawnRects( + SkRect::Make(layer->second.surface_size)); + + std::vector hit_regions; + for (const SkRect& rect : intersection_rects) { + hit_regions.emplace_back(); + auto& new_hit_region = hit_regions.back(); + new_hit_region.region.x = rect.x(); + new_hit_region.region.y = rect.y(); + new_hit_region.region.width = rect.width(); + new_hit_region.region.height = rect.height(); + new_hit_region.hit_test = + fuchsia::ui::composition::HitTestInteraction::DEFAULT; + } + + flatland_->flatland()->SetHitRegions( + flatland_layers_[flatland_layer_index].transform_id, + std::move(hit_regions)); + } + // Attach the FlatlandLayer to the main scene graph. flatland_->flatland()->AddChild( root_transform_id_, flatland_layers_[flatland_layer_index].transform_id); child_transforms_.emplace_back( flatland_layers_[flatland_layer_index].transform_id); - - // 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( - flatland_layers_[flatland_layer_index].transform_id, - {{{0, 0, kMaxHitRegionSize, kMaxHitRegionSize}, - fuchsia::ui::composition::HitTestInteraction:: - SEMANTICALLY_INVISIBLE}}); } // Reset for the next pass: flatland_layer_index++; } - // TODO(fxbug.dev/104956): Setting per-layer overlay hit region for Flatland - // external view embedder should match with what is being done in GFX - // external view embedder. // 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. @@ -396,13 +424,10 @@ void FlatlandExternalViewEmbedder::SubmitFrame( const auto& layer = frame_layers_.find(surface_index.first); FML_CHECK(layer != frame_layers_.end()); - sk_sp picture = - layer->second.recorder->finishRecordingAsPicture(); - FML_CHECK(picture != nullptr); canvas->setMatrix(SkMatrix::I()); canvas->clear(SK_ColorTRANSPARENT); - canvas->drawPicture(picture); + canvas->drawPicture(layer->second.picture); canvas->flush(); } } diff --git a/shell/platform/fuchsia/flutter/flatland_external_view_embedder.h b/shell/platform/fuchsia/flutter/flatland_external_view_embedder.h index 0600b598c1351..c6d670785d70e 100644 --- a/shell/platform/fuchsia/flutter/flatland_external_view_embedder.h +++ b/shell/platform/fuchsia/flutter/flatland_external_view_embedder.h @@ -17,6 +17,7 @@ #include #include "flutter/flow/embedded_views.h" +#include "flutter/flow/rtree.h" #include "flutter/fml/logging.h" #include "flutter/fml/macros.h" #include "flutter/shell/common/canvas_spy.h" @@ -144,18 +145,29 @@ class FlatlandExternalViewEmbedder final struct EmbedderLayer { EmbedderLayer(const SkISize& frame_size, - std::optional view_params) - : embedded_view_params(std::move(view_params)), + std::optional view_params, + flutter::RTreeFactory rtree_factory) + : rtree(rtree_factory.getInstance()), + embedded_view_params(std::move(view_params)), recorder(std::make_unique()), canvas_spy(std::make_unique( - recorder->beginRecording(frame_size.width(), - frame_size.height()))), - surface_size(frame_size) {} + recorder->beginRecording(SkRect::Make(frame_size), + &rtree_factory))), + surface_size(frame_size), + picture(nullptr) {} + + // Records paint operations applied to this layer's `SkCanvas`. + // These records are used to determine which portions of this layer + // contain content. The embedder propagates this information to scenic, so + // that scenic can accurately decide which portions of this layer may + // interact with input. + sk_sp rtree; std::optional embedded_view_params; std::unique_ptr recorder; std::unique_ptr canvas_spy; SkISize surface_size; + sk_sp picture; }; using EmbedderLayerId = std::optional; constexpr static EmbedderLayerId kRootLayerId = EmbedderLayerId{}; 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 a55d89aca2bcd..4c782089df38e 100644 --- a/shell/platform/fuchsia/flutter/tests/fakes/scenic/fake_flatland.cc +++ b/shell/platform/fuchsia/flutter/tests/fakes/scenic/fake_flatland.cc @@ -850,7 +850,7 @@ void FakeFlatland::SetHitRegions( auto& transform = found_transform->second; FML_CHECK(transform); - transform->num_hit_regions = regions.size(); + transform->hit_regions = std::move(regions); } void FakeFlatland::Clear() { diff --git a/shell/platform/fuchsia/flutter/tests/fakes/scenic/fake_flatland_types.cc b/shell/platform/fuchsia/flutter/tests/fakes/scenic/fake_flatland_types.cc index a265241f4d781..777344a36f5df 100644 --- a/shell/platform/fuchsia/flutter/tests/fakes/scenic/fake_flatland_types.cc +++ b/shell/platform/fuchsia/flutter/tests/fakes/scenic/fake_flatland_types.cc @@ -72,7 +72,7 @@ std::shared_ptr CloneFakeTransform( .children = CloneFakeTransformVector( transform->children, transform_cache), .content = CloneFakeContent(transform->content), - .num_hit_regions = transform->num_hit_regions, + .hit_regions = transform->hit_regions, })); FML_CHECK(success); @@ -136,7 +136,7 @@ bool FakeTransform::operator==(const FakeTransform& other) const { return id == other.id && translation == other.translation && *clip_bounds == *other.clip_bounds && orientation == other.orientation && children == other.children && - content == other.content && num_hit_regions == other.num_hit_regions; + content == other.content && hit_regions == other.hit_regions; } bool FakeGraph::operator==(const FakeGraph& other) const { 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 cd2170c3b58f3..1b0fc40076e0a 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 @@ -15,6 +15,7 @@ #include #include +#include #include #include #include @@ -98,6 +99,31 @@ inline bool operator==(const fuchsia::ui::composition::ImageProperties& a, return size_equal; } +inline bool operator==(const fuchsia::ui::composition::HitRegion& a, + const fuchsia::ui::composition::HitRegion& b) { + return a.region == b.region && a.hit_test == b.hit_test; +} + +inline bool operator!=(const fuchsia::ui::composition::HitRegion& a, + const fuchsia::ui::composition::HitRegion& b) { + return !(a == b); +} + +inline bool operator==( + const std::vector& a, + const std::vector& b) { + if (a.size() != b.size()) + return false; + + for (size_t i = 0; i < a.size(); ++i) { + if (a[i] != b[i]) { + return false; + } + } + + return true; +} + namespace flutter_runner::testing { constexpr static fuchsia::ui::composition::TransformId kInvalidTransformId{0}; @@ -194,7 +220,7 @@ struct FakeTransform { std::vector> children; std::shared_ptr content; - size_t num_hit_regions; + std::vector hit_regions; }; struct FakeGraph { 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 fac48844c782e..213efaa2af131 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 @@ -202,6 +202,15 @@ Matcher IsViewportProperties( inset)); } +Matcher IsHitRegion( + const float x, + const float y, + const float width, + const float height, + const fuchsia::ui::composition::HitTestInteraction hit_test) { + return FieldsAre(FieldsAre(x, y, width, height), hit_test); +} + Matcher IsEmptyGraph() { return FieldsAre(IsEmpty(), IsEmpty(), Eq(nullptr), Eq(std::nullopt)); } @@ -224,7 +233,7 @@ Matcher IsFlutterGraph( /*scale*/ scale, FakeTransform::kDefaultOrientation, /*clip_bounds*/ _, FakeTransform::kDefaultOpacity, /*children*/ ElementsAreArray(layer_matchers), - /*content*/ Eq(nullptr), /*num_hit_regions*/ _)), + /*content*/ Eq(nullptr), /*hit_regions*/ _)), Eq(FakeView{ .view_token = viewport_token_koids.second, .view_ref = view_ref_koids.first, @@ -240,7 +249,8 @@ Matcher IsFlutterGraph( Matcher> IsImageLayer( const fuchsia::math::SizeU& layer_size, fuchsia::ui::composition::BlendMode blend_mode, - size_t num_hit_regions) { + std::vector> + hit_region_matchers) { return Pointee(FieldsAre( /*id*/ _, FakeTransform::kDefaultTranslation, FakeTransform::kDefaultScale, FakeTransform::kDefaultOrientation, @@ -252,7 +262,7 @@ Matcher> IsImageLayer( FakeImage::kDefaultSampleRegion, layer_size, FakeImage::kDefaultOpacity, blend_mode, /*buffer_import_token*/ _, /*vmo_index*/ 0))), - num_hit_regions)); + /* hit_regions*/ ElementsAreArray(hit_region_matchers))); } Matcher> IsViewportLayer( @@ -271,7 +281,7 @@ Matcher> IsViewportLayer( /* id */ _, IsViewportProperties(view_logical_size, view_inset), /* viewport_token */ GetKoids(view_token).second, /* child_view_watcher */ _))), - /*num_hit_regions*/ 0)); + /*hit_regions*/ _)); } fuchsia::ui::composition::OnNextFrameBeginValues WithPresentCredits( @@ -478,11 +488,21 @@ TEST_F(FlatlandExternalViewEmbedderTest, SimpleScene) { // Pump the message loop. The scene updates should propagate to flatland. loop().RunUntilIdle(); + EXPECT_THAT( fake_flatland().graph(), - IsFlutterGraph(parent_viewport_watcher, viewport_creation_token, view_ref, - /*layers*/ - {IsImageLayer(frame_size, kFirstLayerBlendMode, 1)})); + 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)})})); } TEST_F(FlatlandExternalViewEmbedderTest, SceneWithOneView) { @@ -598,10 +618,26 @@ TEST_F(FlatlandExternalViewEmbedderTest, SceneWithOneView) { fake_flatland().graph(), IsFlutterGraph( parent_viewport_watcher, viewport_creation_token, view_ref, /*layers*/ - {IsImageLayer(frame_size, kFirstLayerBlendMode, 1), + {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, 1)}, + IsImageLayer( + frame_size, kUpperLayerBlendMode, + {IsHitRegion( + /* x */ 384.f, + /* y */ 256.f, + /* width */ 16.f, + /* height */ 16.f, + /* hit_test */ + fuchsia::ui::composition::HitTestInteraction::DEFAULT)})}, {kInvDPR, kInvDPR})); // Destroy the view. The scene graph shouldn't change yet. @@ -611,10 +647,26 @@ TEST_F(FlatlandExternalViewEmbedderTest, SceneWithOneView) { fake_flatland().graph(), IsFlutterGraph( parent_viewport_watcher, viewport_creation_token, view_ref, /*layers*/ - {IsImageLayer(frame_size, kFirstLayerBlendMode, 1), + {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, 1)}, + IsImageLayer( + frame_size, kUpperLayerBlendMode, + {IsHitRegion( + /* x */ 384.f, + /* y */ 256.f, + /* width */ 16.f, + /* height */ 16.f, + /* hit_test */ + fuchsia::ui::composition::HitTestInteraction::DEFAULT)})}, {kInvDPR, kInvDPR})); // Draw another frame without the view. The scene graph shouldn't change yet. @@ -634,19 +686,43 @@ TEST_F(FlatlandExternalViewEmbedderTest, SceneWithOneView) { fake_flatland().graph(), IsFlutterGraph( parent_viewport_watcher, viewport_creation_token, view_ref, /*layers*/ - {IsImageLayer(frame_size, kFirstLayerBlendMode, 1), + {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, 1)}, + IsImageLayer( + frame_size, kUpperLayerBlendMode, + {IsHitRegion( + /* x */ 384.f, + /* y */ 256.f, + /* width */ 16.f, + /* height */ 16.f, + /* hit_test */ + fuchsia::ui::composition::HitTestInteraction::DEFAULT)})}, {kInvDPR, kInvDPR})); // Pump the message loop. The scene updates should propagate to flatland. loop().RunUntilIdle(); EXPECT_THAT( fake_flatland().graph(), - IsFlutterGraph(parent_viewport_watcher, viewport_creation_token, - view_ref, /*layers*/ - {IsImageLayer(frame_size, kFirstLayerBlendMode, 1)})); + 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)})})); } TEST_F(FlatlandExternalViewEmbedderTest, SceneWithOneView_NoOverlay) { @@ -736,24 +812,40 @@ TEST_F(FlatlandExternalViewEmbedderTest, SceneWithOneView_NoOverlay) { loop().RunUntilIdle(); EXPECT_THAT( fake_flatland().graph(), - IsFlutterGraph(parent_viewport_watcher, viewport_creation_token, - view_ref, /*layers*/ - {IsImageLayer(frame_size, kFirstLayerBlendMode, 1), - IsViewportLayer(child_view_token, child_view_size, - FakeViewport::kDefaultViewportInset, - {0, 0}, kScale, kOpacityFloat)})); + 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, + FakeViewport::kDefaultViewportInset, {0, 0}, kScale, + kOpacityFloat)})); // Destroy the view. The scene graph shouldn't change yet. external_view_embedder.DestroyView( child_view_id, [](fuchsia::ui::composition::ContentId) {}); EXPECT_THAT( fake_flatland().graph(), - IsFlutterGraph(parent_viewport_watcher, viewport_creation_token, - view_ref, /*layers*/ - {IsImageLayer(frame_size, kFirstLayerBlendMode, 1), - IsViewportLayer(child_view_token, child_view_size, - FakeViewport::kDefaultViewportInset, - {0, 0}, kScale, kOpacityFloat)})); + 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, + FakeViewport::kDefaultViewportInset, {0, 0}, kScale, + kOpacityFloat)})); // Draw another frame without the view. The scene graph shouldn't change yet. DrawSimpleFrame( @@ -771,20 +863,36 @@ TEST_F(FlatlandExternalViewEmbedderTest, SceneWithOneView_NoOverlay) { EXPECT_THAT( fake_flatland().graph(), - IsFlutterGraph(parent_viewport_watcher, viewport_creation_token, - view_ref, /*layers*/ - {IsImageLayer(frame_size, kFirstLayerBlendMode, 1), - IsViewportLayer(child_view_token, child_view_size, - FakeViewport::kDefaultViewportInset, - {0, 0}, kScale, kOpacityFloat)})); + 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, + FakeViewport::kDefaultViewportInset, {0, 0}, kScale, + kOpacityFloat)})); // Pump the message loop. The scene updates should propagate to flatland. loop().RunUntilIdle(); EXPECT_THAT( fake_flatland().graph(), - IsFlutterGraph(parent_viewport_watcher, viewport_creation_token, - view_ref, /*layers*/ - {IsImageLayer(frame_size, kFirstLayerBlendMode, 1)})); + 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)})})); } TEST_F(FlatlandExternalViewEmbedderTest, @@ -850,18 +958,36 @@ TEST_F(FlatlandExternalViewEmbedderTest, loop().RunUntilIdle(); EXPECT_THAT( fake_flatland().graph(), - IsFlutterGraph(parent_viewport_watcher, viewport_creation_token, view_ref, - /*layers*/ - {IsImageLayer(frame_size, kFirstLayerBlendMode, 1)})); + 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)})})); // Destroy the view. The scene graph shouldn't change yet. external_view_embedder.DestroyView( child_view_id, [](fuchsia::ui::composition::ContentId) {}); EXPECT_THAT( fake_flatland().graph(), - IsFlutterGraph(parent_viewport_watcher, viewport_creation_token, view_ref, - /*layers*/ - {IsImageLayer(frame_size, kFirstLayerBlendMode, 1)})); + 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)})})); // Draw another frame without the view and change the size. The scene graph // shouldn't change yet. @@ -883,17 +1009,208 @@ TEST_F(FlatlandExternalViewEmbedderTest, }); EXPECT_THAT( fake_flatland().graph(), - IsFlutterGraph(parent_viewport_watcher, viewport_creation_token, view_ref, - /*layers*/ - {IsImageLayer(frame_size, kFirstLayerBlendMode, 1)})); + 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)})})); // Pump the message loop. The scene updates should propagate to flatland. loop().RunUntilIdle(); EXPECT_THAT( fake_flatland().graph(), - IsFlutterGraph(parent_viewport_watcher, viewport_creation_token, - view_ref, /*layers*/ - {IsImageLayer(new_frame_size, kFirstLayerBlendMode, 1)})); + IsFlutterGraph( + parent_viewport_watcher, viewport_creation_token, view_ref, /*layers*/ + {IsImageLayer( + new_frame_size, kFirstLayerBlendMode, + {IsHitRegion( + /* x */ 64.f, + /* y */ 128.f, + /* width */ 8.f, + /* height */ 8.f, + /* hit_test */ + fuchsia::ui::composition::HitTestInteraction::DEFAULT)})})); +} + +// This test case exercises the scenario in which the view contains two disjoint +// regions with painted content; we should generate two separate hit regions +// matching the bounds of the painted regions in this case. +TEST_F(FlatlandExternalViewEmbedderTest, SimpleScene_DisjointHitRegions) { + 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()); + 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)); + + // 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(frame_size_signed.width()), + static_cast(frame_size_signed.height())}; + DrawSimpleFrame( + external_view_embedder, frame_size_signed, 1.f, [](SkCanvas* canvas) { + const SkSize canvas_size = SkSize::Make(canvas->imageInfo().width(), + canvas->imageInfo().height()); + + SkRect paint_region_1, paint_region_2; + + paint_region_1 = SkRect::MakeXYWH( + canvas_size.width() / 4.f, canvas_size.height() / 2.f, + canvas_size.width() / 32.f, canvas_size.height() / 32.f); + + SkPaint rect_paint; + rect_paint.setColor(SK_ColorGREEN); + canvas->drawRect(paint_region_1, rect_paint); + + paint_region_2 = SkRect::MakeXYWH( + canvas_size.width() * 3.f / 4.f, canvas_size.height() / 2.f, + canvas_size.width() / 32.f, canvas_size.height() / 32.f); + + rect_paint.setColor(SK_ColorRED); + canvas->drawRect(paint_region_2, rect_paint); + }); + EXPECT_THAT(fake_flatland().graph(), + IsFlutterGraph(parent_viewport_watcher, viewport_creation_token, + view_ref)); + + // Pump the message loop. The scene updates should propagate to flatland. + 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), + IsHitRegion( + /* x */ 384.f, + /* y */ 256.f, + /* width */ 16.f, + /* height */ 16.f, + /* hit_test */ + fuchsia::ui::composition::HitTestInteraction::DEFAULT)})})); +} + +// This test case exercises the scenario in which the view contains two +// overlapping regions with painted content; we should generate one hit +// region matching the union of the bounds of the two painted regions in +// this case. +TEST_F(FlatlandExternalViewEmbedderTest, SimpleScene_OverlappingHitRegions) { + 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()); + 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)); + + // 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(frame_size_signed.width()), + static_cast(frame_size_signed.height())}; + DrawSimpleFrame( + external_view_embedder, frame_size_signed, 1.f, [](SkCanvas* canvas) { + const SkSize canvas_size = SkSize::Make(canvas->imageInfo().width(), + canvas->imageInfo().height()); + + SkRect paint_region_1, paint_region_2; + + paint_region_1 = SkRect::MakeXYWH( + canvas_size.width() / 4.f, canvas_size.height() / 2.f, + 3.f * canvas_size.width() / 8.f, canvas_size.height() / 4.f); + + SkPaint rect_paint; + rect_paint.setColor(SK_ColorGREEN); + canvas->drawRect(paint_region_1, rect_paint); + + paint_region_2 = SkRect::MakeXYWH( + canvas_size.width() * 3.f / 8.f, canvas_size.height() / 2.f, + 3.f * canvas_size.width() / 8.f, canvas_size.height() / 4.f); + + rect_paint.setColor(SK_ColorRED); + canvas->drawRect(paint_region_2, rect_paint); + }); + EXPECT_THAT(fake_flatland().graph(), + IsFlutterGraph(parent_viewport_watcher, viewport_creation_token, + view_ref)); + + // Pump the message loop. The scene updates should propagate to flatland. + 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 */ 256.f, + /* height */ 128.f, + /* hit_test */ + fuchsia::ui::composition::HitTestInteraction::DEFAULT)})})); } } // namespace flutter_runner::testing