blob: 9b6f3a780374b35668912b0c7c289d29bbfdb5ca [file] [log] [blame]
// Copyright 2019 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/focus/cpp/fidl.h>
#include <lib/fidl/cpp/binding.h>
#include <lib/fidl/cpp/interface_handle.h>
#include <lib/gtest/test_loop_fixture.h>
#include <lib/ui/scenic/cpp/view_ref_pair.h>
#include <lib/ui/scenic/cpp/view_token_pair.h>
#include <vector>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include "src/lib/fxl/logging.h"
#include "src/ui/scenic/lib/gfx/tests/gfx_test.h"
#include "src/ui/scenic/lib/gfx/tests/mocks/util.h"
#include "src/ui/scenic/lib/scheduling/id.h"
// This test exercises the properties of a focus chain. The setup has multiple
// Views arranged in a hierarchy, and also includes a FocusChainListener. Each
// change in focus should be accompanied by a new focus chain. The listener
// should immediately receive an updated focus chain.
//
// The geometry is not important in this test, so View surface geometries will overlap on a 9 x 9
// pixel layer.
//
// Since GFX Views have their origin coordinate at the top-left, we don't need to perform
// translation to center each View on the owning Layer.
namespace src_ui_scenic_lib_gfx_tests {
using fuchsia::ui::focus::FocusChain;
using fuchsia::ui::focus::FocusChainListener;
using fuchsia::ui::focus::FocusChainListenerRegistry;
using fuchsia::ui::focus::FocusChainListenerRegistryPtr;
using fuchsia::ui::views::ViewRef;
using scenic_impl::gfx::ExtractKoid;
using scenic_impl::gfx::ViewTree;
using scenic_impl::gfx::test::SessionWrapper;
using ViewFocuserPtr = fuchsia::ui::views::FocuserPtr;
using ViewFocuserRequest = fidl::InterfaceRequest<fuchsia::ui::views::Focuser>;
// Class fixture for TEST_F.
class FocusChainTest : public scenic_impl::gfx::test::GfxSystemTest, public FocusChainListener {
protected:
FocusChainTest() : focus_chain_listener_(this) {}
~FocusChainTest() override = default;
void SetUp() override {
scenic_impl::gfx::test::GfxSystemTest::SetUp();
context_provider().ConnectToPublicService<FocusChainListenerRegistry>(
focus_chain_listener_registry_.NewRequest());
fidl::InterfaceHandle<FocusChainListener> listener_handle;
focus_chain_listener_.Bind(listener_handle.NewRequest());
focus_chain_listener_registry_->Register(std::move(listener_handle));
RunLoopUntilIdle();
}
void TearDown() override {
focus_chain_listener_.Close(ZX_OK);
focus_chain_listener_registry_.Unbind();
GfxSystemTest::TearDown();
}
void RequestToPresent(scenic::Session* session) {
session->Present(/*presentation time*/ 0, [](auto) {});
RunLoopFor(zx::msec(20)); // "Good enough" deadline to ensure session update gets scheduled.
}
bool RequestFocusChange(ViewFocuserPtr* view_focuser_ptr, const ViewRef& target) {
ViewRef clone;
fidl::Clone(target, &clone);
bool request_processed = false;
bool request_honored = false;
(*view_focuser_ptr)
->RequestFocus(std::move(clone), [&request_processed, &request_honored](auto result) {
request_processed = true;
if (!result.is_err()) {
request_honored = true;
}
});
RunLoopUntilIdle();
EXPECT_TRUE(request_processed);
return request_honored;
}
// |fuchsia::ui::focus::FocusChainListener|
void OnFocusChange(FocusChain focus_chain, OnFocusChangeCallback callback) override {
observed_focus_chains_.push_back(std::move(focus_chain));
callback(); // Receipt.
}
size_t CountReceivedFocusChains() const { return observed_focus_chains_.size(); }
const FocusChain* LastFocusChain() const {
if (observed_focus_chains_.empty()) {
return nullptr;
} else {
// Can't do std::optional<const FocusChain&>.
return &observed_focus_chains_.back();
}
}
private:
FocusChainListenerRegistryPtr focus_chain_listener_registry_;
fidl::Binding<FocusChainListener> focus_chain_listener_;
std::vector<FocusChain> observed_focus_chains_;
};
// Some classes use the following two-node tree topology:
// A
// |
// B
// However, don't hesitate to craft a tree topology to best suit the test.
struct TwoNodeFocusChainTest : public FocusChainTest {
struct ClientA : public SessionWrapper {
ClientA(scenic_impl::Scenic* scenic) : SessionWrapper(scenic) {}
std::unique_ptr<scenic::Scene> scene;
std::unique_ptr<scenic::ViewHolder> holder_b;
};
struct ClientB : public SessionWrapper {
ClientB(scenic_impl::Scenic* scenic) : SessionWrapper(scenic) {}
std::unique_ptr<scenic::View> view;
zx_koid_t view_ref_koid = ZX_KOID_INVALID;
};
void SetUp() override {
FocusChainTest::SetUp();
client_a = std::make_unique<ClientA>(scenic());
client_b = std::make_unique<ClientB>(scenic());
auto pair_ab = scenic::ViewTokenPair::New();
client_a->RunNow(
[test = this, state = client_a.get(), vh_ab = std::move(pair_ab.view_holder_token)](
scenic::Session* session, scenic::EntityNode* session_anchor) mutable {
const std::array<float, 3> kZero = {0, 0, 0};
// Minimal scene.
scenic::Compositor compositor(session);
scenic::LayerStack layer_stack(session);
compositor.SetLayerStack(layer_stack);
scenic::Layer layer(session);
layer.SetSize(9 /*px*/, 9 /*px*/);
layer_stack.AddLayer(layer);
scenic::Renderer renderer(session);
layer.SetRenderer(renderer);
state->scene = std::make_unique<scenic::Scene>(session);
scenic::Camera camera(*state->scene);
renderer.SetCamera(camera);
// Add local root node to the scene, and attach the ViewHolder to the root node.
state->scene->AddChild(*session_anchor);
state->holder_b =
std::make_unique<scenic::ViewHolder>(session, std::move(vh_ab), "view holder B");
state->holder_b->SetViewProperties(kZero, {9, 9, 1}, kZero, kZero);
session_anchor->Attach(*state->holder_b);
test->RequestToPresent(session);
});
client_b->RunNow([test = this, state = client_b.get(), v_ab = std::move(pair_ab.view_token)](
scenic::Session* session, scenic::EntityNode* session_anchor) mutable {
auto pair = scenic::ViewRefPair::New();
state->view_ref_koid = ExtractKoid(pair.view_ref);
state->view =
std::make_unique<scenic::View>(session, std::move(v_ab), std::move(pair.control_ref),
std::move(pair.view_ref), "view B");
state->view->AddChild(*session_anchor);
test->RequestToPresent(session);
});
}
void TearDown() override {
client_a = nullptr;
client_b = nullptr;
FocusChainTest::TearDown();
}
std::unique_ptr<ClientA> client_a;
std::unique_ptr<ClientB> client_b;
};
// Some classes use the following three-node tree topology:
// A
// |
// B
// |
// C
// However, don't hesitate to craft a tree topology to best suit the test.
struct ThreeNodeFocusChainTest : public FocusChainTest {
struct ClientA : public SessionWrapper {
ClientA(scenic_impl::Scenic* scenic) : SessionWrapper(scenic) {}
std::unique_ptr<scenic::Scene> scene;
std::unique_ptr<scenic::ViewHolder> holder_b;
};
struct ClientB : public SessionWrapper {
ClientB(scenic_impl::Scenic* scenic) : SessionWrapper(scenic) {}
std::unique_ptr<scenic::View> view;
zx_koid_t view_ref_koid = ZX_KOID_INVALID;
std::unique_ptr<scenic::ViewHolder> holder_c;
};
struct ClientC : public SessionWrapper {
ClientC(scenic_impl::Scenic* scenic) : SessionWrapper(scenic) {}
std::unique_ptr<scenic::View> view;
zx_koid_t view_ref_koid = ZX_KOID_INVALID;
};
void SetUp() override {
FocusChainTest::SetUp();
client_a = std::make_unique<ClientA>(scenic());
client_b = std::make_unique<ClientB>(scenic());
client_c = std::make_unique<ClientC>(scenic());
auto pair_ab = scenic::ViewTokenPair::New();
auto pair_bc = scenic::ViewTokenPair::New();
client_a->RunNow(
[test = this, state = client_a.get(), vh_ab = std::move(pair_ab.view_holder_token)](
scenic::Session* session, scenic::EntityNode* session_anchor) mutable {
const std::array<float, 3> kZero = {0, 0, 0};
// Minimal scene.
scenic::Compositor compositor(session);
scenic::LayerStack layer_stack(session);
compositor.SetLayerStack(layer_stack);
scenic::Layer layer(session);
layer.SetSize(9 /*px*/, 9 /*px*/);
layer_stack.AddLayer(layer);
scenic::Renderer renderer(session);
layer.SetRenderer(renderer);
state->scene = std::make_unique<scenic::Scene>(session);
scenic::Camera camera(*state->scene);
renderer.SetCamera(camera);
// Add local root node to the scene, and attach the ViewHolder to the root node.
state->scene->AddChild(*session_anchor);
state->holder_b =
std::make_unique<scenic::ViewHolder>(session, std::move(vh_ab), "view holder B");
state->holder_b->SetViewProperties(kZero, {9, 9, 1}, kZero, kZero);
session_anchor->Attach(*state->holder_b);
test->RequestToPresent(session);
});
client_b->RunNow([test = this, state = client_b.get(), v_ab = std::move(pair_ab.view_token),
vh_bc = std::move(pair_bc.view_holder_token)](
scenic::Session* session, scenic::EntityNode* session_anchor) mutable {
const std::array<float, 3> kZero = {0, 0, 0};
auto pair = scenic::ViewRefPair::New();
state->view_ref_koid = ExtractKoid(pair.view_ref);
state->view =
std::make_unique<scenic::View>(session, std::move(v_ab), std::move(pair.control_ref),
std::move(pair.view_ref), "view B");
state->view->AddChild(*session_anchor);
state->holder_c =
std::make_unique<scenic::ViewHolder>(session, std::move(vh_bc), "view holder C");
state->holder_c->SetViewProperties(kZero, {9, 9, 1}, kZero, kZero);
session_anchor->Attach(*state->holder_c);
test->RequestToPresent(session);
});
client_c->RunNow([test = this, state = client_c.get(), v_bc = std::move(pair_bc.view_token)](
scenic::Session* session, scenic::EntityNode* session_anchor) mutable {
auto pair = scenic::ViewRefPair::New();
state->view_ref_koid = ExtractKoid(pair.view_ref);
state->view =
std::make_unique<scenic::View>(session, std::move(v_bc), std::move(pair.control_ref),
std::move(pair.view_ref), "view C");
state->view->AddChild(*session_anchor);
test->RequestToPresent(session);
});
}
void TearDown() override {
client_a = nullptr;
client_b = nullptr;
client_c = nullptr;
FocusChainTest::TearDown();
}
std::unique_ptr<ClientA> client_a;
std::unique_ptr<ClientB> client_b;
std::unique_ptr<ClientC> client_c;
};
// Some classes use the following four-node tree topology:
// A
// / \
// B C
// |
// D
// However, don't hesitate to craft a tree topology to best suit the test.
struct FourNodeFocusChainTest : public FocusChainTest {
struct RootClient : public SessionWrapper {
RootClient(scenic_impl::Scenic* scenic) : SessionWrapper(scenic) {}
std::unique_ptr<scenic::Scene> scene;
std::unique_ptr<scenic::ViewHolder> holder_b;
std::unique_ptr<scenic::ViewHolder> holder_c;
};
struct BranchClient : public SessionWrapper {
BranchClient(scenic_impl::Scenic* scenic) : SessionWrapper(scenic) {}
std::unique_ptr<scenic::View> view;
zx_koid_t view_ref_koid = ZX_KOID_INVALID;
std::unique_ptr<scenic::ViewHolder> holder_d;
};
struct LeafClient : public SessionWrapper {
LeafClient(scenic_impl::Scenic* scenic) : SessionWrapper(scenic) {}
std::unique_ptr<scenic::View> view;
zx_koid_t view_ref_koid = ZX_KOID_INVALID;
};
void SetUp() override {
FocusChainTest::SetUp();
client_a = std::make_unique<RootClient>(scenic());
client_b = std::make_unique<BranchClient>(scenic());
client_c = std::make_unique<LeafClient>(scenic());
client_d = std::make_unique<LeafClient>(scenic());
auto pair_ab = scenic::ViewTokenPair::New();
auto pair_ac = scenic::ViewTokenPair::New();
auto pair_bd = scenic::ViewTokenPair::New();
client_a->RunNow([test = this, state = client_a.get(),
vh_ab = std::move(pair_ab.view_holder_token),
vh_ac = std::move(pair_ac.view_holder_token)](
scenic::Session* session, scenic::EntityNode* session_anchor) mutable {
const std::array<float, 3> kZero = {0, 0, 0};
// Minimal scene.
scenic::Compositor compositor(session);
scenic::LayerStack layer_stack(session);
compositor.SetLayerStack(layer_stack);
scenic::Layer layer(session);
layer.SetSize(9 /*px*/, 9 /*px*/);
layer_stack.AddLayer(layer);
scenic::Renderer renderer(session);
layer.SetRenderer(renderer);
state->scene = std::make_unique<scenic::Scene>(session);
scenic::Camera camera(*state->scene);
renderer.SetCamera(camera);
// Add local root node to the scene, and ViewHolders to the root node.
state->scene->AddChild(*session_anchor);
state->holder_b =
std::make_unique<scenic::ViewHolder>(session, std::move(vh_ab), "view holder B");
state->holder_c =
std::make_unique<scenic::ViewHolder>(session, std::move(vh_ac), "view holder C");
state->holder_b->SetViewProperties(kZero, {9, 9, 1}, kZero, kZero);
state->holder_c->SetViewProperties(kZero, {9, 9, 1}, kZero, kZero);
session_anchor->Attach(*state->holder_b);
session_anchor->Attach(*state->holder_c);
test->RequestToPresent(session);
});
client_b->RunNow([test = this, state = client_b.get(), v_ab = std::move(pair_ab.view_token),
vh_bd = std::move(pair_bd.view_holder_token)](
scenic::Session* session, scenic::EntityNode* session_anchor) mutable {
const std::array<float, 3> kZero = {0, 0, 0};
auto pair = scenic::ViewRefPair::New();
state->view_ref_koid = ExtractKoid(pair.view_ref);
state->view =
std::make_unique<scenic::View>(session, std::move(v_ab), std::move(pair.control_ref),
std::move(pair.view_ref), "view B");
state->view->AddChild(*session_anchor);
state->holder_d =
std::make_unique<scenic::ViewHolder>(session, std::move(vh_bd), "view holder D");
state->holder_d->SetViewProperties(kZero, {9, 9, 1}, kZero, kZero);
session_anchor->Attach(*state->holder_d);
test->RequestToPresent(session);
});
client_c->RunNow([test = this, state = client_c.get(), v_ac = std::move(pair_ac.view_token)](
scenic::Session* session, scenic::EntityNode* session_anchor) mutable {
auto pair = scenic::ViewRefPair::New();
state->view_ref_koid = ExtractKoid(pair.view_ref);
state->view =
std::make_unique<scenic::View>(session, std::move(v_ac), std::move(pair.control_ref),
std::move(pair.view_ref), "view C");
state->view->AddChild(*session_anchor);
test->RequestToPresent(session);
});
client_d->RunNow([test = this, state = client_d.get(), v_bd = std::move(pair_bd.view_token)](
scenic::Session* session, scenic::EntityNode* session_anchor) mutable {
auto pair = scenic::ViewRefPair::New();
state->view_ref_koid = ExtractKoid(pair.view_ref);
state->view =
std::make_unique<scenic::View>(session, std::move(v_bd), std::move(pair.control_ref),
std::move(pair.view_ref), "view D");
state->view->AddChild(*session_anchor);
test->RequestToPresent(session);
});
}
void TearDown() override {
client_a = nullptr;
client_b = nullptr;
client_c = nullptr;
client_d = nullptr;
FocusChainTest::TearDown();
}
std::unique_ptr<RootClient> client_a;
std::unique_ptr<BranchClient> client_b;
std::unique_ptr<LeafClient> client_c;
std::unique_ptr<LeafClient> client_d;
};
TEST_F(FocusChainTest, EmptySceneTransitions) {
EXPECT_EQ(CountReceivedFocusChains(), 0u);
struct RootClient : public SessionWrapper {
RootClient(scenic_impl::Scenic* scenic) : SessionWrapper(scenic) {}
std::unique_ptr<scenic::Scene> scene;
} some_session(scenic()), root_session(scenic());
some_session.RunNow([test = this, state = &some_session](
scenic::Session* session, scenic::EntityNode* session_anchor) mutable {
// Merely creating a scene, without hooking it up to a compositor properly, should not
// trigger a focus change.
state->scene = std::make_unique<scenic::Scene>(session);
state->scene->AddChild(*session_anchor);
test->RequestToPresent(session);
});
EXPECT_EQ(CountReceivedFocusChains(), 0u);
root_session.RunNow([test = this, state = &root_session](
scenic::Session* session, scenic::EntityNode* session_anchor) mutable {
// Create a scene that is hooked up to a compositor. This set of
// commands should trigger the creation of a focus chain, with length 1.
scenic::Compositor compositor(session);
scenic::LayerStack layer_stack(session);
compositor.SetLayerStack(layer_stack);
scenic::Layer layer(session);
layer.SetSize(9 /*px*/, 9 /*px*/);
layer_stack.AddLayer(layer);
scenic::Renderer renderer(session);
layer.SetRenderer(renderer);
state->scene = std::make_unique<scenic::Scene>(session);
scenic::Camera camera(*state->scene);
renderer.SetCamera(camera);
state->scene->AddChild(*session_anchor);
test->RequestToPresent(session);
});
EXPECT_EQ(CountReceivedFocusChains(), 1u);
ASSERT_TRUE(LastFocusChain());
EXPECT_EQ(LastFocusChain()->focus_chain().size(), 1u);
}
// Tree topology:
// [ A.scene_b A.scene_c ]
// | |
// B C
// Focus chain is determined by which scene (if any) is connected to the compositor.
// This test emulates Root Presenter's Presentation swap.
TEST_F(FocusChainTest, LayerSwapMeansSceneSwap) {
struct MultiSceneRootClient : public SessionWrapper {
MultiSceneRootClient(scenic_impl::Scenic* scenic) : SessionWrapper(scenic) {}
std::unique_ptr<scenic::Compositor> compositor;
std::unique_ptr<scenic::LayerStack> layer_stack;
std::unique_ptr<scenic::Layer> layer_b;
std::unique_ptr<scenic::Layer> layer_c;
} client_a(scenic());
struct LeafClient : public SessionWrapper {
LeafClient(scenic_impl::Scenic* scenic) : SessionWrapper(scenic) {}
std::unique_ptr<scenic::View> view;
zx_koid_t view_ref_koid = ZX_KOID_INVALID;
} client_b(scenic()), client_c(scenic());
auto pair_ab = scenic::ViewTokenPair::New();
auto pair_ac = scenic::ViewTokenPair::New();
client_a.RunNow([test = this, state = &client_a, vh_ab = std::move(pair_ab.view_holder_token),
vh_ac = std::move(pair_ac.view_holder_token)](
scenic::Session* session, scenic::EntityNode* unused) mutable {
const std::array<float, 3> kZero = {0, 0, 0};
// Scene graph is set up with multiple layers, and inserts at most one in the layer stack.
state->compositor = std::make_unique<scenic::Compositor>(session);
state->layer_stack = std::make_unique<scenic::LayerStack>(session);
state->compositor->SetLayerStack(*state->layer_stack);
// Create layer_b and scene, but do not insert into layer stack yet.
{
state->layer_b = std::make_unique<scenic::Layer>(session);
state->layer_b->SetSize(9 /*px*/, 9 /*px*/);
scenic::Renderer renderer(session);
state->layer_b->SetRenderer(renderer);
scenic::Scene scene(session);
scenic::Camera camera(scene);
renderer.SetCamera(camera);
scenic::EntityNode root_b(session);
scene.AddChild(root_b);
scenic::ViewHolder holder_b(session, std::move(vh_ab), "view holder B");
root_b.Attach(holder_b);
}
// Create layer_c and scene, but do not insert into layer stack yet.
{
state->layer_c = std::make_unique<scenic::Layer>(session);
state->layer_c->SetSize(9 /*px*/, 9 /*px*/);
scenic::Renderer renderer(session);
state->layer_c->SetRenderer(renderer);
scenic::Scene scene(session);
scenic::Camera camera(scene);
renderer.SetCamera(camera);
scenic::EntityNode root_c(session);
scene.AddChild(root_c);
scenic::ViewHolder holder_c(session, std::move(vh_ac), "view holder C");
root_c.Attach(holder_c);
}
test->RequestToPresent(session);
});
client_b.RunNow([test = this, state = &client_b, v_ab = std::move(pair_ab.view_token)](
scenic::Session* session, scenic::EntityNode* session_anchor) mutable {
auto pair = scenic::ViewRefPair::New();
state->view_ref_koid = ExtractKoid(pair.view_ref);
state->view = std::make_unique<scenic::View>(
session, std::move(v_ab), std::move(pair.control_ref), std::move(pair.view_ref), "view B");
test->RequestToPresent(session);
});
client_c.RunNow([test = this, state = &client_c, v_ac = std::move(pair_ac.view_token)](
scenic::Session* session, scenic::EntityNode* session_anchor) mutable {
auto pair = scenic::ViewRefPair::New();
state->view_ref_koid = ExtractKoid(pair.view_ref);
state->view = std::make_unique<scenic::View>(
session, std::move(v_ac), std::move(pair.control_ref), std::move(pair.view_ref), "view C");
test->RequestToPresent(session);
});
ASSERT_FALSE(RunLoopUntilIdle()); // There should be no pending tasks.
EXPECT_EQ(CountReceivedFocusChains(), 0u);
// Add Layer B.
client_a.RunNow([test = this, state = &client_a](scenic::Session* session,
scenic::EntityNode* session_anchor) mutable {
state->layer_stack->AddLayer(*state->layer_b);
test->RequestToPresent(session);
});
EXPECT_EQ(CountReceivedFocusChains(), 1u);
EXPECT_EQ(LastFocusChain()->focus_chain().size(), 1u);
const zx_koid_t scene_b = ExtractKoid(LastFocusChain()->focus_chain()[0]);
// Layer B's focus chain extended to B.
ViewTree::FocusChangeStatus status =
engine()->scene_graph()->RequestFocusChange(scene_b, client_b.view_ref_koid);
RunLoopUntilIdle(); // Process FIDL messages.
EXPECT_EQ(status, ViewTree::FocusChangeStatus::kAccept);
EXPECT_EQ(CountReceivedFocusChains(), 2u);
EXPECT_EQ(ExtractKoid(LastFocusChain()->focus_chain()[0]), scene_b);
EXPECT_EQ(ExtractKoid(LastFocusChain()->focus_chain()[1]), client_b.view_ref_koid);
// Replace Layer B with Layer C.
client_a.RunNow([test = this, state = &client_a](scenic::Session* session,
scenic::EntityNode* session_anchor) mutable {
state->layer_stack->RemoveAllLayers();
state->layer_stack->AddLayer(*state->layer_c);
test->RequestToPresent(session);
});
EXPECT_EQ(CountReceivedFocusChains(), 3u);
EXPECT_EQ(LastFocusChain()->focus_chain().size(), 1u);
const zx_koid_t scene_c = ExtractKoid(LastFocusChain()->focus_chain()[0]);
// Root is swapped out!
EXPECT_NE(scene_b, scene_c);
// Layer C's focus chain extended to C.
status = engine()->scene_graph()->RequestFocusChange(scene_c, client_c.view_ref_koid);
RunLoopUntilIdle(); // Process FIDL messages.
EXPECT_EQ(status, ViewTree::FocusChangeStatus::kAccept);
EXPECT_EQ(CountReceivedFocusChains(), 4u);
EXPECT_EQ(ExtractKoid(LastFocusChain()->focus_chain()[0]), scene_c);
EXPECT_EQ(ExtractKoid(LastFocusChain()->focus_chain()[1]), client_c.view_ref_koid);
// Remove Layer C.
client_a.RunNow([test = this, state = &client_a](scenic::Session* session,
scenic::EntityNode* session_anchor) mutable {
state->layer_stack->RemoveAllLayers();
test->RequestToPresent(session);
});
EXPECT_EQ(CountReceivedFocusChains(), 5u);
EXPECT_TRUE(LastFocusChain()->IsEmpty());
}
// Tree topology:
// A
// |
// B
// This test is intentionally not a TwoNodeFocusChainTest because it makes assertions against
// intermediate state during tree setup.
TEST_F(FocusChainTest, OneLinkTopology) {
auto pair_ab = scenic::ViewTokenPair::New();
// Client "A" sets up the Scene, and a ViewHolder for "B".
struct RootClient : public SessionWrapper {
RootClient(scenic_impl::Scenic* scenic) : SessionWrapper(scenic) {}
std::unique_ptr<scenic::Scene> scene;
std::unique_ptr<scenic::ViewHolder> view_holder;
} client_a(scenic());
client_a.RunNow([test = this, state = &client_a, vh_ab = std::move(pair_ab.view_holder_token)](
scenic::Session* session, scenic::EntityNode* session_anchor) mutable {
const std::array<float, 3> kZero = {0, 0, 0};
// Minimal scene.
scenic::Compositor compositor(session);
scenic::LayerStack layer_stack(session);
compositor.SetLayerStack(layer_stack);
scenic::Layer layer(session);
layer.SetSize(9 /*px*/, 9 /*px*/);
layer_stack.AddLayer(layer);
scenic::Renderer renderer(session);
layer.SetRenderer(renderer);
state->scene = std::make_unique<scenic::Scene>(session);
scenic::Camera camera(*state->scene);
renderer.SetCamera(camera);
// Add local root node to the scene, and attach the ViewHolder to the root node.
state->scene->AddChild(*session_anchor);
state->view_holder =
std::make_unique<scenic::ViewHolder>(session, std::move(vh_ab), "view holder B");
state->view_holder->SetViewProperties(kZero, {9, 9, 1}, kZero, kZero);
session_anchor->Attach(*state->view_holder);
test->RequestToPresent(session);
});
// Merely setting up a ViewHolder does not trigger a fresh focus chain, or make it longer.
EXPECT_EQ(CountReceivedFocusChains(), 1u);
EXPECT_EQ(LastFocusChain()->focus_chain().size(), 1u);
// Client "B" sets up its own View.
struct LeafClient : public SessionWrapper {
LeafClient(scenic_impl::Scenic* scenic) : SessionWrapper(scenic) {}
std::unique_ptr<scenic::View> view;
zx_koid_t view_ref_koid = ZX_KOID_INVALID;
} client_b(scenic());
client_b.RunNow([test = this, state = &client_b, v_ab = std::move(pair_ab.view_token)](
scenic::Session* session, scenic::EntityNode* session_anchor) mutable {
auto pair = scenic::ViewRefPair::New();
state->view_ref_koid = ExtractKoid(pair.view_ref);
state->view = std::make_unique<scenic::View>(
session, std::move(v_ab), std::move(pair.control_ref), std::move(pair.view_ref), "view B");
state->view->AddChild(*session_anchor);
test->RequestToPresent(session);
});
// Merely setting up a separate View, that is connected to the scene, does not trigger a fresh
// focus chain, or make it longer.
EXPECT_EQ(CountReceivedFocusChains(), 1u);
EXPECT_EQ(LastFocusChain()->focus_chain().size(), 1u);
}
TEST_F(TwoNodeFocusChainTest, FocusTransferDownAllowed) {
// Emulate a focus transfer, due to either an explicit command, or via user input.
const zx_koid_t root = engine()->scene_graph()->view_tree().focus_chain()[0];
ViewTree::FocusChangeStatus status =
engine()->scene_graph()->RequestFocusChange(root, client_b->view_ref_koid);
RunLoopUntilIdle(); // Process FIDL messages.
EXPECT_EQ(status, ViewTree::FocusChangeStatus::kAccept);
EXPECT_EQ(CountReceivedFocusChains(), 2u);
EXPECT_EQ(LastFocusChain()->focus_chain().size(), 2u);
}
TEST_F(TwoNodeFocusChainTest, FocusTransferNullChangeNoFidl) {
// A View can transfer focus from itself to itself, but a null change should not trigger a new
// focus chain.
const zx_koid_t root = engine()->scene_graph()->view_tree().focus_chain()[0];
ViewTree::FocusChangeStatus status = engine()->scene_graph()->RequestFocusChange(root, root);
RunLoopUntilIdle(); // Process FIDL messages.
EXPECT_EQ(status, ViewTree::FocusChangeStatus::kAccept);
EXPECT_EQ(CountReceivedFocusChains(), 1u);
EXPECT_EQ(LastFocusChain()->focus_chain().size(), 1u);
// Transfer down for a similar test on Client B.
status = engine()->scene_graph()->RequestFocusChange(root, client_b->view_ref_koid);
RunLoopUntilIdle(); // Process FIDL messages.
EXPECT_EQ(status, ViewTree::FocusChangeStatus::kAccept);
EXPECT_EQ(CountReceivedFocusChains(), 2u);
EXPECT_EQ(LastFocusChain()->focus_chain().size(), 2u);
// Transfer focus from itself to itself. No change expected.
status =
engine()->scene_graph()->RequestFocusChange(client_b->view_ref_koid, client_b->view_ref_koid);
RunLoopUntilIdle(); // Process FIDL messages.
EXPECT_EQ(status, ViewTree::FocusChangeStatus::kAccept);
EXPECT_EQ(CountReceivedFocusChains(), 2u);
EXPECT_EQ(LastFocusChain()->focus_chain().size(), 2u);
}
TEST_F(TwoNodeFocusChainTest, FocusTransferUpwardDenied) {
const zx_koid_t root = engine()->scene_graph()->view_tree().focus_chain()[0];
ViewTree::FocusChangeStatus status =
engine()->scene_graph()->RequestFocusChange(root, client_b->view_ref_koid);
RunLoopUntilIdle(); // Process FIDL messages.
EXPECT_EQ(status, ViewTree::FocusChangeStatus::kAccept);
EXPECT_EQ(CountReceivedFocusChains(), 2u);
EXPECT_EQ(LastFocusChain()->focus_chain().size(), 2u);
// Upward focus request, denied. No change expected.
status = engine()->scene_graph()->RequestFocusChange(client_b->view_ref_koid, root);
RunLoopUntilIdle(); // Process FIDL messages.
EXPECT_EQ(status, ViewTree::FocusChangeStatus::kErrorRequestorNotRequestAncestor);
EXPECT_EQ(CountReceivedFocusChains(), 2u);
EXPECT_EQ(LastFocusChain()->focus_chain().size(), 2u);
}
// Tree topology:
// A
// / \
// B C
// |
// D
TEST_F(FourNodeFocusChainTest, LengthyFocusChain) {
// Merely setting up four separate Views, that are connected to the scene, does not trigger a
// fresh focus chain, or make it longer.
EXPECT_EQ(CountReceivedFocusChains(), 1u);
EXPECT_EQ(LastFocusChain()->focus_chain().size(), 1u);
// Emulate a focus transfer from "A" to "D".
const zx_koid_t root = engine()->scene_graph()->view_tree().focus_chain()[0];
ViewTree::FocusChangeStatus status =
engine()->scene_graph()->RequestFocusChange(root, client_d->view_ref_koid);
RunLoopUntilIdle(); // Process FIDL messages.
// Focus chain is now [A - B - D].
EXPECT_EQ(status, ViewTree::FocusChangeStatus::kAccept);
EXPECT_EQ(CountReceivedFocusChains(), 2u);
EXPECT_EQ(LastFocusChain()->focus_chain().size(), 3u);
EXPECT_EQ(ExtractKoid(LastFocusChain()->focus_chain()[0]), root);
EXPECT_EQ(ExtractKoid(LastFocusChain()->focus_chain()[1]), client_b->view_ref_koid);
EXPECT_EQ(ExtractKoid(LastFocusChain()->focus_chain()[2]), client_d->view_ref_koid);
}
TEST_F(FourNodeFocusChainTest, SiblingTransferRequestsDenied) {
// Setup: Transfer to "D".
const zx_koid_t root = engine()->scene_graph()->view_tree().focus_chain()[0];
ViewTree::FocusChangeStatus status =
engine()->scene_graph()->RequestFocusChange(root, client_d->view_ref_koid);
RunLoopUntilIdle(); // Process FIDL messages.
// Transfer request from "D" to "C" denied.
status =
engine()->scene_graph()->RequestFocusChange(client_d->view_ref_koid, client_c->view_ref_koid);
RunLoopUntilIdle(); // Process FIDL messages.
// No change in focus chain.
EXPECT_EQ(status, ViewTree::FocusChangeStatus::kErrorRequestorNotRequestAncestor);
EXPECT_EQ(CountReceivedFocusChains(), 2u);
EXPECT_EQ(LastFocusChain()->focus_chain().size(), 3u);
EXPECT_EQ(ExtractKoid(LastFocusChain()->focus_chain()[0]), root);
EXPECT_EQ(ExtractKoid(LastFocusChain()->focus_chain()[1]), client_b->view_ref_koid);
EXPECT_EQ(ExtractKoid(LastFocusChain()->focus_chain()[2]), client_d->view_ref_koid);
// Transfer request from "C" to "C" denied.
status =
engine()->scene_graph()->RequestFocusChange(client_c->view_ref_koid, client_c->view_ref_koid);
RunLoopUntilIdle(); // Process FIDL messages.
// No change in focus chain.
EXPECT_EQ(status, ViewTree::FocusChangeStatus::kErrorRequestorNotAuthorized);
EXPECT_EQ(CountReceivedFocusChains(), 2u);
EXPECT_EQ(LastFocusChain()->focus_chain().size(), 3u);
EXPECT_EQ(ExtractKoid(LastFocusChain()->focus_chain()[0]), root);
EXPECT_EQ(ExtractKoid(LastFocusChain()->focus_chain()[1]), client_b->view_ref_koid);
EXPECT_EQ(ExtractKoid(LastFocusChain()->focus_chain()[2]), client_d->view_ref_koid);
}
// Tree topology:
// A
// |
// B
// |
// C
TEST_F(ThreeNodeFocusChainTest, ViewDestructionShortensFocusChain) {
// Start with a transfer of focus, from "A" to "C". Focus chain is length 3.
zx_koid_t root = engine()->scene_graph()->view_tree().focus_chain()[0];
ViewTree::FocusChangeStatus status =
engine()->scene_graph()->RequestFocusChange(root, client_c->view_ref_koid);
RunLoopUntilIdle(); // Process FIDL messages.
EXPECT_EQ(status, ViewTree::FocusChangeStatus::kAccept);
EXPECT_EQ(CountReceivedFocusChains(), 2u);
EXPECT_EQ(LastFocusChain()->focus_chain().size(), 3u);
EXPECT_EQ(ExtractKoid(LastFocusChain()->focus_chain()[0]), root);
EXPECT_EQ(ExtractKoid(LastFocusChain()->focus_chain()[1]), client_b->view_ref_koid);
EXPECT_EQ(ExtractKoid(LastFocusChain()->focus_chain()[2]), client_c->view_ref_koid);
// Client "C" destroys its view.
client_c->RunNow([test = this, state = client_c.get()](scenic::Session* session,
scenic::EntityNode* session_anchor) {
state->view = nullptr;
test->RequestToPresent(session);
});
// Focus chain is now length 2.
EXPECT_EQ(CountReceivedFocusChains(), 3u);
EXPECT_EQ(LastFocusChain()->focus_chain().size(), 2u);
EXPECT_EQ(ExtractKoid(LastFocusChain()->focus_chain()[0]), root);
EXPECT_EQ(ExtractKoid(LastFocusChain()->focus_chain()[1]), client_b->view_ref_koid);
// Client "B" destroys its view.
client_b->RunNow([test = this, state = client_b.get()](scenic::Session* session,
scenic::EntityNode* session_anchor) {
state->view = nullptr;
test->RequestToPresent(session);
});
// Focus chain is now length 1.
EXPECT_EQ(CountReceivedFocusChains(), 4u);
EXPECT_EQ(LastFocusChain()->focus_chain().size(), 1u);
EXPECT_EQ(ExtractKoid(LastFocusChain()->focus_chain()[0]), root);
// Client "A" destroys its scene.
client_a->RunNow([test = this, state = client_a.get()](scenic::Session* session,
scenic::EntityNode* session_anchor) {
state->scene = nullptr;
test->RequestToPresent(session);
});
// Focus chain is now empty.
EXPECT_EQ(CountReceivedFocusChains(), 5u);
EXPECT_TRUE(LastFocusChain()->IsEmpty());
}
// Tree topology:
// A
// |
// B
// |
// C
TEST_F(ThreeNodeFocusChainTest, ViewHolderDestructionShortensFocusChain) {
// Start with a transfer of focus, from "A" to "C". Focus chain is length 3.
const zx_koid_t root = engine()->scene_graph()->view_tree().focus_chain()[0];
ViewTree::FocusChangeStatus status =
engine()->scene_graph()->RequestFocusChange(root, client_c->view_ref_koid);
RunLoopUntilIdle(); // Process FIDL messages.
EXPECT_EQ(status, ViewTree::FocusChangeStatus::kAccept);
EXPECT_EQ(CountReceivedFocusChains(), 2u);
EXPECT_EQ(LastFocusChain()->focus_chain().size(), 3u);
EXPECT_EQ(ExtractKoid(LastFocusChain()->focus_chain()[0]), root);
EXPECT_EQ(ExtractKoid(LastFocusChain()->focus_chain()[1]), client_b->view_ref_koid);
EXPECT_EQ(ExtractKoid(LastFocusChain()->focus_chain()[2]), client_c->view_ref_koid);
// Client "B" detaches and destroys its view holder.
client_b->RunNow([test = this, state = client_b.get()](scenic::Session* session,
scenic::EntityNode* session_anchor) {
session_anchor->DetachChildren();
state->holder_c = nullptr;
test->RequestToPresent(session);
});
// Focus chain is now length 2.
EXPECT_EQ(CountReceivedFocusChains(), 3u);
EXPECT_EQ(LastFocusChain()->focus_chain().size(), 2u);
EXPECT_EQ(ExtractKoid(LastFocusChain()->focus_chain()[0]), root);
EXPECT_EQ(ExtractKoid(LastFocusChain()->focus_chain()[1]), client_b->view_ref_koid);
// Client "A" destroys its scene.
client_a->RunNow([test = this, state = client_a.get()](scenic::Session* session,
scenic::EntityNode* session_anchor) {
state->scene = nullptr;
test->RequestToPresent(session);
});
// Focus chain is now empty.
EXPECT_EQ(CountReceivedFocusChains(), 4u);
EXPECT_TRUE(LastFocusChain()->IsEmpty());
}
TEST_F(ThreeNodeFocusChainTest, ViewHolderDisconnectShortensFocusChain) {
// Start with a transfer of focus, from "A" to "C". Focus chain is length 3.
const zx_koid_t root = engine()->scene_graph()->view_tree().focus_chain()[0];
ViewTree::FocusChangeStatus status =
engine()->scene_graph()->RequestFocusChange(root, client_c->view_ref_koid);
RunLoopUntilIdle(); // Process FIDL messages.
EXPECT_EQ(status, ViewTree::FocusChangeStatus::kAccept);
EXPECT_EQ(CountReceivedFocusChains(), 2u);
EXPECT_EQ(LastFocusChain()->focus_chain().size(), 3u);
EXPECT_EQ(ExtractKoid(LastFocusChain()->focus_chain()[0]), root);
EXPECT_EQ(ExtractKoid(LastFocusChain()->focus_chain()[1]), client_b->view_ref_koid);
EXPECT_EQ(ExtractKoid(LastFocusChain()->focus_chain()[2]), client_c->view_ref_koid);
// Disconnect (but don't destroy) "B"'s view holder for "C".
client_b->RunNow([test = this, state = client_b.get()](scenic::Session* session,
scenic::EntityNode* session_anchor) {
session_anchor->DetachChildren();
test->RequestToPresent(session);
});
EXPECT_EQ(CountReceivedFocusChains(), 3u);
EXPECT_EQ(LastFocusChain()->focus_chain().size(), 2u);
EXPECT_EQ(ExtractKoid(LastFocusChain()->focus_chain()[0]), root);
EXPECT_EQ(ExtractKoid(LastFocusChain()->focus_chain()[1]), client_b->view_ref_koid);
}
TEST_F(FocusChainTest, LateViewConnectTriggersViewTreeUpdate) {
struct ParentClient : public SessionWrapper {
ParentClient(scenic_impl::Scenic* scenic, ViewFocuserRequest view_focuser_request)
: SessionWrapper(scenic, std::move(view_focuser_request)) {}
std::unique_ptr<scenic::Compositor> compositor;
std::unique_ptr<scenic::ViewHolder> holder_child;
};
struct ChildClient : public SessionWrapper {
ChildClient(scenic_impl::Scenic* scenic) : SessionWrapper(scenic) {}
std::unique_ptr<scenic::View> view;
};
ViewFocuserPtr parent_focuser;
ParentClient parent_client(scenic(), parent_focuser.NewRequest());
ChildClient child_client(scenic());
auto token_pair = scenic::ViewTokenPair::New(); // parent-child view tokens
auto child_refs = scenic::ViewRefPair::New(); // child view's view ref pair
ViewRef target;
fidl::Clone(child_refs.view_ref, &target);
parent_client.RunNow([test = this, state = &parent_client](scenic::Session* session,
scenic::EntityNode* session_anchor) {
// Minimal scene, but without a ViewHolder.
state->compositor = std::make_unique<scenic::Compositor>(session);
scenic::LayerStack layer_stack(session);
state->compositor->SetLayerStack(layer_stack);
scenic::Layer layer(session);
layer.SetSize(9 /*px*/, 9 /*px*/);
layer_stack.AddLayer(layer);
scenic::Renderer renderer(session);
layer.SetRenderer(renderer);
scenic::Scene scene(session);
scenic::Camera camera(scene);
renderer.SetCamera(camera);
scene.AddChild(*session_anchor);
test->RequestToPresent(session);
});
EXPECT_EQ(CountReceivedFocusChains(), 1u);
child_client.RunNow(
[test = this, state = &child_client, child_token = std::move(token_pair.view_token),
control_ref = std::move(child_refs.control_ref), view_ref = std::move(child_refs.view_ref)](
scenic::Session* session, scenic::EntityNode*) mutable {
state->view =
std::make_unique<scenic::View>(session, std::move(child_token), std::move(control_ref),
std::move(view_ref), "child view");
test->RequestToPresent(session);
});
EXPECT_EQ(CountReceivedFocusChains(), 1u);
parent_client.RunNow(
[test = this, state = &parent_client, parent_token = std::move(token_pair.view_holder_token)](
scenic::Session* session, scenic::EntityNode* session_anchor) mutable {
const std::array<float, 3> kZero = {0, 0, 0};
state->holder_child =
std::make_unique<scenic::ViewHolder>(session, std::move(parent_token), "child holder");
state->holder_child->SetViewProperties(kZero, {9, 9, 1}, kZero, kZero);
session_anchor->Attach(*state->holder_child);
test->RequestToPresent(session);
});
// TODO(42737): Remove when session update logic guarantees view tree updates in every session.
child_client.RunNow([test = this](scenic::Session* session, scenic::EntityNode* session_anchor) {
test->RequestToPresent(session);
});
EXPECT_TRUE(RequestFocusChange(&parent_focuser, target));
EXPECT_EQ(CountReceivedFocusChains(), 2u);
}
} // namespace src_ui_scenic_lib_gfx_tests