ax_table_info.cc 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582
  1. // Copyright 2018 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 "ui/accessibility/ax_table_info.h"
  5. #include "base/observer_list.h"
  6. #include "base/strings/string_number_conversions.h"
  7. #include "base/strings/string_util.h"
  8. #include "ui/accessibility/ax_constants.mojom.h"
  9. #include "ui/accessibility/ax_enums.mojom.h"
  10. #include "ui/accessibility/ax_node.h"
  11. #include "ui/accessibility/ax_role_properties.h"
  12. #include "ui/accessibility/ax_tree.h"
  13. #include "ui/accessibility/ax_tree_observer.h"
  14. #include "ui/gfx/geometry/rect_f.h"
  15. using ax::mojom::IntAttribute;
  16. namespace ui {
  17. namespace {
  18. // Given a node representing a table row, search its children
  19. // recursively to find any cells or table headers, and append
  20. // them to |cells|.
  21. //
  22. // We recursively check generic containers like <div> and any
  23. // nodes that are ignored, but we don't search any other roles
  24. // in-between a table row and its cells.
  25. void FindCellsInRow(AXNode* node, std::vector<AXNode*>* cell_nodes) {
  26. for (auto iter = node->UnignoredChildrenBegin();
  27. iter != node->UnignoredChildrenEnd(); ++iter) {
  28. AXNode* child = iter.get();
  29. if (child->GetRole() == ax::mojom::Role::kGenericContainer)
  30. FindCellsInRow(child, cell_nodes);
  31. else if (IsCellOrTableHeader(child->GetRole()))
  32. cell_nodes->push_back(child);
  33. }
  34. }
  35. // Given a node representing a table/grid, search its children
  36. // recursively to find any rows and append them to |row_node_list|, then
  37. // for each row find its cells and add them to |cell_nodes_per_row| as a
  38. // 2-dimensional array.
  39. //
  40. // We only recursively check for the following roles in between a table and
  41. // its rows: generic containers like <div>, any nodes that are ignored, and
  42. // table sections (which have Role::kRowGroup).
  43. void FindRowsAndThenCells(AXNode* node,
  44. std::vector<AXNode*>* row_node_list,
  45. std::vector<std::vector<AXNode*>>* cell_nodes_per_row,
  46. AXNodeID& caption_node_id) {
  47. for (auto iter = node->UnignoredChildrenBegin();
  48. iter != node->UnignoredChildrenEnd(); ++iter) {
  49. AXNode* child = iter.get();
  50. if (child->GetRole() == ax::mojom::Role::kGenericContainer ||
  51. child->GetRole() == ax::mojom::Role::kGroup ||
  52. child->GetRole() == ax::mojom::Role::kRowGroup) {
  53. FindRowsAndThenCells(child, row_node_list, cell_nodes_per_row,
  54. caption_node_id);
  55. } else if (IsTableRow(child->GetRole())) {
  56. row_node_list->push_back(child);
  57. cell_nodes_per_row->push_back(std::vector<AXNode*>());
  58. FindCellsInRow(child, &cell_nodes_per_row->back());
  59. } else if (child->GetRole() == ax::mojom::Role::kCaption) {
  60. caption_node_id = child->id();
  61. }
  62. }
  63. }
  64. size_t GetSizeTAttribute(const AXNode& node, IntAttribute attribute) {
  65. return base::saturated_cast<size_t>(node.GetIntAttribute(attribute));
  66. }
  67. } // namespace
  68. // static
  69. AXTableInfo* AXTableInfo::Create(AXTree* tree, AXNode* table_node) {
  70. DCHECK(tree);
  71. DCHECK(table_node);
  72. #if DCHECK_IS_ON()
  73. // Sanity check, make sure the node is in the tree.
  74. AXNode* node = table_node;
  75. while (node && node != tree->root())
  76. node = node->GetParent();
  77. DCHECK_EQ(node, tree->root());
  78. #endif
  79. if (!IsTableLike(table_node->GetRole()))
  80. return nullptr;
  81. AXTableInfo* info = new AXTableInfo(tree, table_node);
  82. bool success = info->Update();
  83. DCHECK(success);
  84. return info;
  85. }
  86. bool AXTableInfo::Update() {
  87. if (!table_node_->IsTable())
  88. return false;
  89. ClearVectors();
  90. std::vector<std::vector<AXNode*>> cell_nodes_per_row;
  91. caption_id = 0;
  92. FindRowsAndThenCells(table_node_, &row_nodes, &cell_nodes_per_row,
  93. caption_id);
  94. DCHECK_EQ(cell_nodes_per_row.size(), row_nodes.size());
  95. // Get the optional row and column count from the table. If we encounter
  96. // a cell with an index or span larger than this, we'll update the
  97. // table row and column count to be large enough to fit all cells.
  98. row_count = GetSizeTAttribute(*table_node_, IntAttribute::kTableRowCount);
  99. col_count = GetSizeTAttribute(*table_node_, IntAttribute::kTableColumnCount);
  100. // Note - GetIntAttribute returns 0 if no value has been specified for the
  101. // attribute.
  102. aria_row_count =
  103. int{table_node_->GetIntAttribute(IntAttribute::kAriaRowCount)};
  104. aria_col_count =
  105. int{table_node_->GetIntAttribute(IntAttribute::kAriaColumnCount)};
  106. // Iterate over the cells and build up an array of CellData
  107. // entries, one for each cell. Compute the actual row and column
  108. BuildCellDataVectorFromRowAndCellNodes(row_nodes, cell_nodes_per_row);
  109. // At this point we have computed valid row and column indices for
  110. // every cell in the table, and an accurate row and column count for the
  111. // whole table that fits every cell and its spans. The final step is to
  112. // fill in a 2-dimensional array that lets us look up an individual cell
  113. // by its (row, column) coordinates, plus arrays to hold row and column
  114. // headers.
  115. BuildCellAndHeaderVectorsFromCellData();
  116. // On Mac, we add a few extra nodes to the table - see comment
  117. // at the top of UpdateExtraMacNodes for details.
  118. #if defined(AX_EXTRA_MAC_NODES)
  119. UpdateExtraMacNodes();
  120. #endif
  121. // The table metadata is now valid, any table queries will now be
  122. // fast. Any time a node in the table is updated, we'll have to
  123. // recompute all of this.
  124. valid_ = true;
  125. return true;
  126. }
  127. void AXTableInfo::Invalidate() {
  128. valid_ = false;
  129. }
  130. void AXTableInfo::ClearVectors() {
  131. col_headers.clear();
  132. row_headers.clear();
  133. all_headers.clear();
  134. cell_ids.clear();
  135. unique_cell_ids.clear();
  136. cell_data_vector.clear();
  137. row_nodes.clear();
  138. cell_id_to_index.clear();
  139. row_id_to_index.clear();
  140. incremental_row_col_map_.clear();
  141. }
  142. void AXTableInfo::BuildCellDataVectorFromRowAndCellNodes(
  143. const std::vector<AXNode*>& row_node_list,
  144. const std::vector<std::vector<AXNode*>>& cell_nodes_per_row) {
  145. // Iterate over the cells and build up an array of CellData
  146. // entries, one for each cell. Compute the actual row and column
  147. // indices for each cell by taking the specified row and column
  148. // index in the accessibility tree if legal, but replacing it with
  149. // valid table coordinates otherwise.
  150. size_t cell_index = 0;
  151. size_t current_aria_row_index = 1;
  152. size_t next_row_index = 0;
  153. for (size_t i = 0; i < cell_nodes_per_row.size(); i++) {
  154. auto& cell_nodes_in_this_row = cell_nodes_per_row[i];
  155. AXNode* row_node = row_node_list[i];
  156. bool is_first_cell_in_row = true;
  157. size_t current_col_index = 0;
  158. size_t current_aria_col_index = 1;
  159. // Make sure the row index is always at least as high as the one reported by
  160. // the source tree.
  161. row_id_to_index[row_node->id()] =
  162. std::max(next_row_index,
  163. GetSizeTAttribute(*row_node, IntAttribute::kTableRowIndex));
  164. size_t* current_row_index = &row_id_to_index[row_node->id()];
  165. size_t spanned_col_index = 0;
  166. for (AXNode* cell : cell_nodes_in_this_row) {
  167. // Fill in basic info in CellData.
  168. CellData cell_data;
  169. unique_cell_ids.push_back(cell->id());
  170. cell_id_to_index[cell->id()] = cell_index++;
  171. cell_data.cell = cell;
  172. // Get table cell accessibility attributes - note that these may
  173. // be missing or invalid, we'll correct them next.
  174. cell_data.row_index =
  175. GetSizeTAttribute(*cell, IntAttribute::kTableCellRowIndex);
  176. cell_data.row_span =
  177. GetSizeTAttribute(*cell, IntAttribute::kTableCellRowSpan);
  178. cell_data.aria_row_index =
  179. GetSizeTAttribute(*cell, IntAttribute::kAriaCellRowIndex);
  180. cell_data.col_index =
  181. GetSizeTAttribute(*cell, IntAttribute::kTableCellColumnIndex);
  182. cell_data.aria_col_index =
  183. GetSizeTAttribute(*cell, IntAttribute::kAriaCellColumnIndex);
  184. cell_data.col_span =
  185. GetSizeTAttribute(*cell, IntAttribute::kTableCellColumnSpan);
  186. // The col span and row span must be at least 1.
  187. cell_data.row_span = std::max(size_t{1}, cell_data.row_span);
  188. cell_data.col_span = std::max(size_t{1}, cell_data.col_span);
  189. // Ensure the column index must always be incrementing.
  190. cell_data.col_index = std::max(cell_data.col_index, current_col_index);
  191. // And update the spanned column index.
  192. spanned_col_index = std::max(spanned_col_index, cell_data.col_index);
  193. if (is_first_cell_in_row) {
  194. is_first_cell_in_row = false;
  195. // If it's the first cell in the row, ensure the row index is
  196. // incrementing. The rest of the cells in this row are forced to have
  197. // the same row index.
  198. if (cell_data.row_index > *current_row_index) {
  199. *current_row_index = cell_data.row_index;
  200. } else {
  201. cell_data.row_index = *current_row_index;
  202. }
  203. // The starting ARIA row and column index might be specified in
  204. // the row node, we should check there.
  205. if (!cell_data.aria_row_index) {
  206. cell_data.aria_row_index =
  207. GetSizeTAttribute(*row_node, IntAttribute::kAriaCellRowIndex);
  208. }
  209. if (!cell_data.aria_col_index) {
  210. cell_data.aria_col_index =
  211. GetSizeTAttribute(*row_node, IntAttribute::kAriaCellColumnIndex);
  212. }
  213. cell_data.aria_row_index =
  214. std::max(cell_data.aria_row_index, current_aria_row_index);
  215. current_aria_row_index = cell_data.aria_row_index;
  216. } else {
  217. // Don't allow the row index to change after the beginning
  218. // of a row.
  219. cell_data.row_index = *current_row_index;
  220. cell_data.aria_row_index = current_aria_row_index;
  221. }
  222. // Adjust the spanned col index by looking at the incremental row col map.
  223. // This map contains already filled in values, accounting for spans, of
  224. // all row, col indices. The map should have filled in all values we need
  225. // (upper left triangle of cells of the table).
  226. while (true) {
  227. const auto& row_it = incremental_row_col_map_.find(*current_row_index);
  228. if (row_it == incremental_row_col_map_.end()) {
  229. break;
  230. } else {
  231. const auto& col_it = row_it->second.find(spanned_col_index);
  232. if (col_it == row_it->second.end()) {
  233. break;
  234. } else {
  235. // A pre-existing cell resides in our desired position. Make a
  236. // best-fit to the right of the existing span.
  237. const CellData& spanned_cell_data = col_it->second;
  238. spanned_col_index =
  239. spanned_cell_data.col_index + spanned_cell_data.col_span;
  240. // Adjust the actual col index to be the best fit with the existing
  241. // spanned cell data.
  242. cell_data.col_index = spanned_col_index;
  243. }
  244. }
  245. }
  246. // Memoize the cell data using our incremental row col map.
  247. for (size_t r = cell_data.row_index;
  248. r < (cell_data.row_index + cell_data.row_span); r++) {
  249. for (size_t c = cell_data.col_index;
  250. c < (cell_data.col_index + cell_data.col_span); c++) {
  251. incremental_row_col_map_[r][c] = cell_data;
  252. }
  253. }
  254. // Ensure the ARIA col index is incrementing.
  255. cell_data.aria_col_index =
  256. std::max(cell_data.aria_col_index, current_aria_col_index);
  257. current_aria_col_index = cell_data.aria_col_index;
  258. // Update the row count and col count for the whole table to make
  259. // sure they're large enough to fit this cell, including its spans.
  260. // The -1 in the ARIA calculations is because ARIA indices are 1-based,
  261. // whereas all other indices are zero-based.
  262. row_count = std::max(row_count, cell_data.row_index + cell_data.row_span);
  263. col_count = std::max(col_count, cell_data.col_index + cell_data.col_span);
  264. if (aria_row_count != ax::mojom::kUnknownAriaColumnOrRowCount) {
  265. aria_row_count = std::max(
  266. (aria_row_count),
  267. static_cast<int>(current_aria_row_index + cell_data.row_span - 1));
  268. }
  269. if (aria_col_count != ax::mojom::kUnknownAriaColumnOrRowCount) {
  270. aria_col_count = std::max(
  271. (aria_col_count),
  272. static_cast<int>(current_aria_col_index + cell_data.col_span - 1));
  273. }
  274. // Update |current_col_index| to reflect the next available index after
  275. // this cell including its colspan. The next column index in this row
  276. // must be at least this large. Same for the current ARIA col index.
  277. current_col_index = cell_data.col_index + cell_data.col_span;
  278. current_aria_col_index = cell_data.aria_col_index + cell_data.col_span;
  279. spanned_col_index = current_col_index;
  280. // Add this cell to our vector.
  281. cell_data_vector.push_back(cell_data);
  282. }
  283. // At the end of each row, increment |current_aria_row_index| to reflect the
  284. // next available index after this row. The next row index must be at least
  285. // this large. Also update |next_row_index|.
  286. current_aria_row_index++;
  287. next_row_index = *current_row_index + 1;
  288. }
  289. }
  290. void AXTableInfo::BuildCellAndHeaderVectorsFromCellData() {
  291. // Allocate space for the 2-D array of cell IDs and 1-D
  292. // arrays of row headers and column headers.
  293. row_headers.resize(row_count);
  294. col_headers.resize(col_count);
  295. // Fill in the arrays.
  296. //
  297. // At this point we have computed valid row and column indices for
  298. // every cell in the table, and an accurate row and column count for the
  299. // whole table that fits every cell and its spans. The final step is to
  300. // fill in a 2-dimensional array that lets us look up an individual cell
  301. // by its (row, column) coordinates, plus arrays to hold row and column
  302. // headers.
  303. // For cells.
  304. cell_ids.resize(row_count);
  305. for (size_t r = 0; r < row_count; r++) {
  306. cell_ids[r].resize(col_count);
  307. for (size_t c = 0; c < col_count; c++) {
  308. const auto& row_it = incremental_row_col_map_.find(r);
  309. if (row_it != incremental_row_col_map_.end()) {
  310. const auto& col_it = row_it->second.find(c);
  311. if (col_it != row_it->second.end())
  312. cell_ids[r][c] = col_it->second.cell->id();
  313. }
  314. }
  315. }
  316. // No longer need this.
  317. incremental_row_col_map_.clear();
  318. // For relations.
  319. for (auto& cell_data : cell_data_vector) {
  320. for (size_t r = cell_data.row_index;
  321. r < cell_data.row_index + cell_data.row_span; r++) {
  322. DCHECK_LT(r, row_count);
  323. for (size_t c = cell_data.col_index;
  324. c < cell_data.col_index + cell_data.col_span; c++) {
  325. DCHECK_LT(c, col_count);
  326. AXNode* cell = cell_data.cell;
  327. if (cell->GetRole() == ax::mojom::Role::kColumnHeader) {
  328. col_headers[c].push_back(cell->id());
  329. all_headers.push_back(cell->id());
  330. } else if (cell->GetRole() == ax::mojom::Role::kRowHeader) {
  331. row_headers[r].push_back(cell->id());
  332. all_headers.push_back(cell->id());
  333. }
  334. }
  335. }
  336. }
  337. }
  338. void AXTableInfo::UpdateExtraMacNodes() {
  339. // On macOS, maintain additional AXNodes: one column node for each
  340. // column of the table, and one table header container.
  341. //
  342. // The nodes all set the table as the parent node, that way the Mac-specific
  343. // platform code can treat these nodes as additional children of the table
  344. // node.
  345. //
  346. // The columns have id -1, -2, -3, ... - this won't conflict with ids from
  347. // the source tree, which are all positive.
  348. //
  349. // Each column has the kColumnIndex attribute set, and then each of the cells
  350. // in that column gets added as an indirect ID. That exposes them as children
  351. // via Mac APIs but ensures we don't explore those nodes multiple times when
  352. // walking the tree. The column also has the ID of the first column header
  353. // set.
  354. //
  355. // The table header container is just a node with all of the headers in the
  356. // table as indirect children.
  357. // Delete old extra nodes.
  358. ClearExtraMacNodes();
  359. // There is one node for each column, and one more for the table header
  360. // container.
  361. size_t extra_node_count = col_count + 1;
  362. std::vector<AXNode*> new_extra_mac_nodes;
  363. new_extra_mac_nodes.reserve(extra_node_count);
  364. std::vector<AXTreeObserver::Change> changes;
  365. // Reserve room for the extra Mac nodes plus for the table itself.
  366. changes.reserve(extra_node_count + 1);
  367. for (size_t i = 0; i < col_count; i++) {
  368. new_extra_mac_nodes.push_back(CreateExtraMacColumnNode(i));
  369. changes.push_back(AXTreeObserver::Change(
  370. new_extra_mac_nodes[i], AXTreeObserver::ChangeType::NODE_CREATED));
  371. }
  372. new_extra_mac_nodes.push_back(CreateExtraMacTableHeaderNode());
  373. changes.push_back(
  374. AXTreeObserver::Change(new_extra_mac_nodes[col_count],
  375. AXTreeObserver::ChangeType::NODE_CREATED));
  376. {
  377. ScopedTreeUpdateInProgressStateSetter tree_update_in_progress(*tree_);
  378. // Add the newly created columns to the accessibility tree.
  379. extra_mac_nodes.swap(new_extra_mac_nodes);
  380. // Update the newly added columns to reflect the current state of the table.
  381. for (size_t i = 0; i < col_count; i++)
  382. UpdateExtraMacColumnNodeAttributes(i);
  383. // Update the table header container to contain all headers.
  384. AXNodeData data = extra_mac_nodes[col_count]->data();
  385. data.intlist_attributes.clear();
  386. data.AddIntListAttribute(ax::mojom::IntListAttribute::kIndirectChildIds,
  387. all_headers);
  388. extra_mac_nodes[col_count]->SetData(data);
  389. } // tree_update_in_progress.
  390. changes.push_back(AXTreeObserver::Change(
  391. table_node_, AXTreeObserver::ChangeType::NODE_CHANGED));
  392. for (AXNode* node : extra_mac_nodes) {
  393. for (AXTreeObserver& observer : tree_->observers())
  394. observer.OnNodeCreated(tree_, node);
  395. }
  396. for (AXTreeObserver& observer : tree_->observers())
  397. observer.OnAtomicUpdateFinished(tree_, /* root_changed= */ false, changes);
  398. }
  399. AXNode* AXTableInfo::CreateExtraMacColumnNode(size_t col_index) {
  400. AXNodeID id = tree_->GetNextNegativeInternalNodeId();
  401. size_t index_in_parent = col_index + table_node_->children().size();
  402. int32_t unignored_index_in_parent =
  403. col_index + table_node_->GetUnignoredChildCount();
  404. AXNode* node = new AXNode(tree_, table_node_, id, index_in_parent,
  405. unignored_index_in_parent);
  406. AXNodeData data;
  407. data.id = id;
  408. data.role = ax::mojom::Role::kColumn;
  409. node->SetData(data);
  410. return node;
  411. }
  412. AXNode* AXTableInfo::CreateExtraMacTableHeaderNode() {
  413. AXNodeID id = tree_->GetNextNegativeInternalNodeId();
  414. size_t index_in_parent = col_count + table_node_->children().size();
  415. int32_t unignored_index_in_parent =
  416. col_count + table_node_->GetUnignoredChildCount();
  417. AXNode* node = new AXNode(tree_, table_node_, id, index_in_parent,
  418. unignored_index_in_parent);
  419. AXNodeData data;
  420. data.id = id;
  421. data.role = ax::mojom::Role::kTableHeaderContainer;
  422. node->SetData(data);
  423. return node;
  424. }
  425. void AXTableInfo::UpdateExtraMacColumnNodeAttributes(size_t col_index) {
  426. ui::AXNodeData data = extra_mac_nodes[col_index]->data();
  427. data.int_attributes.clear();
  428. // Update the column index.
  429. data.AddIntAttribute(IntAttribute::kTableColumnIndex,
  430. static_cast<int32_t>(col_index));
  431. // Update the column header.
  432. if (!col_headers[col_index].empty()) {
  433. data.AddIntAttribute(IntAttribute::kTableColumnHeaderId,
  434. col_headers[col_index][0]);
  435. }
  436. // Update the list of cells in the column.
  437. data.intlist_attributes.clear();
  438. std::vector<AXNodeID> col_nodes;
  439. AXNodeID last = 0;
  440. for (size_t row_index = 0; row_index < row_count; row_index++) {
  441. AXNodeID cell_id = cell_ids[row_index][col_index];
  442. if (cell_id != 0 && cell_id != last)
  443. col_nodes.push_back(cell_id);
  444. last = cell_id;
  445. }
  446. data.AddIntListAttribute(ax::mojom::IntListAttribute::kIndirectChildIds,
  447. col_nodes);
  448. extra_mac_nodes[col_index]->SetData(data);
  449. }
  450. void AXTableInfo::ClearExtraMacNodes() {
  451. if (extra_mac_nodes.empty())
  452. return;
  453. for (AXNode* extra_mac_node : extra_mac_nodes) {
  454. for (AXTreeObserver& observer : tree_->observers())
  455. observer.OnNodeWillBeDeleted(tree_, extra_mac_node);
  456. }
  457. std::vector<AXNodeID> deleted_ids;
  458. {
  459. ScopedTreeUpdateInProgressStateSetter tree_update_in_progress(*tree_);
  460. for (AXNode* extra_mac_node : extra_mac_nodes) {
  461. AXNodeID deleted_id = extra_mac_node->id();
  462. deleted_ids.push_back(deleted_id);
  463. delete extra_mac_node;
  464. }
  465. extra_mac_nodes.clear();
  466. } // tree_update_in_progress.
  467. for (AXNodeID deleted_id : deleted_ids) {
  468. for (AXTreeObserver& observer : tree_->observers())
  469. observer.OnNodeDeleted(tree_, deleted_id);
  470. }
  471. for (AXTreeObserver& observer : tree_->observers()) {
  472. observer.OnAtomicUpdateFinished(
  473. tree_, /* root_changed= */ false,
  474. {{table_node_, AXTreeObserver::ChangeType::NODE_CHANGED}});
  475. }
  476. }
  477. std::string AXTableInfo::ToString() const {
  478. // First, scan through to get the length of the largest id.
  479. int padding = 0;
  480. for (size_t r = 0; r < row_count; r++) {
  481. for (size_t c = 0; c < col_count; c++) {
  482. // Extract the length of the id for padding purposes.
  483. padding = std::max(padding, static_cast<int>(log10(cell_ids[r][c])));
  484. }
  485. }
  486. std::string result;
  487. for (size_t r = 0; r < row_count; r++) {
  488. result += "|";
  489. for (size_t c = 0; c < col_count; c++) {
  490. int cell_id = cell_ids[r][c];
  491. result += base::NumberToString(cell_id);
  492. int cell_padding = padding;
  493. if (cell_id != 0)
  494. cell_padding = padding - static_cast<int>(log10(cell_id));
  495. result += std::string(cell_padding, ' ') + '|';
  496. }
  497. result += "\n";
  498. }
  499. return result;
  500. }
  501. AXTableInfo::AXTableInfo(AXTree* tree, AXNode* table_node)
  502. : tree_(tree), table_node_(table_node) {}
  503. AXTableInfo::~AXTableInfo() {
  504. ClearExtraMacNodes();
  505. }
  506. } // namespace ui