blob: 652c7ae48990c38f9e99074e3fecf5e78ba32eab [file] [log] [blame]
// Copyright 2019 The Chromium 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/engine/browser/ax_tree_converter.h"
#include <fuchsia/math/cpp/fidl.h>
#include <fuchsia/ui/gfx/cpp/fidl.h>
#include <fuchsia/ui/views/cpp/fidl.h>
#include <lib/ui/scenic/cpp/commands.h>
#include <stdint.h>
#include <vector>
#include "base/bit_cast.h"
#include "base/numerics/safe_conversions.h"
#include "ui/accessibility/ax_enums.mojom.h"
#include "ui/accessibility/ax_tree_id.h"
#include "ui/gfx/geometry/rect_f.h"
using fuchsia::accessibility::semantics::MAX_LABEL_SIZE;
namespace {
// Fuchsia's default root node ID.
constexpr uint32_t kFuchsiaRootNodeId = 0;
// Remmaped value for an AxNode ID that is 0 and is not a root.
// Value is chosen specifically to be outside the range of a 32-bit signed int,
// so as to not conflict with other values used by Chromium.
constexpr uint32_t kZeroIdRemappedForFuchsia = 1u + INT32_MAX;
fuchsia::accessibility::semantics::Role ConvertRole(ax::mojom::Role role) {
if (role == ax::mojom::Role::kButton)
return fuchsia::accessibility::semantics::Role::BUTTON;
if (role == ax::mojom::Role::kHeader)
return fuchsia::accessibility::semantics::Role::HEADER;
if (role == ax::mojom::Role::kImage)
return fuchsia::accessibility::semantics::Role::IMAGE;
if (role == ax::mojom::Role::kSlider)
return fuchsia::accessibility::semantics::Role::SLIDER;
if (role == ax::mojom::Role::kTextField)
return fuchsia::accessibility::semantics::Role::TEXT_FIELD;
return fuchsia::accessibility::semantics::Role::UNKNOWN;
}
fuchsia::accessibility::semantics::Attributes ConvertAttributes(
const ui::AXNodeData& node) {
fuchsia::accessibility::semantics::Attributes attributes;
if (node.HasStringAttribute(ax::mojom::StringAttribute::kName)) {
const std::string& name =
node.GetStringAttribute(ax::mojom::StringAttribute::kName);
attributes.set_label(name.substr(0, MAX_LABEL_SIZE));
}
if (node.HasStringAttribute(ax::mojom::StringAttribute::kDescription)) {
const std::string& description =
node.GetStringAttribute(ax::mojom::StringAttribute::kDescription);
attributes.set_secondary_label(description.substr(0, MAX_LABEL_SIZE));
}
if (node.IsRangeValueSupported()) {
fuchsia::accessibility::semantics::RangeAttributes range_attributes;
if (node.HasFloatAttribute(ax::mojom::FloatAttribute::kMinValueForRange)) {
range_attributes.set_min_value(
node.GetFloatAttribute(ax::mojom::FloatAttribute::kMinValueForRange));
}
if (node.HasFloatAttribute(ax::mojom::FloatAttribute::kMaxValueForRange)) {
range_attributes.set_max_value(
node.GetFloatAttribute(ax::mojom::FloatAttribute::kMaxValueForRange));
}
if (node.HasFloatAttribute(ax::mojom::FloatAttribute::kStepValueForRange)) {
range_attributes.set_step_delta(node.GetFloatAttribute(
ax::mojom::FloatAttribute::kStepValueForRange));
}
attributes.set_range(std::move(range_attributes));
}
return attributes;
}
// This function handles conversions for all data that is part of a Semantic
// Node's state. The corresponding data in an AXNodeData is stored in various
// attributes.
fuchsia::accessibility::semantics::States ConvertStates(
const ui::AXNodeData& node) {
fuchsia::accessibility::semantics::States states;
// Converts checked state of a node.
if (node.HasIntAttribute(ax::mojom::IntAttribute::kCheckedState)) {
ax::mojom::CheckedState ax_state = node.GetCheckedState();
switch (ax_state) {
case ax::mojom::CheckedState::kNone:
states.set_checked_state(
fuchsia::accessibility::semantics::CheckedState::NONE);
break;
case ax::mojom::CheckedState::kTrue:
states.set_checked_state(
fuchsia::accessibility::semantics::CheckedState::CHECKED);
break;
case ax::mojom::CheckedState::kFalse:
states.set_checked_state(
fuchsia::accessibility::semantics::CheckedState::UNCHECKED);
break;
case ax::mojom::CheckedState::kMixed:
states.set_checked_state(
fuchsia::accessibility::semantics::CheckedState::MIXED);
break;
}
}
// Indicates whether a node has been selected.
if (node.HasBoolAttribute(ax::mojom::BoolAttribute::kSelected)) {
states.set_selected(
node.GetBoolAttribute(ax::mojom::BoolAttribute::kSelected));
}
// Indicates if the node is hidden.
states.set_hidden(node.IsIgnored());
// The user entered value of the node, if applicable.
if (node.HasStringAttribute(ax::mojom::StringAttribute::kValue)) {
const std::string& value =
node.GetStringAttribute(ax::mojom::StringAttribute::kValue);
states.set_value(value.substr(0, MAX_LABEL_SIZE));
}
// The value a range element currently has.
if (node.HasFloatAttribute(ax::mojom::FloatAttribute::kValueForRange)) {
states.set_range_value(
node.GetFloatAttribute(ax::mojom::FloatAttribute::kValueForRange));
}
return states;
}
std::vector<fuchsia::accessibility::semantics::Action> ConvertActions(
const ui::AXNodeData& node) {
std::vector<fuchsia::accessibility::semantics::Action> fuchsia_actions;
const bool has_default =
node.HasAction(ax::mojom::Action::kDoDefault) ||
node.GetDefaultActionVerb() != ax::mojom::DefaultActionVerb::kNone;
if (has_default) {
fuchsia_actions.push_back(
fuchsia::accessibility::semantics::Action::DEFAULT);
}
if (node.HasAction(ax::mojom::Action::kFocus)) {
fuchsia_actions.push_back(
fuchsia::accessibility::semantics::Action::SET_FOCUS);
}
if (node.HasAction(ax::mojom::Action::kSetValue)) {
fuchsia_actions.push_back(
fuchsia::accessibility::semantics::Action::SET_VALUE);
}
if (node.HasAction(ax::mojom::Action::kScrollToMakeVisible)) {
fuchsia_actions.push_back(
fuchsia::accessibility::semantics::Action::SHOW_ON_SCREEN);
}
return fuchsia_actions;
}
std::vector<uint32_t> ConvertChildIds(std::vector<int32_t> ids) {
std::vector<uint32_t> child_ids;
child_ids.reserve(ids.size());
for (auto i : ids) {
child_ids.push_back(base::checked_cast<uint32_t>(i));
}
return child_ids;
}
fuchsia::ui::gfx::BoundingBox ConvertBoundingBox(gfx::RectF bounds) {
fuchsia::ui::gfx::BoundingBox box;
box.min = scenic::NewVector3({bounds.bottom_left().x(),
bounds.bottom_left().y(), 0.0f});
box.max = scenic::NewVector3({bounds.top_right().x(), bounds.top_right().y(),
0.0f});
return box;
}
// The Semantics Manager applies this matrix to position the node and its
// subtree as an optimization to handle resizing or repositioning. This requires
// only one node to be updated on such an event.
fuchsia::ui::gfx::mat4 ConvertTransform(gfx::Transform* transform) {
std::array<float, 16> mat = {};
transform->matrix().asColMajorf(mat.data());
fuchsia::ui::gfx::Matrix4Value fuchsia_transform =
scenic::NewMatrix4Value(mat);
return fuchsia_transform.value;
}
} // namespace
fuchsia::accessibility::semantics::Node AXNodeDataToSemanticNode(
const ui::AXNodeData& node) {
fuchsia::accessibility::semantics::Node fuchsia_node;
fuchsia_node.set_node_id(base::checked_cast<uint32_t>(node.id));
fuchsia_node.set_role(ConvertRole(node.role));
fuchsia_node.set_states(ConvertStates(node));
fuchsia_node.set_attributes(ConvertAttributes(node));
fuchsia_node.set_actions(ConvertActions(node));
fuchsia_node.set_child_ids(ConvertChildIds(node.child_ids));
fuchsia_node.set_location(ConvertBoundingBox(node.relative_bounds.bounds));
if (node.relative_bounds.transform) {
fuchsia_node.set_transform(
ConvertTransform(node.relative_bounds.transform.get()));
}
return fuchsia_node;
}
bool ConvertAction(fuchsia::accessibility::semantics::Action fuchsia_action,
ax::mojom::Action* mojom_action) {
switch (fuchsia_action) {
case fuchsia::accessibility::semantics::Action::DEFAULT:
*mojom_action = ax::mojom::Action::kDoDefault;
return true;
case fuchsia::accessibility::semantics::Action::DECREMENT:
*mojom_action = ax::mojom::Action::kDecrement;
return true;
case fuchsia::accessibility::semantics::Action::INCREMENT:
*mojom_action = ax::mojom::Action::kIncrement;
return true;
case fuchsia::accessibility::semantics::Action::SHOW_ON_SCREEN:
*mojom_action = ax::mojom::Action::kScrollToMakeVisible;
return true;
case fuchsia::accessibility::semantics::Action::SECONDARY:
case fuchsia::accessibility::semantics::Action::SET_FOCUS:
case fuchsia::accessibility::semantics::Action::SET_VALUE:
return false;
default:
LOG(WARNING)
<< "Unknown fuchsia::accessibility::semantics::Action with value "
<< static_cast<int>(fuchsia_action);
return false;
}
}
uint32_t ConvertToFuchsiaNodeId(int32_t ax_node_id, int32_t ax_root_node_id) {
if (ax_node_id == ax_root_node_id)
return kFuchsiaRootNodeId;
if (ax_node_id == kFuchsiaRootNodeId) {
// This AxNode is not the root, but its ID is the same as Fuchsia's root ID.
// We remap it to something different to not create a confflict.
return kZeroIdRemappedForFuchsia;
}
return base::checked_cast<uint32_t>(ax_node_id);
}