ax_computed_node_data_unittest.cc 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553
  1. // Copyright 2021 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_computed_node_data.h"
  5. #include <memory>
  6. #include <utility>
  7. #include <vector>
  8. #include "base/memory/raw_ptr.h"
  9. #include "testing/gmock/include/gmock/gmock-matchers.h"
  10. #include "testing/gtest/include/gtest/gtest.h"
  11. #include "ui/accessibility/ax_enums.mojom.h"
  12. #include "ui/accessibility/ax_node_data.h"
  13. #include "ui/accessibility/ax_node_id_forward.h"
  14. #include "ui/accessibility/ax_position.h"
  15. #include "ui/accessibility/ax_tree.h"
  16. #include "ui/accessibility/ax_tree_data.h"
  17. #include "ui/accessibility/ax_tree_id.h"
  18. #include "ui/accessibility/test_ax_tree_manager.h"
  19. namespace ui {
  20. namespace {
  21. class AXComputedNodeDataTest : public ::testing::Test,
  22. public TestAXTreeManager {
  23. public:
  24. AXComputedNodeDataTest();
  25. ~AXComputedNodeDataTest() override;
  26. AXComputedNodeDataTest(const AXComputedNodeDataTest& other) = delete;
  27. AXComputedNodeDataTest& operator=(const AXComputedNodeDataTest& other) =
  28. delete;
  29. void SetUp() override;
  30. protected:
  31. // Numbers at the end of variable names indicate their position under the
  32. // root.
  33. AXNodeData root_;
  34. AXNodeData paragraph_0_;
  35. AXNodeData static_text_0_0_ignored_;
  36. AXNodeData paragraph_1_ignored_;
  37. AXNodeData static_text_1_0_;
  38. AXNodeData paragraph_2_ignored_;
  39. AXNodeData link_2_0_ignored_;
  40. AXNodeData static_text_2_0_0_;
  41. AXNodeData static_text_2_0_1_;
  42. raw_ptr<AXNode> root_node_;
  43. };
  44. AXComputedNodeDataTest::AXComputedNodeDataTest() = default;
  45. AXComputedNodeDataTest::~AXComputedNodeDataTest() = default;
  46. void AXComputedNodeDataTest::SetUp() {
  47. // ++kRootWebArea contenteditable
  48. // ++++kParagraph
  49. // ++++++kStaticText IGNORED "i"
  50. // ++++kParagraph IGNORED
  51. // ++++++kStaticText "t_1"
  52. // ++++kParagraph IGNORED
  53. // ++++++kLink IGNORED
  54. // ++++++++kStaticText "s+t++2...0. 0"
  55. // ++++++++kStaticText "s t\n2\r0\r\n1"
  56. root_.id = 1;
  57. paragraph_0_.id = 2;
  58. static_text_0_0_ignored_.id = 3;
  59. paragraph_1_ignored_.id = 4;
  60. static_text_1_0_.id = 5;
  61. paragraph_2_ignored_.id = 6;
  62. link_2_0_ignored_.id = 7;
  63. static_text_2_0_0_.id = 8;
  64. static_text_2_0_1_.id = 9;
  65. root_.role = ax::mojom::Role::kRootWebArea;
  66. root_.AddBoolAttribute(ax::mojom::BoolAttribute::kNonAtomicTextFieldRoot,
  67. true);
  68. root_.child_ids = {paragraph_0_.id, paragraph_1_ignored_.id,
  69. paragraph_2_ignored_.id};
  70. paragraph_0_.role = ax::mojom::Role::kParagraph;
  71. paragraph_0_.AddBoolAttribute(ax::mojom::BoolAttribute::kIsLineBreakingObject,
  72. true);
  73. paragraph_0_.child_ids = {static_text_0_0_ignored_.id};
  74. static_text_0_0_ignored_.role = ax::mojom::Role::kStaticText;
  75. static_text_0_0_ignored_.AddState(ax::mojom::State::kIgnored);
  76. // Ignored text should not appear anywhere and should not be used to separate
  77. // words.
  78. static_text_0_0_ignored_.SetName("i");
  79. paragraph_1_ignored_.role = ax::mojom::Role::kParagraph;
  80. paragraph_1_ignored_.AddState(ax::mojom::State::kIgnored);
  81. paragraph_1_ignored_.AddBoolAttribute(
  82. ax::mojom::BoolAttribute::kIsLineBreakingObject, true);
  83. paragraph_1_ignored_.child_ids = {static_text_1_0_.id};
  84. static_text_1_0_.role = ax::mojom::Role::kStaticText;
  85. // An underscore should separate words.
  86. static_text_1_0_.SetName("t_1");
  87. paragraph_2_ignored_.role = ax::mojom::Role::kParagraph;
  88. paragraph_2_ignored_.AddState(ax::mojom::State::kIgnored);
  89. paragraph_2_ignored_.AddBoolAttribute(
  90. ax::mojom::BoolAttribute::kIsLineBreakingObject, true);
  91. paragraph_2_ignored_.child_ids = {link_2_0_ignored_.id};
  92. link_2_0_ignored_.role = ax::mojom::Role::kLink;
  93. link_2_0_ignored_.AddState(ax::mojom::State::kLinked);
  94. link_2_0_ignored_.AddState(ax::mojom::State::kIgnored);
  95. link_2_0_ignored_.child_ids = {static_text_2_0_0_.id, static_text_2_0_1_.id};
  96. static_text_2_0_0_.role = ax::mojom::Role::kStaticText;
  97. // A series of punctuation marks, or a stretch of whitespace should separate
  98. // words.
  99. static_text_2_0_0_.SetName("s+t++2...0. 0");
  100. static_text_2_0_1_.role = ax::mojom::Role::kStaticText;
  101. // Both a carage return as well as a line break should separate lines, but not
  102. // a space character.
  103. static_text_2_0_1_.SetName("s t\n2\r0\r\n1");
  104. AXTreeUpdate initial_state;
  105. initial_state.root_id = root_.id;
  106. initial_state.nodes = {root_,
  107. paragraph_0_,
  108. static_text_0_0_ignored_,
  109. paragraph_1_ignored_,
  110. static_text_1_0_,
  111. paragraph_2_ignored_,
  112. link_2_0_ignored_,
  113. static_text_2_0_0_,
  114. static_text_2_0_1_};
  115. initial_state.has_tree_data = true;
  116. AXTreeData tree_data;
  117. tree_data.tree_id = AXTreeID::CreateNewAXTreeID();
  118. tree_data.title = "Application";
  119. initial_state.tree_data = tree_data;
  120. auto tree = std::make_unique<AXTree>();
  121. ASSERT_TRUE(tree->Unserialize(initial_state)) << tree->error();
  122. root_node_ = tree->root();
  123. ASSERT_EQ(root_.id, root_node_->id());
  124. // `SetTree` is defined in our `TestAXTreeManager` superclass and it passes
  125. // ownership of the created AXTree to the manager.
  126. SetTree(std::move(tree));
  127. }
  128. } // namespace
  129. using ::testing::ElementsAre;
  130. using ::testing::ElementsAreArray;
  131. using ::testing::SizeIs;
  132. using ::testing::StrEq;
  133. TEST_F(AXComputedNodeDataTest, UnignoredValues) {
  134. const AXNode* paragraph_0_node = root_node_->GetChildAtIndex(0);
  135. const AXNode* static_text_0_0_ignored_node =
  136. paragraph_0_node->GetChildAtIndex(0);
  137. const AXNode* paragraph_1_ignored_node = root_node_->GetChildAtIndex(1);
  138. const AXNode* static_text_1_0_node =
  139. paragraph_1_ignored_node->GetChildAtIndex(0);
  140. const AXNode* paragraph_2_ignored_node = root_node_->GetChildAtIndex(2);
  141. const AXNode* link_2_0_ignored_node =
  142. paragraph_2_ignored_node->GetChildAtIndex(0);
  143. const AXNode* static_text_2_0_0_node =
  144. link_2_0_ignored_node->GetChildAtIndex(0);
  145. const AXNode* static_text_2_0_1_node =
  146. link_2_0_ignored_node->GetChildAtIndex(1);
  147. // Perform the checks twice to ensure that caching returns the same values.
  148. for (int i = 0; i < 2; ++i) {
  149. EXPECT_EQ(
  150. 0,
  151. root_node_->GetComputedNodeData().GetOrComputeUnignoredIndexInParent());
  152. EXPECT_EQ(0, paragraph_0_node->GetComputedNodeData()
  153. .GetOrComputeUnignoredIndexInParent());
  154. EXPECT_EQ(1, static_text_1_0_node->GetComputedNodeData()
  155. .GetOrComputeUnignoredIndexInParent());
  156. EXPECT_EQ(2, static_text_2_0_0_node->GetComputedNodeData()
  157. .GetOrComputeUnignoredIndexInParent());
  158. EXPECT_EQ(3, static_text_2_0_1_node->GetComputedNodeData()
  159. .GetOrComputeUnignoredIndexInParent());
  160. EXPECT_EQ(
  161. kInvalidAXNodeID,
  162. root_node_->GetComputedNodeData().GetOrComputeUnignoredParentID());
  163. EXPECT_EQ(nullptr,
  164. root_node_->GetComputedNodeData().GetOrComputeUnignoredParent());
  165. EXPECT_FALSE(root_node_->GetComputedNodeData()
  166. .GetOrComputeIsDescendantOfPlatformLeaf());
  167. EXPECT_EQ(root_node_->id(), paragraph_0_node->GetComputedNodeData()
  168. .GetOrComputeUnignoredParentID());
  169. EXPECT_EQ(
  170. root_node_,
  171. paragraph_0_node->GetComputedNodeData().GetOrComputeUnignoredParent());
  172. EXPECT_FALSE(paragraph_0_node->GetComputedNodeData()
  173. .GetOrComputeIsDescendantOfPlatformLeaf());
  174. EXPECT_EQ(paragraph_0_node->id(),
  175. static_text_0_0_ignored_node->GetComputedNodeData()
  176. .GetOrComputeUnignoredParentID());
  177. EXPECT_EQ(paragraph_0_node,
  178. static_text_0_0_ignored_node->GetComputedNodeData()
  179. .GetOrComputeUnignoredParent());
  180. EXPECT_TRUE(static_text_0_0_ignored_node->GetComputedNodeData()
  181. .GetOrComputeIsDescendantOfPlatformLeaf());
  182. EXPECT_EQ(root_node_->id(), paragraph_1_ignored_node->GetComputedNodeData()
  183. .GetOrComputeUnignoredParentID());
  184. EXPECT_EQ(root_node_, paragraph_1_ignored_node->GetComputedNodeData()
  185. .GetOrComputeUnignoredParent());
  186. EXPECT_FALSE(paragraph_1_ignored_node->GetComputedNodeData()
  187. .GetOrComputeIsDescendantOfPlatformLeaf());
  188. EXPECT_EQ(root_node_->id(), static_text_1_0_node->GetComputedNodeData()
  189. .GetOrComputeUnignoredParentID());
  190. EXPECT_EQ(root_node_, static_text_1_0_node->GetComputedNodeData()
  191. .GetOrComputeUnignoredParent());
  192. EXPECT_FALSE(static_text_1_0_node->GetComputedNodeData()
  193. .GetOrComputeIsDescendantOfPlatformLeaf());
  194. EXPECT_EQ(root_node_->id(), paragraph_2_ignored_node->GetComputedNodeData()
  195. .GetOrComputeUnignoredParentID());
  196. EXPECT_EQ(root_node_, paragraph_2_ignored_node->GetComputedNodeData()
  197. .GetOrComputeUnignoredParent());
  198. EXPECT_FALSE(paragraph_2_ignored_node->GetComputedNodeData()
  199. .GetOrComputeIsDescendantOfPlatformLeaf());
  200. EXPECT_EQ(root_node_->id(), link_2_0_ignored_node->GetComputedNodeData()
  201. .GetOrComputeUnignoredParentID());
  202. EXPECT_EQ(root_node_, link_2_0_ignored_node->GetComputedNodeData()
  203. .GetOrComputeUnignoredParent());
  204. EXPECT_FALSE(link_2_0_ignored_node->GetComputedNodeData()
  205. .GetOrComputeIsDescendantOfPlatformLeaf());
  206. EXPECT_EQ(root_node_->id(), static_text_2_0_0_node->GetComputedNodeData()
  207. .GetOrComputeUnignoredParentID());
  208. EXPECT_EQ(root_node_, static_text_2_0_0_node->GetComputedNodeData()
  209. .GetOrComputeUnignoredParent());
  210. EXPECT_FALSE(static_text_2_0_0_node->GetComputedNodeData()
  211. .GetOrComputeIsDescendantOfPlatformLeaf());
  212. EXPECT_EQ(root_node_->id(), static_text_2_0_1_node->GetComputedNodeData()
  213. .GetOrComputeUnignoredParentID());
  214. EXPECT_EQ(root_node_, static_text_2_0_1_node->GetComputedNodeData()
  215. .GetOrComputeUnignoredParent());
  216. EXPECT_FALSE(static_text_2_0_1_node->GetComputedNodeData()
  217. .GetOrComputeIsDescendantOfPlatformLeaf());
  218. EXPECT_EQ(
  219. 4, root_node_->GetComputedNodeData().GetOrComputeUnignoredChildCount());
  220. EXPECT_EQ(0, paragraph_0_node->GetComputedNodeData()
  221. .GetOrComputeUnignoredChildCount());
  222. EXPECT_EQ(0, static_text_1_0_node->GetComputedNodeData()
  223. .GetOrComputeUnignoredChildCount());
  224. EXPECT_EQ(0, static_text_2_0_0_node->GetComputedNodeData()
  225. .GetOrComputeUnignoredChildCount());
  226. EXPECT_EQ(0, static_text_2_0_1_node->GetComputedNodeData()
  227. .GetOrComputeUnignoredChildCount());
  228. EXPECT_THAT(
  229. root_node_->GetComputedNodeData().GetOrComputeUnignoredChildIDs(),
  230. ElementsAre(paragraph_0_.id, static_text_1_0_.id, static_text_2_0_0_.id,
  231. static_text_2_0_1_.id));
  232. }
  233. }
  234. TEST_F(AXComputedNodeDataTest, HasOrCanComputeAttribute) {
  235. EXPECT_TRUE(root_node_->GetComputedNodeData().HasOrCanComputeAttribute(
  236. ax::mojom::StringAttribute::kValue));
  237. EXPECT_FALSE(root_node_->GetComputedNodeData().HasOrCanComputeAttribute(
  238. ax::mojom::StringAttribute::kHtmlTag));
  239. EXPECT_TRUE(root_node_->GetComputedNodeData().HasOrCanComputeAttribute(
  240. ax::mojom::IntListAttribute::kWordStarts));
  241. EXPECT_FALSE(root_node_->GetComputedNodeData().HasOrCanComputeAttribute(
  242. ax::mojom::IntListAttribute::kLabelledbyIds));
  243. AXNode* paragraph_0_node = root_node_->GetChildAtIndex(0);
  244. EXPECT_FALSE(paragraph_0_node->GetComputedNodeData().HasOrCanComputeAttribute(
  245. ax::mojom::StringAttribute::kValue));
  246. EXPECT_FALSE(paragraph_0_node->GetComputedNodeData().HasOrCanComputeAttribute(
  247. ax::mojom::StringAttribute::kHtmlTag));
  248. EXPECT_TRUE(paragraph_0_node->GetComputedNodeData().HasOrCanComputeAttribute(
  249. ax::mojom::IntListAttribute::kWordStarts));
  250. EXPECT_FALSE(paragraph_0_node->GetComputedNodeData().HasOrCanComputeAttribute(
  251. ax::mojom::IntListAttribute::kLabelledbyIds));
  252. // By removing the `ax::mojom::BoolAttribute::kNonAtomicTextFieldRoot`
  253. // attribute, the root is no longer a content editable.
  254. root_.RemoveBoolAttribute(ax::mojom::BoolAttribute::kNonAtomicTextFieldRoot);
  255. root_.AddIntListAttribute(ax::mojom::IntListAttribute::kLabelledbyIds,
  256. {static_text_0_0_ignored_.id});
  257. paragraph_0_.AddStringAttribute(ax::mojom::StringAttribute::kValue,
  258. "New: \nvalue.");
  259. paragraph_0_.AddStringAttribute(ax::mojom::StringAttribute::kHtmlTag, "p");
  260. paragraph_0_.AddIntListAttribute(ax::mojom::IntListAttribute::kWordStarts,
  261. {0, 4});
  262. AXTreeUpdate tree_update;
  263. tree_update.root_id = root_.id;
  264. tree_update.nodes = {root_, paragraph_0_};
  265. ASSERT_TRUE(GetTree()->Unserialize(tree_update));
  266. root_node_ = GetTree()->root();
  267. ASSERT_EQ(root_.id, root_node_->id());
  268. // Computing the value attribute is only supported on non-atomic text fields.
  269. EXPECT_FALSE(root_node_->GetComputedNodeData().HasOrCanComputeAttribute(
  270. ax::mojom::StringAttribute::kValue));
  271. EXPECT_FALSE(root_node_->GetComputedNodeData().HasOrCanComputeAttribute(
  272. ax::mojom::StringAttribute::kHtmlTag));
  273. EXPECT_TRUE(root_node_->GetComputedNodeData().HasOrCanComputeAttribute(
  274. ax::mojom::IntListAttribute::kWordStarts));
  275. EXPECT_TRUE(root_node_->GetComputedNodeData().HasOrCanComputeAttribute(
  276. ax::mojom::IntListAttribute::kLabelledbyIds));
  277. paragraph_0_node = root_node_->GetChildAtIndex(0);
  278. // However, for maximum flexibility, if the value attribute is already present
  279. // in the node's data we should use it without checking if the role supports
  280. // it.
  281. EXPECT_TRUE(paragraph_0_node->GetComputedNodeData().HasOrCanComputeAttribute(
  282. ax::mojom::StringAttribute::kValue));
  283. EXPECT_TRUE(paragraph_0_node->GetComputedNodeData().HasOrCanComputeAttribute(
  284. ax::mojom::StringAttribute::kHtmlTag));
  285. EXPECT_TRUE(paragraph_0_node->GetComputedNodeData().HasOrCanComputeAttribute(
  286. ax::mojom::IntListAttribute::kWordStarts));
  287. EXPECT_FALSE(paragraph_0_node->GetComputedNodeData().HasOrCanComputeAttribute(
  288. ax::mojom::IntListAttribute::kLabelledbyIds));
  289. }
  290. TEST_F(AXComputedNodeDataTest, GetOrComputeAttribute) {
  291. // Embedded object behavior is dependant on platform. We manually set it to a
  292. // specific value so that test results are consistent across platforms.
  293. ScopedAXEmbeddedObjectBehaviorSetter embedded_object_behaviour(
  294. AXEmbeddedObjectBehavior::kSuppressCharacter);
  295. // Line breaks should be inserted between each paragraph to mirror how HTML's
  296. // "textContent" works.
  297. EXPECT_THAT(root_node_->GetComputedNodeData().GetOrComputeAttributeUTF8(
  298. ax::mojom::StringAttribute::kValue),
  299. StrEq("\nt_1\ns+t++2...0. 0s t\n2\r0\r\n1"));
  300. EXPECT_THAT(root_node_->GetComputedNodeData().GetOrComputeAttributeUTF8(
  301. ax::mojom::StringAttribute::kHtmlTag),
  302. StrEq(""));
  303. // Boundaries are delimited by a vertical bar, '|'.
  304. // Words: "|t|_|1s|+|t|++|2|...|0|. |0s| |t|\n|2|\r|0|\r\n|1|".
  305. int32_t word_starts[] = {0, 5, 8, 12, 16, 19, 21, 23, 26};
  306. int32_t word_ends[] = {4, 6, 9, 13, 18, 20, 22, 24, 27};
  307. EXPECT_THAT(root_node_->GetComputedNodeData().GetOrComputeAttribute(
  308. ax::mojom::IntListAttribute::kWordStarts),
  309. ElementsAreArray(word_starts));
  310. EXPECT_THAT(root_node_->GetComputedNodeData().GetOrComputeAttribute(
  311. ax::mojom::IntListAttribute::kWordEnds),
  312. ElementsAreArray(word_ends));
  313. // Lines: "|t_1s+t++2...0. 0s t|\n|2|\r|0|\r\n|1|".
  314. int32_t line_starts[] = {0, 21, 23, 26};
  315. int32_t line_ends[] = {21, 23, 26, 27};
  316. EXPECT_THAT(root_node_->GetComputedNodeData().GetOrComputeAttribute(
  317. ax::mojom::IntListAttribute::kLineStarts),
  318. ElementsAreArray(line_starts));
  319. EXPECT_THAT(root_node_->GetComputedNodeData().GetOrComputeAttribute(
  320. ax::mojom::IntListAttribute::kLineEnds),
  321. ElementsAreArray(line_ends));
  322. // Sentences: "|t_1s+t++2...0.| |0s t|\n|2|\r|0|\r\n|1|".
  323. int32_t sentence_starts[] = {0, 21, 23, 26};
  324. int32_t sentence_ends[] = {21, 23, 26, 27};
  325. EXPECT_THAT(root_node_->GetComputedNodeData().GetOrComputeAttribute(
  326. ax::mojom::IntListAttribute::kSentenceStarts),
  327. ElementsAreArray(sentence_starts));
  328. EXPECT_THAT(root_node_->GetComputedNodeData().GetOrComputeAttribute(
  329. ax::mojom::IntListAttribute::kSentenceEnds),
  330. ElementsAreArray(sentence_ends));
  331. EXPECT_THAT(root_node_->GetComputedNodeData().GetOrComputeAttribute(
  332. ax::mojom::IntListAttribute::kLabelledbyIds),
  333. SizeIs(0));
  334. AXNode* paragraph_0_node = root_node_->GetChildAtIndex(0);
  335. EXPECT_THAT(paragraph_0_node->GetComputedNodeData().GetOrComputeAttributeUTF8(
  336. ax::mojom::StringAttribute::kValue),
  337. StrEq(""))
  338. << "The static text child should be ignored.";
  339. EXPECT_THAT(paragraph_0_node->GetComputedNodeData().GetOrComputeAttributeUTF8(
  340. ax::mojom::StringAttribute::kHtmlTag),
  341. StrEq(""));
  342. // Ignored text produces no boundaries.
  343. EXPECT_THAT(paragraph_0_node->GetComputedNodeData().GetOrComputeAttribute(
  344. ax::mojom::IntListAttribute::kWordStarts),
  345. SizeIs(0));
  346. EXPECT_THAT(paragraph_0_node->GetComputedNodeData().GetOrComputeAttribute(
  347. ax::mojom::IntListAttribute::kWordEnds),
  348. SizeIs(0));
  349. EXPECT_THAT(paragraph_0_node->GetComputedNodeData().GetOrComputeAttribute(
  350. ax::mojom::IntListAttribute::kLineStarts),
  351. SizeIs(0));
  352. EXPECT_THAT(paragraph_0_node->GetComputedNodeData().GetOrComputeAttribute(
  353. ax::mojom::IntListAttribute::kLineEnds),
  354. SizeIs(0));
  355. EXPECT_THAT(paragraph_0_node->GetComputedNodeData().GetOrComputeAttribute(
  356. ax::mojom::IntListAttribute::kSentenceStarts),
  357. SizeIs(0));
  358. EXPECT_THAT(paragraph_0_node->GetComputedNodeData().GetOrComputeAttribute(
  359. ax::mojom::IntListAttribute::kSentenceEnds),
  360. SizeIs(0));
  361. EXPECT_THAT(paragraph_0_node->GetComputedNodeData().GetOrComputeAttribute(
  362. ax::mojom::IntListAttribute::kLabelledbyIds),
  363. SizeIs(0));
  364. // By removing the `ax::mojom::BoolAttribute::kNonAtomicTextFieldRoot`
  365. // attribute, the root is no longer a content editable.
  366. root_.RemoveBoolAttribute(ax::mojom::BoolAttribute::kNonAtomicTextFieldRoot);
  367. root_.AddIntListAttribute(ax::mojom::IntListAttribute::kLabelledbyIds,
  368. {static_text_0_0_ignored_.id});
  369. paragraph_0_.AddStringAttribute(ax::mojom::StringAttribute::kValue,
  370. "New: \nvalue.");
  371. paragraph_0_.AddStringAttribute(ax::mojom::StringAttribute::kHtmlTag, "p");
  372. // Word starts/ends are intentionally set to the wrong values to ensure that
  373. // `AXNodeData` takes priority over `AXComputedNodeData` if present.
  374. std::vector<int32_t> wrong_word_starts = {1, 5};
  375. std::vector<int32_t> wrong_word_ends = {6, 8};
  376. paragraph_0_.AddIntListAttribute(ax::mojom::IntListAttribute::kWordStarts,
  377. wrong_word_starts);
  378. paragraph_0_.AddIntListAttribute(ax::mojom::IntListAttribute::kWordEnds,
  379. wrong_word_ends);
  380. AXTreeUpdate tree_update;
  381. tree_update.root_id = root_.id;
  382. tree_update.nodes = {root_, paragraph_0_};
  383. ASSERT_TRUE(GetTree()->Unserialize(tree_update));
  384. root_node_ = GetTree()->root();
  385. ASSERT_EQ(root_.id, root_node_->id());
  386. EXPECT_THAT(root_node_->GetComputedNodeData().GetOrComputeAttributeUTF8(
  387. ax::mojom::StringAttribute::kValue),
  388. SizeIs(0))
  389. << "Computing the value attribute is only supported on non-atomic text "
  390. "fields.";
  391. EXPECT_THAT(root_node_->GetComputedNodeData().GetOrComputeAttributeUTF8(
  392. ax::mojom::StringAttribute::kHtmlTag),
  393. SizeIs(0));
  394. // No change to the various boundaries should have been observed since the
  395. // root's text content hasn't changed.
  396. EXPECT_THAT(root_node_->GetComputedNodeData().GetOrComputeAttribute(
  397. ax::mojom::IntListAttribute::kWordStarts),
  398. ElementsAreArray(word_starts));
  399. EXPECT_THAT(root_node_->GetComputedNodeData().GetOrComputeAttribute(
  400. ax::mojom::IntListAttribute::kWordEnds),
  401. ElementsAreArray(word_ends));
  402. EXPECT_THAT(root_node_->GetComputedNodeData().GetOrComputeAttribute(
  403. ax::mojom::IntListAttribute::kLineStarts),
  404. ElementsAreArray(line_starts));
  405. EXPECT_THAT(root_node_->GetComputedNodeData().GetOrComputeAttribute(
  406. ax::mojom::IntListAttribute::kLineEnds),
  407. ElementsAreArray(line_ends));
  408. EXPECT_THAT(root_node_->GetComputedNodeData().GetOrComputeAttribute(
  409. ax::mojom::IntListAttribute::kSentenceStarts),
  410. ElementsAreArray(sentence_starts));
  411. EXPECT_THAT(root_node_->GetComputedNodeData().GetOrComputeAttribute(
  412. ax::mojom::IntListAttribute::kSentenceEnds),
  413. ElementsAreArray(sentence_ends));
  414. EXPECT_THAT(root_node_->GetComputedNodeData().GetOrComputeAttribute(
  415. ax::mojom::IntListAttribute::kLabelledbyIds),
  416. ElementsAre(static_text_0_0_ignored_.id));
  417. paragraph_0_node = root_node_->GetChildAtIndex(0);
  418. EXPECT_THAT(paragraph_0_node->GetComputedNodeData().GetOrComputeAttributeUTF8(
  419. ax::mojom::StringAttribute::kValue),
  420. StrEq("New: \nvalue."))
  421. << "For maximum flexibility, if the value attribute is already present "
  422. "in the node's data we should use it without checking if the role "
  423. "supports it.";
  424. EXPECT_THAT(paragraph_0_node->GetComputedNodeData().GetOrComputeAttributeUTF8(
  425. ax::mojom::StringAttribute::kHtmlTag),
  426. StrEq("p"));
  427. // Word starts/ends are intentionally set to the wrong values in `AXNodeData`.
  428. EXPECT_THAT(paragraph_0_node->GetComputedNodeData().GetOrComputeAttribute(
  429. ax::mojom::IntListAttribute::kWordStarts),
  430. ElementsAreArray(wrong_word_starts))
  431. << "`AXNodeData` should take priority over `AXComputedNodeData`, if "
  432. "present.";
  433. EXPECT_THAT(paragraph_0_node->GetComputedNodeData().GetOrComputeAttribute(
  434. ax::mojom::IntListAttribute::kWordEnds),
  435. ElementsAreArray(wrong_word_ends))
  436. << "`AXNodeData` should take priority over `AXComputedNodeData`, if "
  437. "present.";
  438. EXPECT_THAT(paragraph_0_node->GetComputedNodeData().GetOrComputeAttribute(
  439. ax::mojom::IntListAttribute::kLineStarts),
  440. ElementsAre());
  441. EXPECT_THAT(paragraph_0_node->GetComputedNodeData().GetOrComputeAttribute(
  442. ax::mojom::IntListAttribute::kLineEnds),
  443. ElementsAre());
  444. EXPECT_THAT(paragraph_0_node->GetComputedNodeData().GetOrComputeAttribute(
  445. ax::mojom::IntListAttribute::kSentenceStarts),
  446. ElementsAre());
  447. EXPECT_THAT(paragraph_0_node->GetComputedNodeData().GetOrComputeAttribute(
  448. ax::mojom::IntListAttribute::kSentenceEnds),
  449. ElementsAre());
  450. EXPECT_THAT(paragraph_0_node->GetComputedNodeData().GetOrComputeAttribute(
  451. ax::mojom::IntListAttribute::kLabelledbyIds),
  452. SizeIs(0));
  453. }
  454. TEST_F(AXComputedNodeDataTest, GetOrComputeTextContent) {
  455. // Embedded object behavior is dependant on platform. We manually set it to a
  456. // specific value so that test results are consistent across platforms.
  457. ScopedAXEmbeddedObjectBehaviorSetter embedded_object_behaviour(
  458. AXEmbeddedObjectBehavior::kSuppressCharacter);
  459. EXPECT_THAT(root_node_->GetComputedNodeData()
  460. .GetOrComputeTextContentWithParagraphBreaksUTF8(),
  461. StrEq("\nt_1\ns+t++2...0. 0s t\n2\r0\r\n1"));
  462. EXPECT_THAT(root_node_->GetComputedNodeData().GetOrComputeTextContentUTF8(),
  463. StrEq("t_1s+t++2...0. 0s t\n2\r0\r\n1"));
  464. EXPECT_EQ(
  465. root_node_->GetComputedNodeData().GetOrComputeTextContentLengthUTF8(),
  466. 27);
  467. // Paragraph_0's text is ignored. Ignored text should not be visible.
  468. const AXNode* paragraph_0_node = root_node_->GetChildAtIndex(0);
  469. EXPECT_THAT(paragraph_0_node->GetComputedNodeData()
  470. .GetOrComputeTextContentWithParagraphBreaksUTF8(),
  471. StrEq(""));
  472. EXPECT_THAT(
  473. paragraph_0_node->GetComputedNodeData().GetOrComputeTextContentUTF8(),
  474. StrEq(""));
  475. EXPECT_EQ(paragraph_0_node->GetComputedNodeData()
  476. .GetOrComputeTextContentLengthUTF8(),
  477. 0);
  478. // The two incarnations of the "TextContent" methods should behave identically
  479. // when line breaks are manually inserted via e.g. a <br> element in HTML, as
  480. // this case demonstrates.
  481. const AXNode* paragraph_2_ignored_node = root_node_->GetChildAtIndex(2);
  482. EXPECT_THAT(paragraph_2_ignored_node->GetComputedNodeData()
  483. .GetOrComputeTextContentWithParagraphBreaksUTF8(),
  484. StrEq("s+t++2...0. 0s t\n2\r0\r\n1"));
  485. EXPECT_THAT(paragraph_2_ignored_node->GetComputedNodeData()
  486. .GetOrComputeTextContentUTF8(),
  487. StrEq("s+t++2...0. 0s t\n2\r0\r\n1"));
  488. EXPECT_EQ(paragraph_2_ignored_node->GetComputedNodeData()
  489. .GetOrComputeTextContentLengthUTF8(),
  490. 24);
  491. }
  492. } // namespace ui