blob: dc43e77b8aa08aef5e9b3c26f05e7663a8ad0985 [file] [log] [blame]
// Copyright 2022 The Fuchsia 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 <fuchsia/ui/annotation/cpp/fidl.h>
#include <fuchsia/ui/app/cpp/fidl.h>
#include <fuchsia/ui/gfx/cpp/fidl.h>
#include <fuchsia/ui/policy/cpp/fidl.h>
#include <fuchsia/ui/scenic/cpp/fidl.h>
#include <fuchsia/ui/views/cpp/fidl.h>
#include <lib/sys/component/cpp/testing/realm_builder.h>
#include <lib/syslog/cpp/macros.h>
#include <lib/ui/scenic/cpp/resources.h>
#include <lib/ui/scenic/cpp/session.h>
#include <lib/ui/scenic/cpp/view_ref_pair.h>
#include <lib/ui/scenic/cpp/view_token_pair.h>
#include <zircon/status.h>
#include <gtest/gtest.h>
#include "src/lib/testing/loop_fixture/real_loop_fixture.h"
#include "src/ui/scenic/tests/gfx_integration_tests/pixel_test.h"
#include "src/ui/scenic/tests/utils/scenic_realm_builder.h"
namespace integration_tests {
using RealmRoot = component_testing::RealmRoot;
class ViewEmbedderTest : public PixelTest {
private:
RealmRoot SetupRealm() {
return ScenicRealmBuilder()
.AddRealmProtocol(fuchsia::ui::scenic::Scenic::Name_)
.AddRealmProtocol(fuchsia::ui::annotation::Registry::Name_)
.Build();
}
};
// Initialize two sessions and their associated views, and ensure that killing the embedded
// session triggers a ViewDisconnected event to the holding one.
TEST_F(ViewEmbedderTest, DeadBindingShouldKillSession) {
// Initialize session 1.
auto test_session = std::make_unique<RootSession>(scenic(), GetDisplayDimensions());
test_session->session.set_error_handler([](auto) { FAIL() << "Session terminated."; });
scenic::Session* const session = &test_session->session;
const auto [display_width, display_height] = test_session->display_dimensions;
scenic::Scene* const scene = &test_session->scene;
test_session->SetUpCamera().SetProjection(0);
// Initialize session 2.
auto unique_session2 = std::make_unique<scenic::Session>(scenic());
auto session2 = unique_session2.get();
session2->set_error_handler([this](zx_status_t status) {
FX_LOGS(INFO) << "Session2 terminated.";
QuitLoop();
});
auto [view_token, view_holder_token] = scenic::ViewTokenPair::New();
auto [view_token2, view_holder_token2] = scenic::ViewTokenPair::New();
scenic::View view(session, std::move(view_token), "ClipView");
scenic::ViewHolder view_holder(session, std::move(view_holder_token), "ClipViewHolder");
// View 2 is embedded by view 1.
scenic::View view2(session2, std::move(view_token2), "ClipView2");
scenic::ViewHolder view_holder2(session, std::move(view_holder_token2), "ClipViewHolder2");
scene->AddChild(view_holder);
// Transform and embed view holder 2 in first view.
scenic::EntityNode transform_node(session);
transform_node.SetTranslation(display_width / 2, 0, 0);
view.AddChild(transform_node);
transform_node.AddChild(view_holder2);
// Ensure that view2 connects to view1.
bool view_connected_observed = false;
bool view2_connected_observed = false;
session->set_event_handler([&](std::vector<fuchsia::ui::scenic::Event> events) {
for (const auto& event : events) {
if (event.Which() == fuchsia::ui::scenic::Event::Tag::kGfx &&
event.gfx().Which() == fuchsia::ui::gfx::Event::Tag::kViewConnected) {
if (view_holder.id() == event.gfx().view_connected().view_holder_id) {
view_connected_observed = true;
} else if (view_holder2.id() == event.gfx().view_connected().view_holder_id) {
view2_connected_observed = true;
}
return;
}
}
});
Present(session);
Present(session2);
EXPECT_TRUE(RunLoopWithTimeoutOrUntil(
[&]() { return view_connected_observed && view2_connected_observed; }));
// Crash Session2 by submitting an invalid release resource command.
session2->AllocResourceId();
session2->ReleaseResource(session2->next_resource_id() + 1);
bool view_disconnected_observed = false;
session->set_event_handler(
[&view_disconnected_observed](std::vector<fuchsia::ui::scenic::Event> events) {
for (const auto& event : events) {
if (event.Which() == fuchsia::ui::scenic::Event::Tag::kGfx &&
event.gfx().Which() == fuchsia::ui::gfx::Event::Tag::kViewDisconnected) {
view_disconnected_observed = true;
return;
}
}
ASSERT_FALSE(true);
});
// Observe results.
Present(session2);
Present(session);
EXPECT_TRUE(RunLoopWithTimeoutOrUntil(
[&view_disconnected_observed]() { return view_disconnected_observed; }));
}
// When annotation View and annotation ViewHolder are created within the same
// frame (i.e. the same SessionUpdate() call), we need to ensure that they are
// created in the correct order.
//
// ViewTree update of annotation ViewHolder should be created earlier before
// annotation View, since the update of latter one refers to the ViewHolder
// in ViewTree. Otherwise it will trigger a DCHECK() within ViewTree and lead
// to a bad tree state.
TEST_F(ViewEmbedderTest, AnnotationViewAndViewHolderInSingleFrame) {
auto test_session = std::make_unique<RootSession>(scenic(), GetDisplayDimensions());
test_session->session.set_error_handler([](auto) { FAIL() << "Session terminated."; });
scenic::Session* const session = &test_session->session;
const auto [display_width, display_height] = test_session->display_dimensions;
// Initialize second session
auto unique_session_view = std::make_unique<scenic::Session>(scenic());
auto unique_session_annotation = std::make_unique<scenic::Session>(scenic());
auto session_view = unique_session_view.get();
auto session_annotation = unique_session_annotation.get();
session_view->set_error_handler([this](zx_status_t status) {
FX_LOGS(ERROR) << "Session terminated.";
FAIL();
QuitLoop();
});
session_annotation->set_error_handler([this](zx_status_t status) {
FX_LOGS(ERROR) << "Annotation Session terminated.";
FAIL();
QuitLoop();
});
test_session->SetUpCamera().SetProjection(0);
scenic::EntityNode entity_node(session);
entity_node.SetTranslation(0, 0, 0);
test_session->scene.AddChild(entity_node);
// Create two sets of view/view-holder token pairs.
auto [view_token, view_holder_token] = scenic::ViewTokenPair::New();
auto [view_control_ref, view_ref] = scenic::ViewRefPair::New();
auto [view_token_annotation, view_holder_token_annotation] = scenic::ViewTokenPair::New();
fuchsia::ui::views::ViewRef view_ref_create;
view_ref.Clone(&view_ref_create);
scenic::View view(session_view, std::move(view_token), std::move(view_control_ref),
std::move(view_ref_create), "View");
scenic::View view_annotation(session_annotation, std::move(view_token_annotation),
"View Annotation");
scenic::ViewHolder view_holder(session, std::move(view_holder_token), "ViewHolder");
// Bounds of each view should be the size of a quarter of the display with
// origin at 0,0 relative to its transform node.
const std::array<float, 3> bmin = {0.f, 0.f, -2.f};
const std::array<float, 3> bmax = {display_width, display_height / 2, 1.f};
const std::array<float, 3> imin = {0, 0, 0};
const std::array<float, 3> imax = {0, 0, 0};
view_holder.SetViewProperties(bmin, bmax, imin, imax);
view_holder.SetTranslation(0, display_height / 2, 0);
// Pane extends across the entire right-side of the display, even though
// its containing view is only in the top-right corner.
auto pane_width = display_width;
auto pane_height = display_height / 2;
scenic::Rectangle pane_shape2(session_view, pane_width / 2, pane_height);
scenic::Rectangle pane_shape_annotation(session_annotation, pane_width / 2, pane_height);
// Create pane materials.
scenic::Material pane_material_view(session_view);
scenic::Material pane_material_annotation(session_annotation);
pane_material_view.SetColor(0, 0, 255, 255); // Blue
pane_material_annotation.SetColor(0, 255, 0, 255); // Green
scenic::ShapeNode pane_node(session_view);
pane_node.SetShape(pane_shape2);
pane_node.SetMaterial(pane_material_view);
pane_node.SetTranslation(pane_width / 4, pane_height / 2, 0);
scenic::ShapeNode pane_node_annotation(session_annotation);
pane_node_annotation.SetShape(pane_shape_annotation);
pane_node_annotation.SetMaterial(pane_material_annotation);
pane_node_annotation.SetTranslation(pane_width * 3 / 4, pane_height / 2, 0);
// Add view holders to the transform.
entity_node.AddChild(view_holder);
view.AddChild(pane_node);
view_annotation.AddChild(pane_node_annotation);
Present(session);
Present(session_view);
RunLoopWithTimeout(zx::msec(100));
// In this way we'll trigger the annotation ViewHolder creation and
// annotation View creation in the same UpdateSessions() call and we
// should ensure that there is no error nor any gfx crash.
bool view_holder_annotation_created = false;
fuchsia::ui::views::ViewRef view_ref_annotation;
view_ref.Clone(&view_ref_annotation);
annotation_registry()->CreateAnnotationViewHolder(
std::move(view_ref_annotation), std::move(view_holder_token_annotation),
[&view_holder_annotation_created]() { view_holder_annotation_created = true; });
EXPECT_FALSE(view_holder_annotation_created);
session_view->Present(zx::time(0), [this](auto) { QuitLoop(); });
session_annotation->Present(zx::time(0), [this](auto) { QuitLoop(); });
RunLoopWithTimeout(zx::msec(100));
EXPECT_TRUE(view_holder_annotation_created);
}
} // namespace integration_tests