blob: 6e682ba0fa3d4069f19c7c50ebf1c52b06c072ff [file] [log] [blame]
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "flutter/shell/platform/fuchsia/flutter/gfx_external_view_embedder.h"
#include <fuchsia/sysmem/cpp/fidl.h>
#include <fuchsia/ui/gfx/cpp/fidl.h>
#include <fuchsia/ui/scenic/cpp/fidl.h>
#include <fuchsia/ui/views/cpp/fidl.h>
#include <lib/async-testing/test_loop.h>
#include <lib/async/dispatcher.h>
#include <lib/fidl/cpp/interface_request.h>
#include <lib/fidl/cpp/synchronous_interface_ptr.h>
#include <lib/inspect/cpp/inspect.h>
#include <lib/ui/scenic/cpp/commands.h>
#include <lib/ui/scenic/cpp/view_ref_pair.h>
#include <lib/ui/scenic/cpp/view_token_pair.h>
#include <algorithm>
#include <cstdint>
#include <functional>
#include <memory>
#include <string>
#include <vector>
#include "flutter/flow/embedded_views.h"
#include "flutter/fml/logging.h"
#include "flutter/fml/time/time_delta.h"
#include "flutter/fml/time/time_point.h"
#include "third_party/skia/include/core/SkColor.h"
#include "third_party/skia/include/core/SkSize.h"
#include "third_party/skia/include/core/SkSurface.h"
#include "fakes/scenic/fake_resources.h"
#include "fakes/scenic/fake_session.h"
#include "flutter/shell/platform/fuchsia/flutter/surface_producer.h"
#include "gmock/gmock.h" // For EXPECT_THAT and matchers
#include "gtest/gtest.h"
using fuchsia::scenic::scheduling::FramePresentedInfo;
using fuchsia::scenic::scheduling::FuturePresentationTimes;
using fuchsia::scenic::scheduling::PresentReceivedInfo;
using ::testing::_;
using ::testing::ElementsAre;
using ::testing::FieldsAre;
using ::testing::IsEmpty;
using ::testing::IsNull;
using ::testing::Matcher;
using ::testing::Pointee;
using ::testing::SizeIs;
using ::testing::VariantWith;
namespace flutter_runner::testing {
namespace {
class FakeSurfaceProducerSurface : public SurfaceProducerSurface {
public:
explicit FakeSurfaceProducerSurface(scenic::Session& session,
const SkISize& size,
uint32_t buffer_id)
: session_(session),
surface_(SkSurface::MakeNull(size.width(), size.height())),
image_id_(session_.AllocResourceId()),
buffer_id_(buffer_id) {
FML_CHECK(buffer_id_ != 0);
fuchsia::sysmem::BufferCollectionTokenSyncPtr token;
buffer_binding_ = token.NewRequest();
session_.RegisterBufferCollection(buffer_id_, std::move(token));
session_.Enqueue(scenic::NewCreateImage2Cmd(
image_id_, surface_->width(), surface_->height(), buffer_id_, 0));
}
~FakeSurfaceProducerSurface() override {
session_.DeregisterBufferCollection(buffer_id_);
session_.Enqueue(scenic::NewReleaseResourceCmd(image_id_));
}
bool IsValid() const override { return true; }
SkISize GetSize() const override {
return SkISize::Make(surface_->width(), surface_->height());
}
void SetImageId(uint32_t image_id) override { FAIL(); }
uint32_t GetImageId() override { return image_id_; }
sk_sp<SkSurface> GetSkiaSurface() const override { return surface_; }
fuchsia::ui::composition::BufferCollectionImportToken
GetBufferCollectionImportToken() override {
return fuchsia::ui::composition::BufferCollectionImportToken{};
}
zx::event GetAcquireFence() override { return zx::event{}; }
zx::event GetReleaseFence() override { return zx::event{}; }
void SetReleaseImageCallback(
ReleaseImageCallback release_image_callback) override {}
size_t AdvanceAndGetAge() override { return 0; }
bool FlushSessionAcquireAndReleaseEvents() override { return true; }
void SignalWritesFinished(
const std::function<void(void)>& on_writes_committed) override {}
private:
scenic::Session& session_;
sk_sp<SkSurface> surface_;
fidl::InterfaceRequest<fuchsia::sysmem::BufferCollectionToken>
buffer_binding_;
FakeResourceId image_id_{kInvalidFakeResourceId};
uint32_t buffer_id_{0};
};
class FakeSurfaceProducer : public SurfaceProducer {
public:
explicit FakeSurfaceProducer(scenic::Session& session) : session_(session) {}
~FakeSurfaceProducer() override = default;
std::unique_ptr<SurfaceProducerSurface> ProduceSurface(
const SkISize& size) override {
return std::make_unique<FakeSurfaceProducerSurface>(session_, size,
buffer_id_++);
}
void SubmitSurfaces(
std::vector<std::unique_ptr<SurfaceProducerSurface>> surfaces) override {}
private:
scenic::Session& session_;
uint32_t buffer_id_{1};
};
struct FakeCompositorLayer {
enum class LayerType : uint32_t {
Image,
View,
};
std::shared_ptr<FakeResource> layer_root;
LayerType layer_type{LayerType::Image};
size_t layer_index{0};
};
std::string GetCurrentTestName() {
return ::testing::UnitTest::GetInstance()->current_test_info()->name();
}
zx_koid_t GetKoid(zx_handle_t handle) {
if (handle == ZX_HANDLE_INVALID) {
return ZX_KOID_INVALID;
}
zx_info_handle_basic_t info;
zx_status_t status = zx_object_get_info(handle, ZX_INFO_HANDLE_BASIC, &info,
sizeof(info), nullptr, nullptr);
return status == ZX_OK ? info.koid : ZX_KOID_INVALID;
}
zx_koid_t GetPeerKoid(zx_handle_t handle) {
if (handle == ZX_HANDLE_INVALID) {
return ZX_KOID_INVALID;
}
zx_info_handle_basic_t info;
zx_status_t status = zx_object_get_info(handle, ZX_INFO_HANDLE_BASIC, &info,
sizeof(info), nullptr, nullptr);
return status == ZX_OK ? info.related_koid : ZX_KOID_INVALID;
}
MATCHER_P(MaybeIsEmpty, assert_empty, "") {
return assert_empty ? ExplainMatchResult(IsEmpty(), arg, result_listener)
: ExplainMatchResult(_, arg, result_listener);
}
Matcher<FakeSceneGraph> IsEmptySceneGraph() {
return FieldsAre(IsEmpty(), IsEmpty(), IsEmpty(), kInvalidFakeResourceId);
}
void AssertRootSceneGraph(const FakeSceneGraph& scene_graph,
bool assert_empty) {
ASSERT_NE(scene_graph.root_view_id, kInvalidFakeResourceId);
ASSERT_EQ(scene_graph.resource_map.count(scene_graph.root_view_id), 1u);
auto scene_graph_root =
scene_graph.resource_map.find(scene_graph.root_view_id);
ASSERT_THAT(
scene_graph_root->second,
Pointee(FieldsAre(
scene_graph.root_view_id, "", FakeResource::kDefaultEmptyEventMask,
VariantWith<FakeView>(FieldsAre(
_, _, _, _,
ElementsAre(Pointee(FieldsAre(
_, "Flutter::MetricsWatcher",
fuchsia::ui::gfx::kMetricsEventMask,
VariantWith<FakeEntityNode>(FieldsAre(
FieldsAre(
ElementsAre(Pointee(FieldsAre(
_, "Flutter::LayerTree",
FakeResource::kDefaultEmptyEventMask,
VariantWith<FakeEntityNode>(FieldsAre(
FieldsAre(MaybeIsEmpty(assert_empty),
FakeNode::kDefaultZeroRotation,
FakeNode::kDefaultOneScale,
FakeNode::kDefaultZeroTranslation,
FakeNode::kDefaultZeroAnchor,
FakeNode::kIsHitTestable,
FakeNode::kIsSemanticallyVisible),
IsEmpty()))))),
FakeNode::kDefaultZeroRotation,
FakeNode::kDefaultOneScale,
FakeNode::kDefaultZeroTranslation,
FakeNode::kDefaultZeroAnchor,
FakeNode::kIsHitTestable,
FakeNode::kIsSemanticallyVisible),
IsEmpty()))))),
FakeView::kDebugBoundsDisbaled)))));
}
void ExpectRootSceneGraph(
const FakeSceneGraph& scene_graph,
const std::string& debug_name,
const fuchsia::ui::views::ViewHolderToken& view_holder_token,
const fuchsia::ui::views::ViewRef& view_ref) {
AssertRootSceneGraph(scene_graph, true);
// These are safe to do unchecked due to `AssertRootSceneGraph` above.
auto root_view_it = scene_graph.resource_map.find(scene_graph.root_view_id);
auto* root_view_state = std::get_if<FakeView>(&root_view_it->second->state);
EXPECT_EQ(root_view_state->token, GetPeerKoid(view_holder_token.value.get()));
EXPECT_EQ(root_view_state->control_ref,
GetPeerKoid(view_ref.reference.get()));
EXPECT_EQ(root_view_state->view_ref, GetKoid(view_ref.reference.get()));
EXPECT_EQ(root_view_state->debug_name, debug_name);
EXPECT_EQ(scene_graph.resource_map.size(), 3u);
}
void ExpectImageCompositorLayer(const FakeCompositorLayer& layer,
const SkISize layer_size) {
const SkSize float_layer_size =
SkSize::Make(layer_size.width(), layer_size.height());
const size_t flutter_layer_index =
(layer.layer_index + 1) / 2; // Integer division
const float views_under_layer_depth =
flutter_layer_index *
GfxExternalViewEmbedder::kScenicZElevationForPlatformView;
const float layer_depth =
flutter_layer_index *
GfxExternalViewEmbedder::kScenicZElevationBetweenLayers +
views_under_layer_depth;
const bool layer_hit_testable = (flutter_layer_index == 0)
? FakeNode::kIsHitTestable
: FakeNode::kIsNotHitTestable;
const float layer_opacity =
(flutter_layer_index == 0)
? GfxExternalViewEmbedder::kBackgroundLayerOpacity / 255.f
: GfxExternalViewEmbedder::kOverlayLayerOpacity / 255.f;
EXPECT_EQ(layer.layer_type, FakeCompositorLayer::LayerType::Image);
EXPECT_EQ(layer.layer_index % 2, 0u);
EXPECT_THAT(
layer.layer_root,
Pointee(FieldsAre(
_, "Flutter::Layer", FakeResource::kDefaultEmptyEventMask,
VariantWith<FakeShapeNode>(FieldsAre(
FieldsAre(IsEmpty(), FakeNode::kDefaultZeroRotation,
FakeNode::kDefaultOneScale,
std::array<float, 3>{float_layer_size.width() / 2.f,
float_layer_size.height() / 2.f,
-layer_depth},
FakeNode::kDefaultZeroAnchor, layer_hit_testable,
FakeNode::kIsSemanticallyVisible),
Pointee(
FieldsAre(_, "", FakeResource::kDefaultEmptyEventMask,
VariantWith<FakeShape>(
FieldsAre(VariantWith<FakeShape::RectangleDef>(
FieldsAre(float_layer_size.width(),
float_layer_size.height())))))),
Pointee(FieldsAre(
_, "", FakeResource::kDefaultEmptyEventMask,
VariantWith<FakeMaterial>(FieldsAre(
Pointee(FieldsAre(
_, "", FakeResource::kDefaultEmptyEventMask,
VariantWith<FakeImage>(FieldsAre(
VariantWith<FakeImage::Image2Def>(
FieldsAre(_, 0, float_layer_size.width(),
float_layer_size.height())),
IsNull())))),
std::array<float, 4>{1.f, 1.f, 1.f,
layer_opacity})))))))));
}
void ExpectViewCompositorLayer(const FakeCompositorLayer& layer,
const fuchsia::ui::views::ViewToken& view_token,
const flutter::EmbeddedViewParams& view_params) {
const size_t flutter_layer_index =
(layer.layer_index + 1) / 2; // Integer division
const float views_under_layer_depth =
flutter_layer_index > 0
? (flutter_layer_index - 1) *
GfxExternalViewEmbedder::kScenicZElevationForPlatformView
: 0.f;
const float layer_depth =
flutter_layer_index *
GfxExternalViewEmbedder::kScenicZElevationBetweenLayers +
views_under_layer_depth;
EXPECT_EQ(layer.layer_type, FakeCompositorLayer::LayerType::View);
EXPECT_EQ(layer.layer_index % 2, 1u);
EXPECT_THAT(
layer.layer_root,
Pointee(FieldsAre(
_, _ /*"Flutter::PlatformView::OpacityMutator" */,
FakeResource::kDefaultEmptyEventMask,
VariantWith<FakeOpacityNode>(FieldsAre(
FieldsAre(
ElementsAre(Pointee(FieldsAre(
_, _ /*"Flutter::PlatformView::TransformMutator" */,
FakeResource::kDefaultEmptyEventMask,
VariantWith<FakeEntityNode>(FieldsAre(
FieldsAre(
ElementsAre(Pointee(FieldsAre(
_, "", FakeResource::kDefaultEmptyEventMask,
VariantWith<FakeViewHolder>(FieldsAre(
FieldsAre(
IsEmpty(),
FakeNode::kDefaultZeroRotation,
FakeNode::kDefaultOneScale,
FakeNode::kDefaultZeroTranslation,
FakeNode::kDefaultZeroAnchor,
FakeNode::kIsHitTestable,
FakeNode::kIsSemanticallyVisible),
GetPeerKoid(view_token.value.get()),
"Flutter::PlatformView",
fuchsia::ui::gfx::ViewProperties{
.bounding_box =
fuchsia::ui::gfx::BoundingBox{
.min = {0.f, 0.f, -1000.f},
.max =
{view_params.sizePoints()
.width(),
view_params.sizePoints()
.height(),
0.f},
}},
FakeViewHolder::
kDefaultBoundsColorWhite))))),
FakeNode::kDefaultZeroRotation,
FakeNode::kDefaultOneScale,
std::array<float, 3>{0.f, 0.f, -layer_depth},
FakeNode::kDefaultZeroAnchor,
FakeNode::kIsHitTestable,
FakeNode::kIsSemanticallyVisible),
IsEmpty()))))),
FakeNode::kDefaultZeroRotation, FakeNode::kDefaultOneScale,
FakeNode::kDefaultZeroTranslation,
FakeNode::kDefaultZeroAnchor, FakeNode::kIsHitTestable,
FakeNode::kIsSemanticallyVisible),
FakeOpacityNode::kDefaultOneOpacity)))));
}
std::vector<FakeCompositorLayer> ExtractLayersFromSceneGraph(
const FakeSceneGraph& scene_graph) {
AssertRootSceneGraph(scene_graph, false);
// These are safe to do unchecked due to `AssertRootSceneGraph` above.
auto root_view_it = scene_graph.resource_map.find(scene_graph.root_view_id);
auto* root_view_state = std::get_if<FakeView>(&root_view_it->second->state);
auto* metrics_watcher_state =
std::get_if<FakeEntityNode>(&root_view_state->children[0]->state);
auto* layer_tree_state = std::get_if<FakeEntityNode>(
&metrics_watcher_state->node_state.children[0]->state);
std::vector<FakeCompositorLayer> layers;
for (auto& layer_resource : layer_tree_state->node_state.children) {
const size_t layer_index = layers.size();
const FakeCompositorLayer::LayerType layer_type =
(layer_index % 2 == 0) ? FakeCompositorLayer::LayerType::Image
: FakeCompositorLayer::LayerType::View;
layers.emplace_back(FakeCompositorLayer{
.layer_root = layer_resource,
.layer_type = layer_type,
.layer_index = layer_index,
});
}
return layers;
}
void DrawSimpleFrame(GfxExternalViewEmbedder& external_view_embedder,
SkISize frame_size,
float frame_dpr,
std::function<void(SkCanvas*)> draw_callback) {
external_view_embedder.BeginFrame(frame_size, nullptr, frame_dpr, nullptr);
{
SkCanvas* root_canvas = external_view_embedder.GetRootCanvas();
external_view_embedder.PostPrerollAction(nullptr);
draw_callback(root_canvas);
}
external_view_embedder.EndFrame(false, nullptr);
external_view_embedder.SubmitFrame(
nullptr, std::make_unique<flutter::SurfaceFrame>(
nullptr, true,
[](const flutter::SurfaceFrame& surface_frame,
SkCanvas* canvas) { return true; }));
}
void DrawFrameWithView(GfxExternalViewEmbedder& external_view_embedder,
SkISize frame_size,
float frame_dpr,
int view_id,
flutter::EmbeddedViewParams& view_params,
std::function<void(SkCanvas*)> background_draw_callback,
std::function<void(SkCanvas*)> overlay_draw_callback) {
external_view_embedder.BeginFrame(frame_size, nullptr, frame_dpr, nullptr);
{
SkCanvas* root_canvas = external_view_embedder.GetRootCanvas();
external_view_embedder.PrerollCompositeEmbeddedView(
view_id, std::make_unique<flutter::EmbeddedViewParams>(view_params));
external_view_embedder.PostPrerollAction(nullptr);
background_draw_callback(root_canvas);
SkCanvas* overlay_canvas =
external_view_embedder.CompositeEmbeddedView(view_id);
overlay_draw_callback(overlay_canvas);
}
external_view_embedder.EndFrame(false, nullptr);
external_view_embedder.SubmitFrame(
nullptr, std::make_unique<flutter::SurfaceFrame>(
nullptr, true,
[](const flutter::SurfaceFrame& surface_frame,
SkCanvas* canvas) { return true; }));
}
FramePresentedInfo MakeFramePresentedInfoForOnePresent(
int64_t latched_time,
int64_t frame_presented_time) {
std::vector<PresentReceivedInfo> present_infos;
present_infos.emplace_back();
present_infos.back().set_present_received_time(0);
present_infos.back().set_latched_time(0);
return FramePresentedInfo{
.actual_presentation_time = 0,
.presentation_infos = std::move(present_infos),
.num_presents_allowed = 1,
};
}
}; // namespace
class GfxExternalViewEmbedderTest
: public ::testing::Test,
public fuchsia::ui::scenic::SessionListener {
protected:
GfxExternalViewEmbedderTest()
: session_listener_(this),
session_subloop_(loop_.StartNewLoop()),
session_connection_(CreateSessionConnection()),
fake_surface_producer_(*session_connection_.get()) {}
~GfxExternalViewEmbedderTest() override = default;
async::TestLoop& loop() { return loop_; }
FakeSession& fake_session() { return fake_session_; }
FakeSurfaceProducer& fake_surface_producer() {
return fake_surface_producer_;
}
GfxSessionConnection& session_connection() { return session_connection_; }
private:
// |fuchsia::ui::scenic::SessionListener|
void OnScenicError(std::string error) override { FAIL(); }
// |fuchsia::ui::scenic::SessionListener|
void OnScenicEvent(std::vector<fuchsia::ui::scenic::Event> events) override {
FAIL();
}
GfxSessionConnection CreateSessionConnection() {
FML_CHECK(!fake_session_.is_bound());
FML_CHECK(!session_listener_.is_bound());
inspect::Node inspect_node =
inspector_.GetRoot().CreateChild("GfxExternalViewEmbedderTest");
auto [session, session_listener] =
fake_session_.Bind(session_subloop_->dispatcher());
session_listener_.Bind(std::move(session_listener));
return GfxSessionConnection(
GetCurrentTestName(), std::move(inspect_node), std::move(session),
[]() { FAIL(); }, [](auto...) {}, 1, fml::TimeDelta::Zero());
}
async::TestLoop loop_; // Must come before FIDL bindings.
inspect::Inspector inspector_;
fidl::Binding<fuchsia::ui::scenic::SessionListener> session_listener_;
std::unique_ptr<async::LoopInterface> session_subloop_;
FakeSession fake_session_;
GfxSessionConnection session_connection_;
FakeSurfaceProducer fake_surface_producer_;
};
TEST_F(GfxExternalViewEmbedderTest, RootScene) {
const std::string debug_name = GetCurrentTestName();
auto [view_token, view_holder_token] = scenic::ViewTokenPair::New();
auto view_ref_pair = scenic::ViewRefPair::New();
fuchsia::ui::views::ViewRef view_ref;
view_ref_pair.view_ref.Clone(&view_ref);
GfxExternalViewEmbedder external_view_embedder(
debug_name, std::move(view_token), std::move(view_ref_pair),
session_connection(), fake_surface_producer());
EXPECT_EQ(fake_session().debug_name(), "");
EXPECT_THAT(fake_session().SceneGraph(), IsEmptySceneGraph());
// Pump the loop; the contents of the initial `Present` should be processed.
loop().RunUntilIdle();
EXPECT_EQ(fake_session().debug_name(), debug_name);
ExpectRootSceneGraph(fake_session().SceneGraph(), debug_name,
view_holder_token, view_ref);
// Fire the `OnFramePresented` event associated with the first `Present`, then
// pump the loop. The `OnFramePresented` event is resolved.
//
// The scene graph shouldn't change.
fake_session().FireOnFramePresentedEvent(
MakeFramePresentedInfoForOnePresent(0, 0));
loop().RunUntilIdle();
ExpectRootSceneGraph(fake_session().SceneGraph(), debug_name,
view_holder_token, view_ref);
}
TEST_F(GfxExternalViewEmbedderTest, SimpleScene) {
const std::string debug_name = GetCurrentTestName();
auto [view_token, view_holder_token] = scenic::ViewTokenPair::New();
auto view_ref_pair = scenic::ViewRefPair::New();
fuchsia::ui::views::ViewRef view_ref;
view_ref_pair.view_ref.Clone(&view_ref);
// Create the `GfxExternalViewEmbedder` and pump the message loop until
// the initial scene graph is setup.
GfxExternalViewEmbedder external_view_embedder(
debug_name, std::move(view_token), std::move(view_ref_pair),
session_connection(), fake_surface_producer());
loop().RunUntilIdle();
fake_session().FireOnFramePresentedEvent(
MakeFramePresentedInfoForOnePresent(0, 0));
loop().RunUntilIdle();
ExpectRootSceneGraph(fake_session().SceneGraph(), debug_name,
view_holder_token, view_ref);
// Draw the scene. The scene graph shouldn't change yet.
const SkISize frame_size = SkISize::Make(512, 512);
DrawSimpleFrame(
external_view_embedder, frame_size, 1.f, [](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);
});
ExpectRootSceneGraph(fake_session().SceneGraph(), debug_name,
view_holder_token, view_ref);
// Pump the message loop. The scene updates should propogate to Scenic.
loop().RunUntilIdle();
std::vector<FakeCompositorLayer> compositor_layers =
ExtractLayersFromSceneGraph(fake_session().SceneGraph());
EXPECT_EQ(compositor_layers.size(), 1u);
ExpectImageCompositorLayer(compositor_layers[0], frame_size);
}
TEST_F(GfxExternalViewEmbedderTest, SceneWithOneView) {
const std::string debug_name = GetCurrentTestName();
auto [view_token, view_holder_token] = scenic::ViewTokenPair::New();
auto view_ref_pair = scenic::ViewRefPair::New();
fuchsia::ui::views::ViewRef view_ref;
view_ref_pair.view_ref.Clone(&view_ref);
// Create the `GfxExternalViewEmbedder` and pump the message loop until
// the initial scene graph is setup.
GfxExternalViewEmbedder external_view_embedder(
debug_name, std::move(view_token), std::move(view_ref_pair),
session_connection(), fake_surface_producer());
loop().RunUntilIdle();
fake_session().FireOnFramePresentedEvent(
MakeFramePresentedInfoForOnePresent(0, 0));
loop().RunUntilIdle();
ExpectRootSceneGraph(fake_session().SceneGraph(), debug_name,
view_holder_token, view_ref);
// Create the view before drawing the scene.
const SkSize child_view_size = SkSize::Make(256.f, 512.f);
auto [child_view_token, child_view_holder_token] =
scenic::ViewTokenPair::New();
const uint32_t child_view_id = child_view_holder_token.value.get();
flutter::EmbeddedViewParams child_view_params(SkMatrix::I(), child_view_size,
flutter::MutatorsStack());
external_view_embedder.CreateView(
child_view_id, []() {}, [](scenic::ResourceId) {});
// Draw the scene. The scene graph shouldn't change yet.
const SkISize frame_size = SkISize::Make(512, 512);
DrawFrameWithView(
external_view_embedder, frame_size, 1.f, 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);
});
ExpectRootSceneGraph(fake_session().SceneGraph(), debug_name,
view_holder_token, view_ref);
// Pump the message loop. The scene updates should propagate to Scenic.
loop().RunUntilIdle();
std::vector<FakeCompositorLayer> compositor_layers =
ExtractLayersFromSceneGraph(fake_session().SceneGraph());
EXPECT_EQ(compositor_layers.size(), 3u);
ExpectImageCompositorLayer(compositor_layers[0], frame_size);
ExpectViewCompositorLayer(compositor_layers[1], child_view_token,
child_view_params);
ExpectImageCompositorLayer(compositor_layers[2], frame_size);
// Destroy the view.
external_view_embedder.DestroyView(child_view_id, [](scenic::ResourceId) {});
// Pump the message loop.
loop().RunUntilIdle();
}
} // namespace flutter_runner::testing