blob: fc0dbe814f0ae494ef32ab85f80841ea63386793 [file] [log] [blame]
// Copyright 2018 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 "src/ui/scenic/lib/gfx/resources/view.h"
#include <lib/async/cpp/task.h>
#include <lib/ui/scenic/cpp/commands.h>
#include <lib/ui/scenic/cpp/view_token_pair.h>
#include <lib/zx/eventpair.h>
#include "src/ui/scenic/lib/gfx/resources/nodes/entity_node.h"
#include "src/ui/scenic/lib/gfx/resources/nodes/view_node.h"
#include "src/ui/scenic/lib/gfx/resources/view_holder.h"
#include "src/ui/scenic/lib/gfx/tests/mocks/util.h"
#include "src/ui/scenic/lib/gfx/tests/session_test.h"
namespace scenic_impl {
namespace gfx {
namespace test {
namespace {
ViewHolderPtr NewAnnotationViewHolder(
Session* session, ViewLinker* view_linker,
fuchsia::ui::views::ViewHolderToken annotation_view_holder_token) {
std::ostringstream annotation_debug_name;
annotation_debug_name << "Annotation ViewHolder [Test]";
ViewHolderPtr annotation_view_holder = fxl::MakeRefCounted<ViewHolder>(
session, session->id(), /* node_id */ 0U,
/* is_annotation */ true, annotation_debug_name.str(), session->shared_error_reporter(),
session->view_tree_updater());
// Set hit test behavior to kSuppress so it will suppress all hit testings.
annotation_view_holder->SetHitTestBehavior(fuchsia::ui::gfx::HitTestBehavior::kSuppress);
// Set up link with annotation View.
ViewLinker::ExportLink link = view_linker->CreateExport(
annotation_view_holder.get(), std::move(annotation_view_holder_token.value),
session->error_reporter());
FXL_CHECK(link.valid()) << "Cannot setup link with annotation View!";
annotation_view_holder->Connect(std::move(link));
return annotation_view_holder;
}
void VerifyViewState(const fuchsia::ui::scenic::Event& event, bool is_rendering_expected) {
EXPECT_EQ(fuchsia::ui::scenic::Event::Tag::kGfx, event.Which());
EXPECT_EQ(::fuchsia::ui::gfx::Event::Tag::kViewStateChanged, event.gfx().Which());
const ::fuchsia::ui::gfx::ViewState& view_state = event.gfx().view_state_changed().state;
EXPECT_EQ(is_rendering_expected, view_state.is_rendering);
}
} // namespace
class ViewTest : public SessionTest {
public:
ViewTest() {}
void TearDown() override {
SessionTest::TearDown();
view_linker_.reset();
}
SessionContext CreateSessionContext() override {
SessionContext session_context = SessionTest::CreateSessionContext();
FXL_DCHECK(!view_linker_);
view_linker_ = std::make_unique<ViewLinker>();
session_context.view_linker = view_linker_.get();
return session_context;
}
std::unique_ptr<ViewLinker> view_linker_;
};
// TODO(ES-179): Only seems to die in debug builds.
TEST_F(ViewTest, DISABLED_CreateViewWithBadTokenDies) {
EXPECT_DEATH_IF_SUPPORTED(Apply(scenic::NewCreateViewCmd(1, fuchsia::ui::views::ViewToken(), "")),
"");
EXPECT_DEATH_IF_SUPPORTED(
Apply(scenic::NewCreateViewHolderCmd(2, fuchsia::ui::views::ViewHolderToken(), "")), "");
}
TEST_F(ViewTest, NullableDebugName) {
auto [view_token, view_holder_token] = scenic::ViewTokenPair::New();
constexpr ResourceId kViewHolderId = 1u;
EXPECT_TRUE(Apply(
scenic::NewCreateViewHolderCmd(kViewHolderId, std::move(view_holder_token), fit::nullopt)));
constexpr ResourceId kViewId = 2u;
EXPECT_TRUE(Apply(scenic::NewCreateViewCmd(kViewId, std::move(view_token), fit::nullopt)));
EXPECT_SCENIC_SESSION_ERROR_COUNT(0);
}
TEST_F(ViewTest, ChildrenCanBeAddedToViewWithoutViewHolder) {
auto [view_token, view_holder_token] = scenic::ViewTokenPair::New();
const ResourceId view_id = 1;
EXPECT_TRUE(Apply(scenic::NewCreateViewCmd(view_id, std::move(view_token), "Test")));
EXPECT_SCENIC_SESSION_ERROR_COUNT(0);
const ResourceId node1_id = 2;
EXPECT_TRUE(Apply(scenic::NewCreateEntityNodeCmd(node1_id)));
EXPECT_SCENIC_SESSION_ERROR_COUNT(0);
const ResourceId node2_id = 3;
EXPECT_TRUE(Apply(scenic::NewCreateEntityNodeCmd(node2_id)));
EXPECT_SCENIC_SESSION_ERROR_COUNT(0);
auto view = FindResource<View>(view_id);
auto node1 = FindResource<Node>(node1_id);
auto node2 = FindResource<Node>(node2_id);
EXPECT_TRUE(view);
EXPECT_TRUE(node1);
EXPECT_TRUE(node2);
EXPECT_TRUE(Apply(scenic::NewAddChildCmd(view_id, node1_id)));
EXPECT_TRUE(Apply(scenic::NewAddChildCmd(view_id, node2_id)));
EXPECT_SCENIC_SESSION_ERROR_COUNT(0);
}
TEST_F(ViewTest, ExportsViewHolderViaCmd) {
auto [view_token, view_holder_token] = scenic::ViewTokenPair::New();
const ResourceId view_holder_id = 1;
EXPECT_TRUE(
Apply(scenic::NewCreateViewHolderCmd(view_holder_id, std::move(view_holder_token), "Test")));
EXPECT_SCENIC_SESSION_ERROR_COUNT(0);
auto view_holder = FindResource<ViewHolder>(view_holder_id);
EXPECT_TRUE(view_holder);
EXPECT_EQ(nullptr, view_holder->view());
EXPECT_EQ(1u, session()->GetMappedResourceCount());
EXPECT_EQ(1u, view_linker_->ExportCount());
EXPECT_EQ(1u, view_linker_->UnresolvedExportCount());
EXPECT_EQ(0u, view_linker_->ImportCount());
EXPECT_EQ(0u, view_linker_->UnresolvedImportCount());
}
TEST_F(ViewTest, ImportsViewViaCmd) {
auto [view_token, view_holder_token] = scenic::ViewTokenPair::New();
const ResourceId view_id = 1;
EXPECT_TRUE(Apply(scenic::NewCreateViewCmd(view_id, std::move(view_token), "Test")));
EXPECT_SCENIC_SESSION_ERROR_COUNT(0);
auto view = FindResource<View>(view_id);
EXPECT_TRUE(view);
EXPECT_EQ(nullptr, view->view_holder());
EXPECT_EQ(1u, session()->GetMappedResourceCount());
EXPECT_EQ(0u, view_linker_->ExportCount());
EXPECT_EQ(0u, view_linker_->UnresolvedExportCount());
EXPECT_EQ(1u, view_linker_->ImportCount());
EXPECT_EQ(1u, view_linker_->UnresolvedImportCount());
}
TEST_F(ViewTest, PairedViewAndHolderAreLinked) {
auto [view_token, view_holder_token] = scenic::ViewTokenPair::New();
const ResourceId view_holder_id = 1u;
EXPECT_TRUE(Apply(scenic::NewCreateViewHolderCmd(view_holder_id, std::move(view_holder_token),
"Holder [Test]")));
EXPECT_SCENIC_SESSION_ERROR_COUNT(0);
auto view_holder = FindResource<ViewHolder>(view_holder_id);
EXPECT_TRUE(view_holder);
EXPECT_EQ(nullptr, view_holder->view());
EXPECT_EQ(1u, session()->GetMappedResourceCount());
EXPECT_EQ(1u, view_linker_->ExportCount());
EXPECT_EQ(1u, view_linker_->UnresolvedExportCount());
EXPECT_EQ(0u, view_linker_->ImportCount());
EXPECT_EQ(0u, view_linker_->UnresolvedImportCount());
const ResourceId view_id = 2u;
EXPECT_TRUE(Apply(scenic::NewCreateViewCmd(view_id, std::move(view_token), "Test")));
EXPECT_SCENIC_SESSION_ERROR_COUNT(0);
auto view = FindResource<View>(view_id);
EXPECT_TRUE(view);
EXPECT_EQ(view.get(), view_holder->view());
EXPECT_EQ(view_holder.get(), view->view_holder());
EXPECT_EQ(2u, session()->GetMappedResourceCount());
EXPECT_EQ(1u, view_linker_->ExportCount());
EXPECT_EQ(0u, view_linker_->UnresolvedExportCount());
EXPECT_EQ(1u, view_linker_->ImportCount());
EXPECT_EQ(0u, view_linker_->UnresolvedImportCount());
EXPECT_NE(0u, events().size());
const fuchsia::ui::scenic::Event& event = events()[0];
EXPECT_EQ(::fuchsia::ui::gfx::Event::Tag::kViewConnected, event.gfx().Which());
}
TEST_F(ViewTest, ExportViewHolderWithDeadHandleFails) {
fuchsia::ui::views::ViewHolderToken view_holder_token_out;
{
auto [view_token, view_holder_token] = scenic::ViewTokenPair::New();
view_holder_token_out.value = zx::eventpair(view_holder_token.value.get());
// view_holder_token dies now.
}
const ResourceId view_holder_id = 1;
EXPECT_FALSE(Apply(
scenic::NewCreateViewHolderCmd(view_holder_id, std::move(view_holder_token_out), "Test")));
EXPECT_SCENIC_SESSION_ERROR_COUNT(1); // Dead handles cause a session error.
auto view_holder = FindResource<ViewHolder>(view_holder_id);
EXPECT_FALSE(view_holder);
EXPECT_EQ(0u, session()->GetMappedResourceCount());
EXPECT_EQ(0u, view_linker_->ExportCount());
EXPECT_EQ(0u, view_linker_->UnresolvedExportCount());
EXPECT_EQ(0u, view_linker_->ImportCount());
EXPECT_EQ(0u, view_linker_->UnresolvedImportCount());
}
TEST_F(ViewTest, ViewHolderDestroyedBeforeView) {
auto [view_token, view_holder_token] = scenic::ViewTokenPair::New();
const ResourceId view_holder_id = 1u;
Apply(scenic::NewCreateViewHolderCmd(view_holder_id, std::move(view_holder_token),
"Holder [Test]"));
const ResourceId view_id = 2u;
Apply(scenic::NewCreateViewCmd(view_id, std::move(view_token), "Test"));
uint32_t next_event_id = events().size();
// Destroy the ViewHolder and disconnect the link.
Apply(scenic::NewReleaseResourceCmd(view_holder_id));
EXPECT_SCENIC_SESSION_ERROR_COUNT(0);
const fuchsia::ui::scenic::Event& event = events()[next_event_id];
EXPECT_EQ(fuchsia::ui::gfx::Event::Tag::kViewHolderDisconnected, event.gfx().Which());
}
TEST_F(ViewTest, ViewDestroyedBeforeViewHolder) {
auto [view_token, view_holder_token] = scenic::ViewTokenPair::New();
const ResourceId view_holder_id = 1u;
Apply(scenic::NewCreateViewHolderCmd(view_holder_id, std::move(view_holder_token),
"Holder [Test]"));
const ResourceId view_id = 2u;
Apply(scenic::NewCreateViewCmd(view_id, std::move(view_token), "Test"));
uint32_t next_event_id = events().size();
// Destroy the ViewHolder and disconnect the link.
Apply(scenic::NewReleaseResourceCmd(view_id));
EXPECT_SCENIC_SESSION_ERROR_COUNT(0);
const fuchsia::ui::scenic::Event& event = events()[next_event_id];
EXPECT_EQ(fuchsia::ui::gfx::Event::Tag::kViewDisconnected, event.gfx().Which());
}
TEST_F(ViewTest, ViewAndViewHolderConnectedEvents) {
auto [view_token, view_holder_token] = scenic::ViewTokenPair::New();
const ResourceId view_holder_id = 1u;
Apply(scenic::NewCreateViewHolderCmd(view_holder_id, std::move(view_holder_token),
"Holder [Test]"));
const ResourceId view_id = 2u;
Apply(scenic::NewCreateViewCmd(view_id, std::move(view_token), "Test"));
EXPECT_SCENIC_SESSION_ERROR_COUNT(0);
bool view_holder_connected_event = false;
bool view_connected_event = false;
for (const fuchsia::ui::scenic::Event& event : events()) {
if (event.gfx().Which() == fuchsia::ui::gfx::Event::Tag::kViewHolderConnected) {
view_holder_connected_event = true;
} else if (event.gfx().Which() == fuchsia::ui::gfx::Event::Tag::kViewConnected) {
view_connected_event = true;
}
}
EXPECT_TRUE(view_holder_connected_event);
EXPECT_TRUE(view_connected_event);
}
TEST_F(ViewTest, ViewHolderConnectsToScene) {
auto [view_token, view_holder_token] = scenic::ViewTokenPair::New();
const ResourceId view_holder_id = 1u;
Apply(scenic::NewCreateViewHolderCmd(view_holder_id, std::move(view_holder_token),
"Holder [Test]"));
const ResourceId view_id = 2u;
Apply(scenic::NewCreateViewCmd(view_id, std::move(view_token), "Test"));
EXPECT_SCENIC_SESSION_ERROR_COUNT(0);
auto view_holder = FindResource<ViewHolder>(view_holder_id);
auto view = FindResource<View>(view_id);
uint32_t next_event_id = events().size();
// Create a Scene and connect the ViewHolder to the Scene.
const ResourceId scene_id = 3u;
Apply(scenic::NewCreateSceneCmd(scene_id));
auto scene = FindResource<Scene>(scene_id);
EXPECT_TRUE(scene);
Apply(scenic::NewAddChildCmd(scene_id, view_holder_id));
// Verify the scene was successfully set.
const fuchsia::ui::scenic::Event& event = events()[next_event_id];
EXPECT_EQ(::fuchsia::ui::gfx::Event::Tag::kViewAttachedToScene, event.gfx().Which());
}
TEST_F(ViewTest, ViewHolderDetachedAndReleased) {
// Create ViewHolder and View.
auto [view_token, view_holder_token] = scenic::ViewTokenPair::New();
const ResourceId view_holder_id = 1u;
Apply(scenic::NewCreateViewHolderCmd(view_holder_id, std::move(view_holder_token),
"Holder [Test]"));
const ResourceId view_id = 2u;
Apply(scenic::NewCreateViewCmd(view_id, std::move(view_token), "Test"));
EXPECT_SCENIC_SESSION_ERROR_COUNT(0);
auto view = FindResource<View>(view_id);
// Create a Scene and connect the ViewHolder to the Scene.
const ResourceId scene_id = 3u;
Apply(scenic::NewCreateSceneCmd(scene_id));
auto scene = FindResource<Scene>(scene_id);
EXPECT_TRUE(scene);
EXPECT_TRUE(Apply(scenic::NewAddChildCmd(scene_id, view_holder_id)));
// Create child node for the View.
const ResourceId node1_id = 4u;
Apply(scenic::NewCreateEntityNodeCmd(node1_id));
EXPECT_TRUE(Apply(scenic::NewAddChildCmd(view_id, node1_id)));
auto node1 = FindResource<Node>(node1_id);
EXPECT_TRUE(node1);
auto view_node = view->GetViewNode();
EXPECT_EQ(1u, view_node->children().size());
EXPECT_SCENIC_SESSION_ERROR_COUNT(0);
// Detach the ViewHolder from the scene graph.
EXPECT_TRUE(Apply(scenic::NewDetachCmd(view_holder_id)));
{
auto view_holder = FindResource<ViewHolder>(view_holder_id);
// The view holder is still in the ResourceMap so it should still be
// connected to the view.
EXPECT_EQ(1u, view_holder->children().size());
// The view is detached from the scene but still attached to the ViewHolder.
bool detached_from_scene_event = false;
for (const fuchsia::ui::scenic::Event& event : events()) {
detached_from_scene_event |=
(event.gfx().Which() == fuchsia::ui::gfx::Event::Tag::kViewDetachedFromScene);
}
EXPECT_TRUE(detached_from_scene_event);
} // view_holder out of scope, release reference.
// Now, release the ViewHolder resource. Its link should be destroyed.
uint32_t next_event_id = events().size();
EXPECT_TRUE(Apply(scenic::NewReleaseResourceCmd(view_holder_id)));
EXPECT_SCENIC_SESSION_ERROR_COUNT(0);
bool view_holder_disconnected_event = false;
for (uint32_t i = next_event_id; i < events().size(); ++i) {
if (events()[i].gfx().Which() == fuchsia::ui::gfx::Event::Tag::kViewHolderDisconnected) {
view_holder_disconnected_event = true;
break;
}
}
EXPECT_TRUE(view_holder_disconnected_event);
// The View's subtree should still be attached to the ViewNode.
EXPECT_EQ(1u, view_node->children().size());
EXPECT_FALSE(view_node->parent());
}
TEST_F(ViewTest, ViewHolderChildrenReleasedFromSceneGraphWhenViewDestroyed) {
// Create ViewHolder and View.
auto [view_token, view_holder_token] = scenic::ViewTokenPair::New();
const ResourceId view_holder_id = 1u;
Apply(scenic::NewCreateViewHolderCmd(view_holder_id, std::move(view_holder_token),
"Holder [Test]"));
const ResourceId view_id = 2u;
Apply(scenic::NewCreateViewCmd(view_id, std::move(view_token), "Test"));
EXPECT_SCENIC_SESSION_ERROR_COUNT(0);
auto view_holder = FindResource<ViewHolder>(view_holder_id);
auto view = FindResource<View>(view_id);
// Create child nodes for the View.
const ResourceId node1_id = 3u;
EXPECT_TRUE(Apply(scenic::NewCreateEntityNodeCmd(node1_id)));
const ResourceId node2_id = 4u;
EXPECT_TRUE(Apply(scenic::NewCreateEntityNodeCmd(node2_id)));
// Add children
EXPECT_TRUE(Apply(scenic::NewAddChildCmd(view_id, node1_id)));
EXPECT_TRUE(Apply(scenic::NewAddChildCmd(view_id, node2_id)));
view = FindResource<View>(view_id);
auto node1 = FindResource<Node>(node1_id);
auto node2 = FindResource<Node>(node2_id);
EXPECT_TRUE(view);
EXPECT_TRUE(node1);
EXPECT_TRUE(node2);
// Release the View
Apply(scenic::NewReleaseResourceCmd(view_id));
view = FindResource<View>(view_id);
node1 = FindResource<Node>(node1_id);
node2 = FindResource<Node>(node2_id);
EXPECT_FALSE(view);
// The child nodes are still part of the ResourcMap, and should not be
// destroyed.
EXPECT_TRUE(node1);
EXPECT_TRUE(node2);
// The nodes should not be parented.
EXPECT_FALSE(node1->parent());
EXPECT_FALSE(node1->scene());
EXPECT_FALSE(node2->parent());
// The view holder should not have any children.
EXPECT_EQ(0u, view_holder->children().size());
}
TEST_F(ViewTest, ViewNodeChildAddedToViewHolder) {
// Create ViewHolder and View.
auto [view_token, view_holder_token] = scenic::ViewTokenPair::New();
const ResourceId view_holder_id = 1u;
Apply(scenic::NewCreateViewHolderCmd(view_holder_id, std::move(view_holder_token),
"Holder [Test]"));
const ResourceId view_id = 2u;
Apply(scenic::NewCreateViewCmd(view_id, std::move(view_token), "Test"));
EXPECT_SCENIC_SESSION_ERROR_COUNT(0);
auto view_holder = FindResource<ViewHolder>(view_holder_id);
auto view = FindResource<View>(view_id);
auto view_node = view->GetViewNode();
EXPECT_TRUE(view->GetViewNode());
EXPECT_EQ(1u, view_holder->children().size());
EXPECT_EQ(view_node->global_id(), view_holder->children()[0]->global_id());
}
TEST_F(ViewTest, ViewHolderCannotAddArbitraryChildNodes) {
// Create ViewHolder.
auto [view_token, view_holder_token] = scenic::ViewTokenPair::New();
const ResourceId view_holder_id = 1u;
Apply(scenic::NewCreateViewHolderCmd(view_holder_id, std::move(view_holder_token),
"Holder [Test]"));
// Create an EntityNode.
const ResourceId node_id = 2u;
EXPECT_TRUE(Apply(scenic::NewCreateEntityNodeCmd(node_id)));
EXPECT_SCENIC_SESSION_ERROR_COUNT(0);
// Attempt to add the node as a child of the ViewHolder.
EXPECT_FALSE(Apply(scenic::NewAddChildCmd(view_holder_id, node_id)));
EXPECT_SCENIC_SESSION_ERROR_COUNT(1);
}
TEST_F(ViewTest, ViewNodePairedToView) {
// Create View.
auto [view_token, view_holder_token] = scenic::ViewTokenPair::New();
const ResourceId view_holder_id = 1u;
Apply(scenic::NewCreateViewHolderCmd(view_holder_id, std::move(view_holder_token),
"Holder [Test]"));
const ResourceId view_id = 2u;
Apply(scenic::NewCreateViewCmd(view_id, std::move(view_token), "Test"));
EXPECT_SCENIC_SESSION_ERROR_COUNT(0);
auto view = FindResource<View>(view_id);
auto view_node = view->GetViewNode();
EXPECT_NE(nullptr, view_node);
EXPECT_EQ(view->global_id(), view_node->GetView()->global_id());
EXPECT_EQ(view->id(), view_node->GetView()->id());
EXPECT_EQ(view->global_id(), view_node->FindOwningView()->global_id());
}
TEST_F(ViewTest, ViewNodeNotInResourceMap) {
// Create ViewHolder and View.
auto [view_token, view_holder_token] = scenic::ViewTokenPair::New();
const ResourceId view_holder_id = 1u;
Apply(scenic::NewCreateViewHolderCmd(view_holder_id, std::move(view_holder_token),
"Holder [Test]"));
const ResourceId view_id = 2u;
Apply(scenic::NewCreateViewCmd(view_id, std::move(view_token), "Test"));
EXPECT_SCENIC_SESSION_ERROR_COUNT(0);
auto view_holder = FindResource<ViewHolder>(view_holder_id);
auto view = FindResource<View>(view_id);
EXPECT_NE(nullptr, view->GetViewNode());
EXPECT_EQ(nullptr, FindResource<ViewNode>(view->GetViewNode()->id()).get());
EXPECT_SCENIC_SESSION_ERROR_COUNT(1);
}
TEST_F(ViewTest, ViewHolderGrandchildGetsSceneRefreshed) {
auto [view_token, view_holder_token] = scenic::ViewTokenPair::New();
const ResourceId kViewHolderId = 1u;
Apply(scenic::NewCreateViewHolderCmd(kViewHolderId, std::move(view_holder_token), "ViewHolder"));
const ResourceId kViewId = 2u;
Apply(scenic::NewCreateViewCmd(kViewId, std::move(view_token), "View"));
// Create a parent node for the ViewHolder.
const ResourceId kEntityNodeId = 3u;
Apply(scenic::NewCreateEntityNodeCmd(kEntityNodeId));
Apply(scenic::NewAddChildCmd(kEntityNodeId, kViewHolderId));
// Create a scene node.
const ResourceId kSceneId = 4u;
Apply(scenic::NewCreateSceneCmd(kSceneId));
auto scene = FindResource<Scene>(kSceneId);
EXPECT_SCENIC_SESSION_ERROR_COUNT(0);
// Set the ViewHolder's parent as the child of the scene.
Apply(scenic::NewAddChildCmd(kSceneId, kEntityNodeId));
// Verify scene was set on ViewHolder
const fuchsia::ui::scenic::Event& event = events().back();
EXPECT_EQ(::fuchsia::ui::gfx::Event::Tag::kViewAttachedToScene, event.gfx().Which());
}
TEST_F(ViewTest, ViewLinksAfterViewHolderConnectsToScene) {
auto [view_token, view_holder_token] = scenic::ViewTokenPair::New();
const ResourceId view_holder_id = 1u;
Apply(scenic::NewCreateViewHolderCmd(view_holder_id, std::move(view_holder_token),
"Holder [Test]"));
auto view_holder = FindResource<ViewHolder>(view_holder_id);
// Create a Scene and connect the ViewHolder to the Scene.
const ResourceId scene_id = 3u;
Apply(scenic::NewCreateSceneCmd(scene_id));
auto scene = FindResource<Scene>(scene_id);
EXPECT_TRUE(scene);
Apply(scenic::NewAddChildCmd(scene_id, view_holder_id));
EXPECT_EQ(0u, events().size());
// Link the View to the ViewHolder.
const ResourceId view_id = 2u;
Apply(scenic::NewCreateViewCmd(view_id, std::move(view_token), "Test"));
auto view = FindResource<View>(view_id);
EXPECT_SCENIC_SESSION_ERROR_COUNT(0);
// Verify the connect event was emitted before the scene attached event.
EXPECT_EQ(4u, events().size());
EXPECT_SCENIC_SESSION_ERROR_COUNT(0);
const fuchsia::ui::scenic::Event& event = events()[0];
EXPECT_EQ(::fuchsia::ui::gfx::Event::Tag::kViewConnected, event.gfx().Which());
bool view_attached_to_scene_event = false;
for (const fuchsia::ui::scenic::Event& event : events()) {
if (event.gfx().Which() == fuchsia::ui::gfx::Event::Tag::kViewAttachedToScene) {
view_attached_to_scene_event = true;
}
}
EXPECT_TRUE(view_attached_to_scene_event);
}
TEST_F(ViewTest, ViewStateChangeNotifiesViewHolder) {
auto [view_token, view_holder_token] = scenic::ViewTokenPair::New();
const ResourceId view_holder_id = 1u;
Apply(scenic::NewCreateViewHolderCmd(view_holder_id, std::move(view_holder_token),
"Holder [Test]"));
const ResourceId view_id = 2u;
Apply(scenic::NewCreateViewCmd(view_id, std::move(view_token), "Test"));
EXPECT_SCENIC_SESSION_ERROR_COUNT(0);
// Verify View and ViewHolder are linked.
auto view_holder = FindResource<ViewHolder>(view_holder_id);
auto view = FindResource<View>(view_id);
EXPECT_EQ(view.get(), view_holder->view());
uint32_t next_event_id = events().size();
// Trigger a change in the ViewState. Mark as rendering.
view->SignalRender();
// Verify that one ViewState change event was enqueued.
RunLoopUntilIdle();
EXPECT_LT(next_event_id, events().size());
const fuchsia::ui::scenic::Event& event = events()[next_event_id];
VerifyViewState(event, true);
}
TEST_F(ViewTest, RenderStateAcrossManyFrames) {
auto [view_token, view_holder_token] = scenic::ViewTokenPair::New();
const ResourceId view_holder_id = 1u;
Apply(scenic::NewCreateViewHolderCmd(view_holder_id, std::move(view_holder_token),
"Holder [Test]"));
const ResourceId view_id = 2u;
Apply(scenic::NewCreateViewCmd(view_id, std::move(view_token), "Test"));
EXPECT_SCENIC_SESSION_ERROR_COUNT(0);
// Verify View and ViewHolder are linked.
auto view_holder = FindResource<ViewHolder>(view_holder_id);
auto view = FindResource<View>(view_id);
EXPECT_EQ(view.get(), view_holder->view());
uint32_t next_event_id = events().size();
// Trigger a change in the ViewState. Mark as rendering.
view->SignalRender();
RunLoopUntilIdle();
// Signal render for subsequent frames. No change in rendering state,
// should not enqueue another event.
view->SignalRender();
view->SignalRender();
RunLoopUntilIdle();
// Verify that one ViewState change event was enqueued.
EXPECT_LT(next_event_id, events().size());
const fuchsia::ui::scenic::Event& event = events()[next_event_id];
VerifyViewState(event, true);
}
TEST_F(ViewTest, RenderStateFalseWhenViewDisconnects) {
auto [view_token, view_holder_token] = scenic::ViewTokenPair::New();
const ResourceId view_holder_id = 1u;
Apply(scenic::NewCreateViewHolderCmd(view_holder_id, std::move(view_holder_token),
"Holder [Test]"));
auto view_holder = FindResource<ViewHolder>(view_holder_id);
const ResourceId view_id = 2u;
Apply(scenic::NewCreateViewCmd(view_id, std::move(view_token), "Test"));
EXPECT_SCENIC_SESSION_ERROR_COUNT(0);
{
auto view = FindResource<View>(view_id);
// Verify resources are mapped and linked.
EXPECT_EQ(2u, session()->GetMappedResourceCount());
// Mark the view as rendering.
view->SignalRender();
RunLoopUntilIdle();
} // Exit scope should destroy the view and disconnect the link.
uint32_t next_event_id = events().size();
Apply(scenic::NewReleaseResourceCmd(view_id));
EXPECT_LT(next_event_id, events().size());
const fuchsia::ui::scenic::Event& event = events()[next_event_id];
VerifyViewState(event, false);
const fuchsia::ui::scenic::Event& event2 = events().back();
EXPECT_EQ(fuchsia::ui::scenic::Event::Tag::kGfx, event2.Which());
EXPECT_EQ(::fuchsia::ui::gfx::Event::Tag::kViewDisconnected, event2.gfx().Which());
}
TEST_F(ViewTest, ViewHolderRenderWaitClearedWhenViewDestroyed) {
auto [view_token, view_holder_token] = scenic::ViewTokenPair::New();
const ResourceId view_holder_id = 1u;
Apply(scenic::NewCreateViewHolderCmd(view_holder_id, std::move(view_holder_token),
"Holder [Test]"));
auto view_holder = FindResource<ViewHolder>(view_holder_id);
const ResourceId view_id = 2u;
Apply(scenic::NewCreateViewCmd(view_id, std::move(view_token), "Test"));
// Verify resources are mapped and linked.
EXPECT_EQ(2u, session()->GetMappedResourceCount());
uint32_t next_event_id = events().size();
EXPECT_SCENIC_SESSION_ERROR_COUNT(0);
// Destroy the view. The link between View and ViewHolder should be
// disconnected.
Apply(scenic::NewReleaseResourceCmd(view_id));
EXPECT_EQ(1u, session()->GetMappedResourceCount());
EXPECT_LT(next_event_id, events().size());
const fuchsia::ui::scenic::Event& event = events().back();
EXPECT_EQ(fuchsia::ui::scenic::Event::Tag::kGfx, event.Which());
EXPECT_EQ(::fuchsia::ui::gfx::Event::Tag::kViewDisconnected, event.gfx().Which());
}
TEST_F(ViewTest, RenderSignalDoesntCrashWhenViewHolderDestroyed) {
auto [view_token, view_holder_token] = scenic::ViewTokenPair::New();
const ResourceId view_holder_id = 1u;
Apply(scenic::NewCreateViewHolderCmd(view_holder_id, std::move(view_holder_token),
"Holder [Test]"));
const ResourceId view_id = 2u;
Apply(scenic::NewCreateViewCmd(view_id, std::move(view_token), "Test"));
// Destroy the ViewHolder and disconnect the link.
Apply(scenic::NewReleaseResourceCmd(view_holder_id));
uint32_t event_size = events().size();
// Mark the view as rendering.
auto view = FindResource<View>(view_id);
view->SignalRender();
RunLoopUntilIdle();
EXPECT_SCENIC_SESSION_ERROR_COUNT(0);
// No additional render state events should have been posted.
EXPECT_EQ(event_size, events().size());
}
TEST_F(ViewTest, RenderStateFalseWhenViewHolderDisconnectsFromScene) {
auto [view_token, view_holder_token] = scenic::ViewTokenPair::New();
const ResourceId view_holder_id = 2u;
Apply(scenic::NewCreateViewHolderCmd(view_holder_id, std::move(view_holder_token),
"Holder [Test]"));
const ResourceId view_id = 1u;
Apply(scenic::NewCreateViewCmd(view_id, std::move(view_token), "Test"));
EXPECT_SCENIC_SESSION_ERROR_COUNT(0);
auto view_holder = FindResource<ViewHolder>(view_holder_id);
auto view = FindResource<View>(view_id);
// Make sure that the ViewHolder is connected to the Scene and the View is
// rendering.
const ResourceId scene_id = 3u;
Apply(scenic::NewCreateSceneCmd(scene_id));
auto scene = FindResource<Scene>(scene_id);
Apply(scenic::NewAddChildCmd(scene_id, view_holder_id));
view->SignalRender();
RunLoopUntilIdle();
uint32_t next_event_id = events().size();
// Detach ViewHolder from the scene.
view_holder->Detach(session()->error_reporter());
// The "stopped rendering" event should have emitted before the "detached from
// scene" event.
EXPECT_LT(next_event_id, events().size());
const fuchsia::ui::scenic::Event& event = events()[next_event_id];
VerifyViewState(event, false);
const fuchsia::ui::scenic::Event& event2 = events().back();
EXPECT_EQ(::fuchsia::ui::gfx::Event::Tag::kViewDetachedFromScene, event2.gfx().Which());
}
TEST_F(ViewTest, AnnotationViewReceivesViewPropertiesChangedEvent) {
auto [view_token, view_holder_token] = scenic::ViewTokenPair::New();
auto [annotation_view_token, annotation_view_holder_token] = scenic::ViewTokenPair::New();
const ResourceId view_holder_id = 1u;
Apply(scenic::NewCreateViewHolderCmd(view_holder_id, std::move(view_holder_token),
"Holder [Test]"));
auto view_holder = FindResource<ViewHolder>(view_holder_id)->GetWeakPtr();
const ResourceId view_id = 2u;
Apply(scenic::NewCreateViewCmd(view_id, std::move(view_token), "Test"));
auto view = FindResource<View>(view_id)->GetWeakPtr();
auto session_annotation = CreateSession();
auto cmd_ctx = CreateCommandContext();
const ResourceId annotation_view_id = 3u;
session_annotation->ApplyCommand(
&cmd_ctx,
scenic::NewCreateViewCmd(annotation_view_id, std::move(annotation_view_token), "Annotation"));
cmd_ctx.Flush();
auto annotation_view =
session_annotation->resources()->FindResource<View>(annotation_view_id)->GetWeakPtr();
// Create Annotation ViewHolder.
EXPECT_TRUE(view->AddAnnotationViewHolder(NewAnnotationViewHolder(
session_annotation.get(), view_linker_.get(), std::move(annotation_view_holder_token))));
// Set ViewProperties.
uint32_t event_size = events().size();
fuchsia::ui::gfx::BoundingBox bounding_box = {.min = {0, 0, 0}, .max = {100, 200, 300}};
fuchsia::ui::gfx::ViewProperties view_properties = {
.bounding_box = bounding_box,
.inset_from_min = {1, 2, 3},
.inset_from_max = {4, 5, 6},
.focus_change = true,
.downward_input = true,
};
view_holder->SetViewProperties(view_properties, session()->error_reporter());
RunLoopUntilIdle();
EXPECT_SCENIC_SESSION_ERROR_COUNT(0);
EXPECT_EQ(event_size + 2, events().size());
const auto& event_1 = events()[events().size() - 2];
const auto& event_2 = events()[events().size() - 1];
EXPECT_TRUE(event_1.is_gfx() && event_1.gfx().is_view_properties_changed());
EXPECT_TRUE(event_2.is_gfx() && event_2.gfx().is_view_properties_changed());
auto [event_view, event_annotation] =
event_1.gfx().view_properties_changed().view_id == view_id
? std::make_pair(event_1.gfx().view_properties_changed(),
event_2.gfx().view_properties_changed())
: std::make_pair(event_2.gfx().view_properties_changed(),
event_1.gfx().view_properties_changed());
EXPECT_EQ(event_view.view_id, view_id);
EXPECT_EQ(event_annotation.view_id, annotation_view_id);
EXPECT_TRUE(fidl::Equals(event_view.properties, view_properties));
EXPECT_FALSE(event_annotation.properties.focus_change);
event_annotation.properties.focus_change = view_properties.focus_change;
EXPECT_TRUE(fidl::Equals(event_annotation.properties, view_properties));
}
TEST_F(ViewTest, AnnotationViewReceivesViewAttachedToSceneEvent) {
auto [view_token, view_holder_token] = scenic::ViewTokenPair::New();
auto [annotation_view_token, annotation_view_holder_token] = scenic::ViewTokenPair::New();
const ResourceId view_holder_id = 1u;
Apply(scenic::NewCreateViewHolderCmd(view_holder_id, std::move(view_holder_token),
"Holder [Test]"));
auto view_holder = FindResource<ViewHolder>(view_holder_id)->GetWeakPtr();
const ResourceId view_id = 2u;
Apply(scenic::NewCreateViewCmd(view_id, std::move(view_token), "Test"));
auto view = FindResource<View>(view_id)->GetWeakPtr();
auto session_annotation = CreateSession();
auto cmd_ctx = CreateCommandContext();
const ResourceId annotation_view_id = 3u;
session_annotation->ApplyCommand(
&cmd_ctx,
scenic::NewCreateViewCmd(annotation_view_id, std::move(annotation_view_token), "Annotation"));
cmd_ctx.Flush();
auto annotation_view =
session_annotation->resources()->FindResource<View>(annotation_view_id)->GetWeakPtr();
// Create Annotation ViewHolder.
EXPECT_TRUE(view->AddAnnotationViewHolder(NewAnnotationViewHolder(
session_annotation.get(), view_linker_.get(), std::move(annotation_view_holder_token))));
// Create a Scene and connect the ViewHolder to the Scene.
const size_t event_size = events().size();
const ResourceId scene_id = 4u;
Apply(scenic::NewCreateSceneCmd(scene_id));
auto scene = FindResource<Scene>(scene_id);
EXPECT_TRUE(scene);
Apply(scenic::NewAddChildCmd(scene_id, view_holder_id));
RunLoopUntilIdle();
EXPECT_SCENIC_SESSION_ERROR_COUNT(0);
EXPECT_EQ(event_size + 2, events().size());
const auto& event_1 = events()[events().size() - 2];
const auto& event_2 = events()[events().size() - 1];
EXPECT_TRUE(event_1.is_gfx() && event_1.gfx().is_view_attached_to_scene());
EXPECT_TRUE(event_2.is_gfx() && event_2.gfx().is_view_attached_to_scene());
const uint32_t event_1_view_id = event_1.gfx().view_attached_to_scene().view_id;
const uint32_t event_2_view_id = event_2.gfx().view_attached_to_scene().view_id;
EXPECT_TRUE((event_1_view_id == view_id && event_2_view_id == annotation_view_id) ||
(event_2_view_id == view_id && event_1_view_id == annotation_view_id));
}
TEST_F(ViewTest, AnnotationViewReceivesViewDetachedFromSceneEvent) {
auto [view_token, view_holder_token] = scenic::ViewTokenPair::New();
auto [annotation_view_token, annotation_view_holder_token] = scenic::ViewTokenPair::New();
const ResourceId view_holder_id = 1u;
Apply(scenic::NewCreateViewHolderCmd(view_holder_id, std::move(view_holder_token),
"Holder [Test]"));
auto view_holder = FindResource<ViewHolder>(view_holder_id)->GetWeakPtr();
const ResourceId view_id = 2u;
Apply(scenic::NewCreateViewCmd(view_id, std::move(view_token), "Test"));
auto view = FindResource<View>(view_id)->GetWeakPtr();
auto session_annotation = CreateSession();
auto cmd_ctx = CreateCommandContext();
const ResourceId annotation_view_id = 3u;
session_annotation->ApplyCommand(
&cmd_ctx,
scenic::NewCreateViewCmd(annotation_view_id, std::move(annotation_view_token), "Annotation"));
cmd_ctx.Flush();
auto annotation_view =
session_annotation->resources()->FindResource<View>(annotation_view_id)->GetWeakPtr();
// Create Annotation ViewHolder.
EXPECT_TRUE(view->AddAnnotationViewHolder(NewAnnotationViewHolder(
session_annotation.get(), view_linker_.get(), std::move(annotation_view_holder_token))));
// Create a Scene and connect the ViewHolder to the Scene.
const ResourceId scene_id = 4u;
Apply(scenic::NewCreateSceneCmd(scene_id));
auto scene = FindResource<Scene>(scene_id);
EXPECT_TRUE(scene);
Apply(scenic::NewAddChildCmd(scene_id, view_holder_id));
// Detach the ViewHolder from the scene graph.
const size_t event_size = events().size();
EXPECT_TRUE(Apply(scenic::NewDetachCmd(view_holder_id)));
RunLoopUntilIdle();
EXPECT_SCENIC_SESSION_ERROR_COUNT(0);
EXPECT_EQ(event_size + 2, events().size());
const auto& event_1 = events()[events().size() - 2];
const auto& event_2 = events()[events().size() - 1];
EXPECT_TRUE(event_1.is_gfx() && event_1.gfx().is_view_detached_from_scene());
EXPECT_TRUE(event_2.is_gfx() && event_2.gfx().is_view_detached_from_scene());
const uint32_t event_1_view_id = event_1.gfx().view_detached_from_scene().view_id;
const uint32_t event_2_view_id = event_2.gfx().view_detached_from_scene().view_id;
EXPECT_TRUE((event_1_view_id == view_id && event_2_view_id == annotation_view_id) ||
(event_2_view_id == view_id && event_1_view_id == annotation_view_id));
}
} // namespace test
} // namespace gfx
} // namespace scenic_impl