blob: dd82cca2082ff89d551a5185ba3fcde218ab4367 [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 focus transfer functionality of fuchsia.ui.views.Focuser. In particular,
// a request may be performed at various points along the resource lifecycle timeline of both
// requestor and requestee. We use the FocusChainListener as the introspection mechanism for
// checking whether a request has been honored or denied.
//
// Policy exercises are tested elsewhere.
//
// The geometry is not important in this test, so surface geometries will overlap on a 5 x 5 pixel
// layer. We use the following two-node tree topology:
// parent
// |
// child
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 FocusTransferTest : public scenic_impl::gfx::test::GfxSystemTest, public FocusChainListener {
protected:
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::Renderer> renderer;
std::unique_ptr<scenic::Scene> scene; // Implicitly has the root ViewRef.
std::unique_ptr<scenic::Camera> camera;
std::unique_ptr<scenic::ViewHolder> holder_child;
};
struct ChildClient : public SessionWrapper {
ChildClient(scenic_impl::Scenic* scenic) : SessionWrapper(scenic) {}
std::unique_ptr<scenic::View> view;
};
FocusTransferTest() : focus_chain_listener_(this) {}
~FocusTransferTest() 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_;
};
TEST_F(FocusTransferTest, RequestValidity_NoRequestorNoRequest) {
ViewFocuserPtr parent_focuser;
ParentClient parent_client(scenic(), parent_focuser.NewRequest());
auto child_refs = scenic::ViewRefPair::New(); // child view's view ref pair
ViewRef target;
fidl::Clone(child_refs.view_ref, &target);
//
// Action: Initial setup with no scene.
// Expect, with focus change request: no focus change, no focus chain
//
parent_client.RunNow([test = this, state = &parent_client](scenic::Session* session,
scenic::EntityNode* session_anchor) {
// Start setting up scene, but don't actually create the scene yet.
state->compositor = std::make_unique<scenic::Compositor>(session);
scenic::LayerStack layer_stack(session);
state->compositor->SetLayerStack(layer_stack);
scenic::Layer layer(session);
layer.SetSize(5 /*px*/, 5 /*px*/);
layer_stack.AddLayer(layer);
state->renderer = std::make_unique<scenic::Renderer>(session);
layer.SetRenderer(*state->renderer);
test->RequestToPresent(session);
});
EXPECT_FALSE(RequestFocusChange(&parent_focuser, target));
}
TEST_F(FocusTransferTest, RequestValidity_RequestorCreatedNoRequest) {
ViewFocuserPtr parent_focuser;
ParentClient parent_client(scenic(), parent_focuser.NewRequest());
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) {
state->compositor = std::make_unique<scenic::Compositor>(session);
scenic::LayerStack layer_stack(session);
state->compositor->SetLayerStack(layer_stack);
scenic::Layer layer(session);
layer.SetSize(5 /*px*/, 5 /*px*/);
layer_stack.AddLayer(layer);
state->renderer = std::make_unique<scenic::Renderer>(session);
layer.SetRenderer(*state->renderer);
// Create scene
state->scene = std::make_unique<scenic::Scene>(session);
test->RequestToPresent(session);
});
EXPECT_FALSE(RequestFocusChange(&parent_focuser, target));
}
TEST_F(FocusTransferTest, RequestValidity_RequestorConnectedNoRequest) {
ViewFocuserPtr parent_focuser;
ParentClient parent_client(scenic(), parent_focuser.NewRequest());
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) {
state->compositor = std::make_unique<scenic::Compositor>(session);
scenic::LayerStack layer_stack(session);
state->compositor->SetLayerStack(layer_stack);
scenic::Layer layer(session);
layer.SetSize(5 /*px*/, 5 /*px*/);
layer_stack.AddLayer(layer);
state->renderer = std::make_unique<scenic::Renderer>(session);
layer.SetRenderer(*state->renderer);
// Create scene
state->scene = std::make_unique<scenic::Scene>(session);
// Connect scene
scenic::Camera camera(*state->scene);
state->renderer->SetCamera(camera);
state->scene->AddChild(*session_anchor);
test->RequestToPresent(session);
});
EXPECT_EQ(CountReceivedFocusChains(), 1u); // A lifecycle event tied to scene creation.
EXPECT_FALSE(RequestFocusChange(&parent_focuser, target));
EXPECT_EQ(CountReceivedFocusChains(), 1u);
}
TEST_F(FocusTransferTest, RequestValidity_RequestorConnectedRequestCreated) {
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) {
state->compositor = std::make_unique<scenic::Compositor>(session);
scenic::LayerStack layer_stack(session);
state->compositor->SetLayerStack(layer_stack);
scenic::Layer layer(session);
layer.SetSize(5 /*px*/, 5 /*px*/);
layer_stack.AddLayer(layer);
state->renderer = std::make_unique<scenic::Renderer>(session);
layer.SetRenderer(*state->renderer);
// Create scene
state->scene = std::make_unique<scenic::Scene>(session);
// Connect scene
scenic::Camera camera(*state->scene);
state->renderer->SetCamera(camera);
state->scene->AddChild(*session_anchor);
test->RequestToPresent(session);
});
//
// Action: Create child view, the target, but don't connect it to Scene via view holder.
// Expect, with focus change request: no focus change, no focus chain.
//
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_FALSE(RequestFocusChange(&parent_focuser, target));
EXPECT_EQ(CountReceivedFocusChains(), 1u);
}
TEST_F(FocusTransferTest, RequestValidity_RequestorConnectedRequestCreatedViewholderCreated) {
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) {
state->compositor = std::make_unique<scenic::Compositor>(session);
scenic::LayerStack layer_stack(session);
state->compositor->SetLayerStack(layer_stack);
scenic::Layer layer(session);
layer.SetSize(5 /*px*/, 5 /*px*/);
layer_stack.AddLayer(layer);
state->renderer = std::make_unique<scenic::Renderer>(session);
layer.SetRenderer(*state->renderer);
// Create scene
state->scene = std::make_unique<scenic::Scene>(session);
// Connect scene
scenic::Camera camera(*state->scene);
state->renderer->SetCamera(camera);
state->scene->AddChild(*session_anchor);
test->RequestToPresent(session);
});
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);
});
//
// Action: Create view holder, but don't connect it to Scene.
// Expect, with focus change request: no focus change, no focus chain.
//
parent_client.RunNow(
[test = this, state = &parent_client, parent_token = std::move(token_pair.view_holder_token)](
scenic::Session* session, scenic::EntityNode*) 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, {5, 5, 1}, kZero, kZero);
test->RequestToPresent(session);
});
EXPECT_FALSE(RequestFocusChange(&parent_focuser, target));
EXPECT_EQ(CountReceivedFocusChains(), 1u);
}
TEST_F(FocusTransferTest, RequestValidity_RequestorConnectedRequestConnected) {
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) {
state->compositor = std::make_unique<scenic::Compositor>(session);
scenic::LayerStack layer_stack(session);
state->compositor->SetLayerStack(layer_stack);
scenic::Layer layer(session);
layer.SetSize(5 /*px*/, 5 /*px*/);
layer_stack.AddLayer(layer);
state->renderer = std::make_unique<scenic::Renderer>(session);
layer.SetRenderer(*state->renderer);
// Created scene
state->scene = std::make_unique<scenic::Scene>(session);
// Connect scene
scenic::Camera camera(*state->scene);
state->renderer->SetCamera(camera);
state->scene->AddChild(*session_anchor);
test->RequestToPresent(session);
});
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);
});
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, {5, 5, 1}, kZero, kZero);
//
// Action: Connect view holder to Scene.
// Expect, with focus change request: focus change, with new focus chain.
//
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));
ASSERT_EQ(CountReceivedFocusChains(), 2u);
ASSERT_TRUE(LastFocusChain()->has_focus_chain());
ASSERT_EQ(LastFocusChain()->focus_chain().size(), 2u);
EXPECT_EQ(ExtractKoid(LastFocusChain()->focus_chain()[1]), ExtractKoid(target));
}
TEST_F(FocusTransferTest, ViewFocuserDisconnectedWhenSessionDies) {
ViewFocuserPtr parent_focuser;
ASSERT_FALSE(parent_focuser);
{
// Scope limits client lifetime.
ParentClient parent_client(scenic(), parent_focuser.NewRequest());
RunLoopUntilIdle();
ASSERT_TRUE(parent_focuser);
}
RunLoopUntilIdle();
// Client death guarantees focuser disconnect.
ASSERT_FALSE(parent_focuser);
}
TEST_F(FocusTransferTest, ViewFocuserDisconnectDoesNotKillSession) {
ViewFocuserPtr parent_focuser;
ASSERT_FALSE(parent_focuser);
ParentClient parent_client(scenic(), parent_focuser.NewRequest());
parent_client.session()->set_error_handler(
[](zx_status_t) { GTEST_FAIL() << "Client shut down unexpectedly."; });
RunLoopUntilIdle();
ASSERT_TRUE(parent_focuser);
parent_focuser.Unbind();
ASSERT_FALSE(parent_focuser);
parent_client.RunNow([test = this](scenic::Session* session, scenic::EntityNode* session_anchor) {
test->RequestToPresent(session);
});
}
} // namespace src_ui_scenic_lib_gfx_tests