ax_tree_converter.cc 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409
  1. // Copyright 2019 The Chromium Authors. All rights reserved.
  2. // Use of this source code is governed by a BSD-style license that can be
  3. // found in the LICENSE file.
  4. #include "fuchsia_web/webengine/browser/ax_tree_converter.h"
  5. #include <fuchsia/math/cpp/fidl.h>
  6. #include <fuchsia/ui/gfx/cpp/fidl.h>
  7. #include <fuchsia/ui/views/cpp/fidl.h>
  8. #include <lib/ui/scenic/cpp/commands.h>
  9. #include <stdint.h>
  10. #include <vector>
  11. #include "base/numerics/safe_conversions.h"
  12. #include "ui/accessibility/ax_enums.mojom.h"
  13. #include "ui/accessibility/ax_tree_id.h"
  14. #include "ui/gfx/geometry/rect_f.h"
  15. namespace {
  16. using AXRole = ax::mojom::Role;
  17. using fuchsia::accessibility::semantics::MAX_LABEL_SIZE;
  18. using fuchsia::accessibility::semantics::Role;
  19. // Fuchsia's default root node ID.
  20. constexpr uint32_t kFuchsiaRootNodeId = 0;
  21. fuchsia::accessibility::semantics::Attributes ConvertAttributes(
  22. const ui::AXNode& node) {
  23. fuchsia::accessibility::semantics::Attributes attributes;
  24. if (node.HasStringAttribute(ax::mojom::StringAttribute::kName)) {
  25. const std::string& name =
  26. node.GetStringAttribute(ax::mojom::StringAttribute::kName);
  27. attributes.set_label(name.substr(0, MAX_LABEL_SIZE));
  28. }
  29. if (node.HasStringAttribute(ax::mojom::StringAttribute::kDescription)) {
  30. const std::string& description =
  31. node.GetStringAttribute(ax::mojom::StringAttribute::kDescription);
  32. attributes.set_secondary_label(description.substr(0, MAX_LABEL_SIZE));
  33. }
  34. if (node.data().IsRangeValueSupported()) {
  35. fuchsia::accessibility::semantics::RangeAttributes range_attributes;
  36. if (node.HasFloatAttribute(ax::mojom::FloatAttribute::kMinValueForRange)) {
  37. range_attributes.set_min_value(
  38. node.GetFloatAttribute(ax::mojom::FloatAttribute::kMinValueForRange));
  39. }
  40. if (node.HasFloatAttribute(ax::mojom::FloatAttribute::kMaxValueForRange)) {
  41. range_attributes.set_max_value(
  42. node.GetFloatAttribute(ax::mojom::FloatAttribute::kMaxValueForRange));
  43. }
  44. if (node.HasFloatAttribute(ax::mojom::FloatAttribute::kStepValueForRange)) {
  45. range_attributes.set_step_delta(node.GetFloatAttribute(
  46. ax::mojom::FloatAttribute::kStepValueForRange));
  47. }
  48. attributes.set_range(std::move(range_attributes));
  49. }
  50. if (node.IsTable()) {
  51. fuchsia::accessibility::semantics::TableAttributes table_attributes;
  52. auto col_count = node.GetTableColCount();
  53. if (col_count)
  54. table_attributes.set_number_of_columns(*col_count);
  55. auto row_count = node.GetTableRowCount();
  56. if (row_count)
  57. table_attributes.set_number_of_rows(*row_count);
  58. if (!table_attributes.IsEmpty())
  59. attributes.set_table_attributes(std::move(table_attributes));
  60. }
  61. if (node.IsTableRow()) {
  62. fuchsia::accessibility::semantics::TableRowAttributes table_row_attributes;
  63. auto row_index = node.GetTableRowRowIndex();
  64. if (row_index) {
  65. table_row_attributes.set_row_index(*row_index);
  66. attributes.set_table_row_attributes(std::move(table_row_attributes));
  67. }
  68. }
  69. if (node.IsTableCellOrHeader()) {
  70. fuchsia::accessibility::semantics::TableCellAttributes
  71. table_cell_attributes;
  72. auto col_index = node.GetTableCellColIndex();
  73. if (col_index)
  74. table_cell_attributes.set_column_index(*col_index);
  75. auto row_index = node.GetTableCellRowIndex();
  76. if (row_index)
  77. table_cell_attributes.set_row_index(*row_index);
  78. auto col_span = node.GetTableCellColSpan();
  79. if (col_span)
  80. table_cell_attributes.set_column_span(*col_span);
  81. auto row_span = node.GetTableCellRowSpan();
  82. if (row_span)
  83. table_cell_attributes.set_row_span(*row_span);
  84. if (!table_cell_attributes.IsEmpty())
  85. attributes.set_table_cell_attributes(std::move(table_cell_attributes));
  86. }
  87. return attributes;
  88. }
  89. // Converts an AXRole to a Fuchsia Role.
  90. Role AxRoleToFuchsiaSemanticRole(AXRole role) {
  91. switch (role) {
  92. case AXRole::kButton:
  93. return Role::BUTTON;
  94. case AXRole::kCheckBox:
  95. return Role::CHECK_BOX;
  96. case AXRole::kHeader:
  97. return Role::HEADER;
  98. case AXRole::kImage:
  99. return Role::IMAGE;
  100. case AXRole::kLink:
  101. return Role::LINK;
  102. case AXRole::kRadioButton:
  103. return Role::RADIO_BUTTON;
  104. case AXRole::kSlider:
  105. return Role::SLIDER;
  106. case AXRole::kTextField:
  107. return Role::TEXT_FIELD;
  108. case AXRole::kStaticText:
  109. return Role::STATIC_TEXT;
  110. case AXRole::kSearchBox:
  111. return Role::SEARCH_BOX;
  112. case AXRole::kTextFieldWithComboBox:
  113. return Role::TEXT_FIELD_WITH_COMBO_BOX;
  114. case AXRole::kTable:
  115. return Role::TABLE;
  116. case AXRole::kGrid:
  117. return Role::GRID;
  118. case AXRole::kRow:
  119. return Role::TABLE_ROW;
  120. case AXRole::kCell:
  121. return Role::CELL;
  122. case AXRole::kColumnHeader:
  123. return Role::COLUMN_HEADER;
  124. case AXRole::kRowGroup:
  125. return Role::ROW_GROUP;
  126. case AXRole::kParagraph:
  127. return Role::PARAGRAPH;
  128. default:
  129. return Role::UNKNOWN;
  130. }
  131. }
  132. // This function handles conversions for all data that is part of a Semantic
  133. // Node's state. The corresponding data in an AXNodeData is stored in various
  134. // attributes.
  135. fuchsia::accessibility::semantics::States ConvertStates(
  136. const ui::AXNodeData& node) {
  137. fuchsia::accessibility::semantics::States states;
  138. // Converts checked state of a node.
  139. if (node.HasIntAttribute(ax::mojom::IntAttribute::kCheckedState)) {
  140. ax::mojom::CheckedState ax_state = node.GetCheckedState();
  141. switch (ax_state) {
  142. case ax::mojom::CheckedState::kNone:
  143. states.set_checked_state(
  144. fuchsia::accessibility::semantics::CheckedState::NONE);
  145. break;
  146. case ax::mojom::CheckedState::kTrue:
  147. states.set_checked_state(
  148. fuchsia::accessibility::semantics::CheckedState::CHECKED);
  149. break;
  150. case ax::mojom::CheckedState::kFalse:
  151. states.set_checked_state(
  152. fuchsia::accessibility::semantics::CheckedState::UNCHECKED);
  153. break;
  154. case ax::mojom::CheckedState::kMixed:
  155. states.set_checked_state(
  156. fuchsia::accessibility::semantics::CheckedState::MIXED);
  157. break;
  158. }
  159. }
  160. // Indicates whether a node has been selected.
  161. if (node.HasBoolAttribute(ax::mojom::BoolAttribute::kSelected)) {
  162. states.set_selected(
  163. node.GetBoolAttribute(ax::mojom::BoolAttribute::kSelected));
  164. }
  165. // Indicates if the node is hidden.
  166. states.set_hidden(node.IsIgnored() || node.IsInvisible());
  167. // The user entered value of the node, if applicable.
  168. if (node.HasStringAttribute(ax::mojom::StringAttribute::kValue)) {
  169. const std::string& value =
  170. node.GetStringAttribute(ax::mojom::StringAttribute::kValue);
  171. states.set_value(value.substr(0, MAX_LABEL_SIZE));
  172. }
  173. // The value a range element currently has.
  174. if (node.HasFloatAttribute(ax::mojom::FloatAttribute::kValueForRange)) {
  175. states.set_range_value(
  176. node.GetFloatAttribute(ax::mojom::FloatAttribute::kValueForRange));
  177. }
  178. // The scroll offsets, if the element is a scrollable container.
  179. const float x_scroll_offset =
  180. node.GetIntAttribute(ax::mojom::IntAttribute::kScrollX);
  181. const float y_scroll_offset =
  182. node.GetIntAttribute(ax::mojom::IntAttribute::kScrollY);
  183. if (x_scroll_offset || y_scroll_offset)
  184. states.set_viewport_offset({x_scroll_offset, y_scroll_offset});
  185. if (node.HasState(ax::mojom::State::kFocusable))
  186. states.set_focusable(true);
  187. return states;
  188. }
  189. std::vector<fuchsia::accessibility::semantics::Action> ConvertActions(
  190. const ui::AXNodeData& node) {
  191. std::vector<fuchsia::accessibility::semantics::Action> fuchsia_actions;
  192. const bool has_default =
  193. node.HasAction(ax::mojom::Action::kDoDefault) ||
  194. node.GetDefaultActionVerb() != ax::mojom::DefaultActionVerb::kNone;
  195. if (has_default) {
  196. fuchsia_actions.push_back(
  197. fuchsia::accessibility::semantics::Action::DEFAULT);
  198. }
  199. if (node.HasAction(ax::mojom::Action::kFocus)) {
  200. fuchsia_actions.push_back(
  201. fuchsia::accessibility::semantics::Action::SET_FOCUS);
  202. }
  203. if (node.HasAction(ax::mojom::Action::kSetValue)) {
  204. fuchsia_actions.push_back(
  205. fuchsia::accessibility::semantics::Action::SET_VALUE);
  206. }
  207. if (node.HasAction(ax::mojom::Action::kScrollToMakeVisible)) {
  208. fuchsia_actions.push_back(
  209. fuchsia::accessibility::semantics::Action::SHOW_ON_SCREEN);
  210. }
  211. return fuchsia_actions;
  212. }
  213. std::vector<uint32_t> ConvertChildIds(std::vector<int32_t> ids,
  214. const ui::AXTreeID& tree_id,
  215. NodeIDMapper* id_mapper) {
  216. std::vector<uint32_t> child_ids;
  217. child_ids.reserve(ids.size());
  218. for (const auto& i : ids) {
  219. child_ids.push_back(id_mapper->ToFuchsiaNodeID(tree_id, i, false));
  220. }
  221. return child_ids;
  222. }
  223. fuchsia::ui::gfx::BoundingBox ConvertBoundingBox(gfx::RectF bounds) {
  224. fuchsia::ui::gfx::BoundingBox box;
  225. // Since the origin is at the top left, min should represent the top left and
  226. // max should be the bottom right.
  227. box.min = scenic::NewVector3({bounds.x(), bounds.y(), 0.0f});
  228. box.max = scenic::NewVector3({bounds.right(), bounds.bottom(), 0.0f});
  229. return box;
  230. }
  231. // The Semantics Manager applies this matrix to position the node and its
  232. // subtree as an optimization to handle resizing or repositioning. This requires
  233. // only one node to be updated on such an event.
  234. fuchsia::ui::gfx::mat4 ConvertTransform(gfx::Transform* transform) {
  235. std::array<float, 16> mat = {};
  236. transform->matrix().getColMajor(mat.data());
  237. fuchsia::ui::gfx::Matrix4Value fuchsia_transform =
  238. scenic::NewMatrix4Value(mat);
  239. return fuchsia_transform.value;
  240. }
  241. } // namespace
  242. fuchsia::accessibility::semantics::Node AXNodeDataToSemanticNode(
  243. const ui::AXNode& ax_node,
  244. const ui::AXNode& container_node,
  245. const ui::AXTreeID& tree_id,
  246. bool is_root,
  247. float device_scale_factor,
  248. NodeIDMapper* id_mapper) {
  249. fuchsia::accessibility::semantics::Node fuchsia_node;
  250. fuchsia_node.set_node_id(
  251. id_mapper->ToFuchsiaNodeID(tree_id, ax_node.id(), is_root));
  252. const ui::AXNodeData& node = ax_node.data();
  253. fuchsia_node.set_role(AxRoleToFuchsiaSemanticRole(node.role));
  254. fuchsia_node.set_states(ConvertStates(node));
  255. fuchsia_node.set_attributes(ConvertAttributes(ax_node));
  256. fuchsia_node.set_actions(ConvertActions(node));
  257. fuchsia_node.set_child_ids(
  258. ConvertChildIds(node.child_ids, tree_id, id_mapper));
  259. fuchsia_node.set_location(ConvertBoundingBox(node.relative_bounds.bounds));
  260. fuchsia_node.set_container_id(
  261. id_mapper->ToFuchsiaNodeID(tree_id, container_node.data().id, false));
  262. // The transform field must be handled carefully to account for
  263. // the offsetting implied by the offset container's relative bounds.
  264. gfx::Transform transform;
  265. if (node.relative_bounds.transform) {
  266. transform = *node.relative_bounds.transform;
  267. }
  268. transform.PostTranslate(container_node.data().relative_bounds.bounds.x(),
  269. container_node.data().relative_bounds.bounds.y());
  270. if (device_scale_factor > 0) {
  271. transform.PostScale(1 / device_scale_factor, 1 / device_scale_factor);
  272. }
  273. fuchsia_node.set_transform(ConvertTransform(&transform));
  274. return fuchsia_node;
  275. }
  276. bool ConvertAction(fuchsia::accessibility::semantics::Action fuchsia_action,
  277. ax::mojom::Action* mojom_action) {
  278. switch (fuchsia_action) {
  279. case fuchsia::accessibility::semantics::Action::DEFAULT:
  280. *mojom_action = ax::mojom::Action::kDoDefault;
  281. return true;
  282. case fuchsia::accessibility::semantics::Action::DECREMENT:
  283. *mojom_action = ax::mojom::Action::kDecrement;
  284. return true;
  285. case fuchsia::accessibility::semantics::Action::INCREMENT:
  286. *mojom_action = ax::mojom::Action::kIncrement;
  287. return true;
  288. case fuchsia::accessibility::semantics::Action::SHOW_ON_SCREEN:
  289. *mojom_action = ax::mojom::Action::kScrollToMakeVisible;
  290. return true;
  291. case fuchsia::accessibility::semantics::Action::SECONDARY:
  292. case fuchsia::accessibility::semantics::Action::SET_FOCUS:
  293. case fuchsia::accessibility::semantics::Action::SET_VALUE:
  294. return false;
  295. default:
  296. LOG(WARNING)
  297. << "Unknown fuchsia::accessibility::semantics::Action with value "
  298. << static_cast<int>(fuchsia_action);
  299. return false;
  300. }
  301. }
  302. NodeIDMapper::NodeIDMapper()
  303. : root_(std::make_pair(ui::AXTreeIDUnknown(), 0)) {}
  304. NodeIDMapper::~NodeIDMapper() = default;
  305. uint32_t NodeIDMapper::ToFuchsiaNodeID(const ui::AXTreeID& ax_tree_id,
  306. int32_t ax_node_id,
  307. bool is_tree_root) {
  308. const bool should_change_root =
  309. is_tree_root && (root_.first != ax_tree_id || root_.second != ax_node_id);
  310. CHECK_LE(next_fuchsia_id_, UINT32_MAX);
  311. uint32_t fuchsia_node_id;
  312. if (should_change_root) {
  313. // The node that points to the root is changing. Update the old root to
  314. // receive an unique ID, and make the new root receive the default value.
  315. if (root_.first != ui::AXTreeIDUnknown())
  316. id_map_[root_.first][root_.second] = next_fuchsia_id_++;
  317. root_ = std::make_pair(ax_tree_id, ax_node_id);
  318. fuchsia_node_id = kFuchsiaRootNodeId;
  319. } else {
  320. auto it = id_map_.find(ax_tree_id);
  321. if (it != id_map_.end()) {
  322. auto node_id_it = it->second.find(ax_node_id);
  323. if (node_id_it != it->second.end())
  324. return node_id_it->second;
  325. }
  326. // The ID is not in the map yet, so give it a new value.
  327. fuchsia_node_id = next_fuchsia_id_++;
  328. }
  329. id_map_[ax_tree_id][ax_node_id] = fuchsia_node_id;
  330. return fuchsia_node_id;
  331. }
  332. absl::optional<std::pair<ui::AXTreeID, int32_t>> NodeIDMapper::ToAXNodeID(
  333. uint32_t fuchsia_node_id) {
  334. for (const auto& tree_id_to_node_ids : id_map_) {
  335. for (const auto& ax_id_to_fuchsia_id : tree_id_to_node_ids.second) {
  336. if (ax_id_to_fuchsia_id.second == fuchsia_node_id)
  337. return std::make_pair(tree_id_to_node_ids.first,
  338. ax_id_to_fuchsia_id.first);
  339. }
  340. }
  341. return absl::nullopt;
  342. }
  343. bool NodeIDMapper::UpdateAXTreeIDForCachedNodeIDs(
  344. const ui::AXTreeID& old_ax_tree_id,
  345. const ui::AXTreeID& new_ax_tree_id) {
  346. if (old_ax_tree_id == new_ax_tree_id)
  347. return false;
  348. auto it = id_map_.find(old_ax_tree_id);
  349. if (it == id_map_.end())
  350. return false;
  351. // The iterator is not stable, so the order of operations here is important.
  352. auto data = std::move(it->second);
  353. id_map_.erase(it);
  354. id_map_[new_ax_tree_id] = std::move(data);
  355. return true;
  356. }