blob: 680a00668cec0f91f26626b812e1fdfe5ae1c2c9 [file] [log] [blame]
// Copyright 2021 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.
// TODO(fxb.dev/98392): Remove non-cts deps, and use <> for sdk libraries.
#include "src/ui/a11y/lib/semantics/tests/semantics_integration_test_fixture.h"
#include <fuchsia/component/cpp/fidl.h>
#include <fuchsia/fonts/cpp/fidl.h>
#include <fuchsia/intl/cpp/fidl.h>
#include <fuchsia/kernel/cpp/fidl.h>
#include <fuchsia/memorypressure/cpp/fidl.h>
#include <fuchsia/net/interfaces/cpp/fidl.h>
#include <fuchsia/posix/socket/cpp/fidl.h>
#include <fuchsia/process/cpp/fidl.h>
#include <fuchsia/scheduler/cpp/fidl.h>
#include <fuchsia/sysmem/cpp/fidl.h>
#include <fuchsia/tracing/provider/cpp/fidl.h>
#include <fuchsia/ui/app/cpp/fidl.h>
#include <fuchsia/ui/composition/cpp/fidl.h>
#include <fuchsia/ui/input/cpp/fidl.h>
#include <fuchsia/ui/pointerinjector/cpp/fidl.h>
#include <fuchsia/vulkan/loader/cpp/fidl.h>
#include <lib/syslog/cpp/macros.h>
#include <zircon/status.h>
#include <zircon/types.h>
#include <cmath>
#include "lib/sys/component/cpp/testing/realm_builder_types.h"
#include "src/ui/a11y/lib/view/a11y_view_semantics.h"
#include "src/ui/testing/ui_test_realm/ui_test_realm.h"
namespace accessibility_test {
namespace {
using component_testing::Directory;
using component_testing::Protocol;
using fuchsia::accessibility::semantics::Node;
constexpr auto kDevicePixelRatio = 1.25f;
bool CompareFloat(float f0, float f1, float epsilon = 0.01f) {
return std::abs(f0 - f1) <= epsilon;
}
} // namespace
void SemanticsManagerProxy::OnStart() {
FX_CHECK(outgoing()->AddPublicService(
fidl::InterfaceRequestHandler<fuchsia::accessibility::semantics::SemanticsManager>(
[this](auto request) {
bindings_.AddBinding(this, std::move(request), dispatcher_);
})) == ZX_OK);
}
void SemanticsManagerProxy::RegisterViewForSemantics(
fuchsia::ui::views::ViewRef view_ref,
fidl::InterfaceHandle<fuchsia::accessibility::semantics::SemanticListener> handle,
fidl::InterfaceRequest<fuchsia::accessibility::semantics::SemanticTree> semantic_tree_request) {
semantics_manager_->RegisterViewForSemantics(std::move(view_ref), std::move(handle),
std::move(semantic_tree_request));
}
void SemanticsIntegrationTestV2::SetUp() {
FX_LOGS(INFO) << "Setting up test fixture";
// Initialize ui test manager.
ui_testing::UITestRealm::Config config;
config.device_pixel_ratio = kDevicePixelRatio;
config.use_scene_owner = true;
config.accessibility_owner = ui_testing::UITestRealm::AccessibilityOwnerType::FAKE;
config.ui_to_client_services = {fuchsia::ui::composition::Allocator::Name_,
fuchsia::ui::composition::Flatland::Name_};
config.passthrough_capabilities = {
Protocol{fuchsia::kernel::VmexResource::Name_},
Protocol{fuchsia::process::Launcher::Name_},
Directory{
.name = "root-ssl-certificates",
.type = fuchsia::component::decl::DependencyType::STRONG,
},
};
ui_test_manager_.emplace(config);
// Initialize ui test realm.
BuildRealm();
}
void SemanticsIntegrationTestV2::TearDown() {
bool complete = false;
ui_test_manager_->TeardownRealm(
[&](fit::result<fuchsia::component::Error> result) { complete = true; });
RunLoopUntil([&]() { return complete; });
}
void SemanticsIntegrationTestV2::BuildRealm() {
FX_LOGS(INFO) << "Building realm";
realm_ = ui_test_manager_->AddSubrealm();
view_manager_ = std::make_unique<a11y::ViewManager>(
std::make_unique<a11y::SemanticTreeServiceFactory>(),
std::make_unique<a11y::A11yViewSemanticsFactory>(),
std::make_unique<MockViewInjectorFactory>(),
std::make_unique<a11y::A11ySemanticsEventManager>(),
std::make_shared<MockAccessibilityView>(), context_.get());
realm_->AddLocalChild(SemanticsIntegrationTestV2::kSemanticsManager, [&] {
return std::make_unique<SemanticsManagerProxy>(view_manager_.get(), dispatcher());
});
// Let subclass configure Realm beyond this base setup.
ConfigureRealm();
ui_test_manager_->BuildRealm();
realm_exposed_services_ = ui_test_manager_->CloneExposedServicesDirectory();
}
void SemanticsIntegrationTestV2::SetupScene() {
ui_test_manager_->InitializeScene();
RunLoopUntil([this]() { return ui_test_manager_->ClientViewIsRendering(); });
auto view_ref_koid = ui_test_manager_->ClientViewRefKoid();
ASSERT_TRUE(view_ref_koid.has_value());
view_ref_koid_ = *view_ref_koid;
}
const Node* SemanticsIntegrationTestV2::FindNodeWithLabel(const Node* node, zx_koid_t view_ref_koid,
std::string label) {
if (!node) {
return nullptr;
}
if (node->has_attributes() && node->attributes().has_label() &&
node->attributes().label() == label) {
return node;
}
if (!node->has_child_ids()) {
return nullptr;
}
for (const auto& child_id : node->child_ids()) {
const auto* child = view_manager()->GetSemanticNode(view_ref_koid, child_id);
FX_DCHECK(child);
auto result = FindNodeWithLabel(child, view_ref_koid, label);
if (result != nullptr) {
return result;
}
}
return nullptr;
}
a11y::SemanticTransform SemanticsIntegrationTestV2::GetTransformForNode(zx_koid_t view_ref_koid,
uint32_t node_id) {
std::vector<const Node*> path;
// Perform a DFS to find the path to the target node
std::function<bool(const Node*)> traverse = [&](const Node* node) {
if (node->node_id() == node_id) {
path.push_back(node);
return true;
}
if (!node->has_child_ids()) {
return false;
}
for (const auto& child_id : node->child_ids()) {
const auto* child = view_manager()->GetSemanticNode(view_ref_koid, child_id);
FX_DCHECK(child);
if (traverse(child)) {
path.push_back(node);
return true;
}
}
return false;
};
auto root = view_manager()->GetSemanticNode(view_ref_koid, 0u);
traverse(root);
a11y::SemanticTransform transform;
for (auto& node : path) {
if (node->has_transform()) {
transform.ChainLocalTransform(node->transform());
}
}
return transform;
}
std::optional<uint32_t> SemanticsIntegrationTestV2::HitTest(zx_koid_t view_ref_koid,
fuchsia::math::PointF target) {
std::optional<fuchsia::accessibility::semantics::Hit> target_hit;
FX_LOGS(INFO) << "target is: " << target.x << ":" << target.y;
auto hit_callback = [&target_hit](fuchsia::accessibility::semantics::Hit hit) {
target_hit = std::move(hit);
};
view_manager()->ExecuteHitTesting(view_ref_koid, target, hit_callback);
RunLoopUntil([&target_hit] { return target_hit.has_value(); });
if (!target_hit.has_value() || !target_hit->has_node_id()) {
return std::nullopt;
}
return target_hit->node_id();
}
fuchsia::math::PointF
SemanticsIntegrationTestV2::CalculateCenterOfSemanticNodeBoundingBoxCoordinate(
zx_koid_t view_ref_koid, const fuchsia::accessibility::semantics::Node* node) {
// Semantic trees may have transforms in each node. That transform defines the spatial relation
// between coordinates in the node's space to coordinates in it's parent's space. This is done
// to enable semantic providers to avoid recomputing location information on every child node
// when a parent node (or the entire view) undergoes a spatial change.
// Get the transform from the node's local space to the view's local space.
auto transform = view_manager()->GetNodeToRootTransform(view_ref_koid, node->node_id());
FX_DCHECK(transform) << "Could not compute a transform for the semantic node: " << view_ref_koid
<< ":" << node->node_id();
const auto node_bounding_box = node->location();
const auto node_bounding_box_center_x = (node_bounding_box.min.x + node_bounding_box.max.x) / 2.f;
const auto node_bounding_box_center_y = (node_bounding_box.min.y + node_bounding_box.max.y) / 2.f;
const fuchsia::ui::gfx::vec3 node_bounding_box_center_local = {node_bounding_box_center_x,
node_bounding_box_center_y, 0.f};
const fuchsia::ui::gfx::vec3 node_bounding_box_center_root =
transform->Apply(node_bounding_box_center_local);
return {node_bounding_box_center_root.x, node_bounding_box_center_root.y};
}
bool SemanticsIntegrationTestV2::PerformAccessibilityAction(
zx_koid_t view_ref_koid, uint32_t node_id, fuchsia::accessibility::semantics::Action action) {
std::optional<bool> callback_handled;
auto callback = [&callback_handled](bool handled) { callback_handled = handled; };
view_manager()->PerformAccessibilityAction(view_ref_koid, node_id, action, callback);
RunLoopUntil([&callback_handled] { return callback_handled.has_value(); });
return *callback_handled;
}
void SemanticsIntegrationTestV2::WaitForScaleFactor() {
RunLoopUntil([this] {
auto node = view_manager()->GetSemanticNode(view_ref_koid(), 0u);
if (!node) {
return false;
}
// The root node applies a transform to convert between the client's
// allocated and logical coordinate spaces. The scale of this transform is
// the inverse of the pixel scale for the view.
// TODO(fxb.dev/93943): Remove accommodation for transform field.
return (node->has_transform() &&
CompareFloat(node->transform().matrix[0], 1 / kDevicePixelRatio)) ||
(node->has_node_to_container_transform() &&
CompareFloat(node->node_to_container_transform().matrix[0], 1 / kDevicePixelRatio));
});
}
} // namespace accessibility_test