ax_computed_node_data.cc 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518
  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 "base/check_op.h"
  6. #include "base/i18n/break_iterator.h"
  7. #include "base/logging.h"
  8. #include "base/numerics/safe_conversions.h"
  9. #include "base/strings/string_util.h"
  10. #include "base/strings/utf_string_conversions.h"
  11. #include "ui/accessibility/ax_enums.mojom.h"
  12. #include "ui/accessibility/ax_node.h"
  13. #include "ui/accessibility/ax_node_position.h"
  14. #include "ui/accessibility/ax_position.h"
  15. #include "ui/accessibility/ax_range.h"
  16. #include "ui/accessibility/ax_tree_manager.h"
  17. #include "ui/accessibility/ax_tree_manager_map.h"
  18. namespace ui {
  19. AXComputedNodeData::AXComputedNodeData(const AXNode& node) : owner_(&node) {}
  20. AXComputedNodeData::~AXComputedNodeData() = default;
  21. int AXComputedNodeData::GetOrComputeUnignoredIndexInParent() const {
  22. DCHECK(!owner_->IsIgnored())
  23. << "Ignored nodes cannot have an `unignored index in parent`.\n"
  24. << *owner_;
  25. if (unignored_index_in_parent_)
  26. return *unignored_index_in_parent_;
  27. if (const AXNode* unignored_parent = SlowGetUnignoredParent()) {
  28. DCHECK_NE(unignored_parent->id(), kInvalidAXNodeID)
  29. << "All nodes should have a valid ID.\n"
  30. << *owner_;
  31. unignored_parent->GetComputedNodeData().ComputeUnignoredValues();
  32. } else {
  33. // This should be the root node and, by convention, we assign it an
  34. // index-in-parent of 0.
  35. unignored_index_in_parent_ = 0;
  36. unignored_parent_id_ = kInvalidAXNodeID;
  37. }
  38. return *unignored_index_in_parent_;
  39. }
  40. AXNodeID AXComputedNodeData::GetOrComputeUnignoredParentID() const {
  41. if (unignored_parent_id_)
  42. return *unignored_parent_id_;
  43. if (const AXNode* unignored_parent = SlowGetUnignoredParent()) {
  44. DCHECK_NE(unignored_parent->id(), kInvalidAXNodeID)
  45. << "All nodes should have a valid ID.\n"
  46. << *owner_;
  47. unignored_parent_id_ = unignored_parent->id();
  48. } else {
  49. // This should be the root node and, by convention, we assign it an
  50. // index-in-parent of 0.
  51. DCHECK(!owner_->GetParent())
  52. << "If `unignored_parent` is nullptr, then this should be the "
  53. "rootnode, since in all trees the rootnode should be unignored.\n"
  54. << *owner_;
  55. unignored_index_in_parent_ = 0;
  56. unignored_parent_id_ = kInvalidAXNodeID;
  57. }
  58. return *unignored_parent_id_;
  59. }
  60. AXNode* AXComputedNodeData::GetOrComputeUnignoredParent() const {
  61. DCHECK(owner_->tree())
  62. << "All nodes should be owned by an accessibility tree.\n"
  63. << *owner_;
  64. return owner_->tree()->GetFromId(GetOrComputeUnignoredParentID());
  65. }
  66. int AXComputedNodeData::GetOrComputeUnignoredChildCount() const {
  67. DCHECK(!owner_->IsIgnored())
  68. << "Ignored nodes cannot have an `unignored child count`.\n"
  69. << *owner_;
  70. if (!unignored_child_count_)
  71. ComputeUnignoredValues();
  72. return *unignored_child_count_;
  73. }
  74. const std::vector<AXNodeID>& AXComputedNodeData::GetOrComputeUnignoredChildIDs()
  75. const {
  76. DCHECK(!owner_->IsIgnored())
  77. << "Ignored nodes cannot have `unignored child IDs`.\n"
  78. << *owner_;
  79. if (!unignored_child_ids_)
  80. ComputeUnignoredValues();
  81. return *unignored_child_ids_;
  82. }
  83. bool AXComputedNodeData::GetOrComputeIsDescendantOfPlatformLeaf() const {
  84. if (!is_descendant_of_leaf_)
  85. ComputeIsDescendantOfPlatformLeaf();
  86. return *is_descendant_of_leaf_;
  87. }
  88. bool AXComputedNodeData::HasOrCanComputeAttribute(
  89. const ax::mojom::StringAttribute attribute) const {
  90. if (owner_->data().HasStringAttribute(attribute))
  91. return true;
  92. switch (attribute) {
  93. case ax::mojom::StringAttribute::kValue:
  94. // The value attribute could be computed on the browser for content
  95. // editables and ARIA text/search boxes.
  96. return owner_->data().IsNonAtomicTextField();
  97. default:
  98. return false;
  99. }
  100. }
  101. bool AXComputedNodeData::HasOrCanComputeAttribute(
  102. const ax::mojom::IntListAttribute attribute) const {
  103. if (owner_->data().HasIntListAttribute(attribute))
  104. return true;
  105. switch (attribute) {
  106. case ax::mojom::IntListAttribute::kLineStarts:
  107. case ax::mojom::IntListAttribute::kLineEnds:
  108. case ax::mojom::IntListAttribute::kSentenceStarts:
  109. case ax::mojom::IntListAttribute::kSentenceEnds:
  110. case ax::mojom::IntListAttribute::kWordStarts:
  111. case ax::mojom::IntListAttribute::kWordEnds:
  112. return true;
  113. default:
  114. return false;
  115. }
  116. }
  117. const std::string& AXComputedNodeData::GetOrComputeAttributeUTF8(
  118. const ax::mojom::StringAttribute attribute) const {
  119. if (owner_->data().HasStringAttribute(attribute))
  120. return owner_->data().GetStringAttribute(attribute);
  121. switch (attribute) {
  122. case ax::mojom::StringAttribute::kValue:
  123. if (owner_->data().IsNonAtomicTextField()) {
  124. DCHECK(HasOrCanComputeAttribute(attribute))
  125. << "Code in `HasOrCanComputeAttribute` should be in sync with "
  126. "'GetOrComputeAttributeUTF8`";
  127. return GetOrComputeTextContentWithParagraphBreaksUTF8();
  128. }
  129. // If an atomic text field has no value attribute sent from the renderer,
  130. // then it means that it is empty, since we do not compute the values of
  131. // such controls on the browser. The same for all other controls, other
  132. // than non-atomic text fields.
  133. return base::EmptyString();
  134. default:
  135. // This is a special case: for performance reasons do not use
  136. // `base::EmptyString()` in other places throughout the codebase.
  137. return base::EmptyString();
  138. }
  139. }
  140. std::u16string AXComputedNodeData::GetOrComputeAttributeUTF16(
  141. const ax::mojom::StringAttribute attribute) const {
  142. if (owner_->data().HasStringAttribute(attribute))
  143. return owner_->data().GetString16Attribute(attribute);
  144. switch (attribute) {
  145. case ax::mojom::StringAttribute::kValue:
  146. if (owner_->data().IsNonAtomicTextField()) {
  147. DCHECK(HasOrCanComputeAttribute(attribute))
  148. << "Code in `HasOrCanComputeAttribute` should be in sync with "
  149. "'GetOrComputeAttributeUTF16`";
  150. return GetOrComputeTextContentWithParagraphBreaksUTF16();
  151. }
  152. // If an atomic text field has no value attribute sent from the renderer,
  153. // then it means that it is empty, since we do not compute the values of
  154. // such controls on the browser. The same for all other controls, other
  155. // than non-atomic text fields.
  156. return std::u16string();
  157. default:
  158. return std::u16string();
  159. }
  160. }
  161. const std::vector<int32_t>& AXComputedNodeData::GetOrComputeAttribute(
  162. const ax::mojom::IntListAttribute attribute) const {
  163. if (owner_->data().HasIntListAttribute(attribute))
  164. return owner_->data().GetIntListAttribute(attribute);
  165. const std::vector<int32_t>* result = nullptr;
  166. switch (attribute) {
  167. case ax::mojom::IntListAttribute::kLineStarts:
  168. ComputeLineOffsetsIfNeeded();
  169. result = &(*line_starts_);
  170. break;
  171. case ax::mojom::IntListAttribute::kLineEnds:
  172. ComputeLineOffsetsIfNeeded();
  173. result = &(*line_ends_);
  174. break;
  175. case ax::mojom::IntListAttribute::kSentenceStarts:
  176. ComputeSentenceOffsetsIfNeeded();
  177. result = &(*sentence_starts_);
  178. break;
  179. case ax::mojom::IntListAttribute::kSentenceEnds:
  180. ComputeSentenceOffsetsIfNeeded();
  181. result = &(*sentence_ends_);
  182. break;
  183. case ax::mojom::IntListAttribute::kWordStarts:
  184. ComputeWordOffsetsIfNeeded();
  185. result = &(*word_starts_);
  186. break;
  187. case ax::mojom::IntListAttribute::kWordEnds:
  188. ComputeWordOffsetsIfNeeded();
  189. result = &(*word_ends_);
  190. break;
  191. default:
  192. return owner_->data().GetIntListAttribute(
  193. ax::mojom::IntListAttribute::kNone);
  194. }
  195. DCHECK(HasOrCanComputeAttribute(attribute))
  196. << "Code in `HasOrCanComputeAttribute` should be in sync with "
  197. "'GetOrComputeAttribute`";
  198. DCHECK(result);
  199. return *result;
  200. }
  201. const std::string&
  202. AXComputedNodeData::GetOrComputeTextContentWithParagraphBreaksUTF8() const {
  203. if (!text_content_with_paragraph_breaks_utf8_) {
  204. VLOG_IF(1, text_content_with_paragraph_breaks_utf16_)
  205. << "Only a single encoding of text content with paragraph breaks "
  206. "should be cached.";
  207. auto range =
  208. AXRange<AXPosition<AXNodePosition, AXNode>>::RangeOfContents(*owner_);
  209. text_content_with_paragraph_breaks_utf8_ = base::UTF16ToUTF8(
  210. range.GetText(AXTextConcatenationBehavior::kWithParagraphBreaks,
  211. AXEmbeddedObjectBehavior::kSuppressCharacter));
  212. }
  213. return *text_content_with_paragraph_breaks_utf8_;
  214. }
  215. const std::u16string&
  216. AXComputedNodeData::GetOrComputeTextContentWithParagraphBreaksUTF16() const {
  217. if (!text_content_with_paragraph_breaks_utf16_) {
  218. VLOG_IF(1, text_content_with_paragraph_breaks_utf8_)
  219. << "Only a single encoding of text content with paragraph breaks "
  220. "should be cached.";
  221. auto range =
  222. AXRange<AXPosition<AXNodePosition, AXNode>>::RangeOfContents(*owner_);
  223. text_content_with_paragraph_breaks_utf16_ =
  224. range.GetText(AXTextConcatenationBehavior::kWithParagraphBreaks,
  225. AXEmbeddedObjectBehavior::kSuppressCharacter);
  226. }
  227. return *text_content_with_paragraph_breaks_utf16_;
  228. }
  229. const std::string& AXComputedNodeData::GetOrComputeTextContentUTF8() const {
  230. if (!text_content_utf8_) {
  231. VLOG_IF(1, text_content_utf16_)
  232. << "Only a single encoding of text content should be cached.";
  233. text_content_utf8_ = ComputeTextContentUTF8();
  234. }
  235. return *text_content_utf8_;
  236. }
  237. const std::u16string& AXComputedNodeData::GetOrComputeTextContentUTF16() const {
  238. if (!text_content_utf16_) {
  239. VLOG_IF(1, text_content_utf8_)
  240. << "Only a single encoding of text content should be cached.";
  241. text_content_utf16_ = ComputeTextContentUTF16();
  242. }
  243. return *text_content_utf16_;
  244. }
  245. int AXComputedNodeData::GetOrComputeTextContentLengthUTF8() const {
  246. return static_cast<int>(GetOrComputeTextContentUTF8().length());
  247. }
  248. int AXComputedNodeData::GetOrComputeTextContentLengthUTF16() const {
  249. return static_cast<int>(GetOrComputeTextContentUTF16().length());
  250. }
  251. void AXComputedNodeData::ComputeUnignoredValues(
  252. AXNodeID unignored_parent_id,
  253. int starting_index_in_parent) const {
  254. DCHECK_GE(starting_index_in_parent, 0);
  255. // Reset any previously computed values.
  256. unignored_index_in_parent_ = absl::nullopt;
  257. unignored_parent_id_ = absl::nullopt;
  258. unignored_child_count_ = absl::nullopt;
  259. unignored_child_ids_ = absl::nullopt;
  260. AXNodeID unignored_parent_id_for_child = unignored_parent_id;
  261. if (!owner_->IsIgnored())
  262. unignored_parent_id_for_child = owner_->id();
  263. int unignored_child_count = 0;
  264. std::vector<AXNodeID> unignored_child_ids;
  265. for (auto iter = owner_->AllChildrenBegin(); iter != owner_->AllChildrenEnd();
  266. ++iter) {
  267. const AXComputedNodeData& computed_data = iter->GetComputedNodeData();
  268. int new_index_in_parent = starting_index_in_parent + unignored_child_count;
  269. if (iter->IsIgnored()) {
  270. // Skip the ignored node and recursively look at its children.
  271. computed_data.ComputeUnignoredValues(unignored_parent_id_for_child,
  272. new_index_in_parent);
  273. DCHECK(computed_data.unignored_child_count_);
  274. unignored_child_count += *computed_data.unignored_child_count_;
  275. DCHECK(computed_data.unignored_child_ids_);
  276. unignored_child_ids.insert(unignored_child_ids.end(),
  277. computed_data.unignored_child_ids_->begin(),
  278. computed_data.unignored_child_ids_->end());
  279. } else {
  280. // Setting `unignored_index_in_parent_` and `unignored_parent_id_` is the
  281. // responsibility of the parent node, since only the parent node can
  282. // calculate these values. This is in contrast to `unignored_child_count_`
  283. // and `unignored_child_ids_` that are only set if this method is called
  284. // on the node itself.
  285. computed_data.unignored_index_in_parent_ = new_index_in_parent;
  286. if (unignored_parent_id_for_child != kInvalidAXNodeID)
  287. computed_data.unignored_parent_id_ = unignored_parent_id_for_child;
  288. ++unignored_child_count;
  289. unignored_child_ids.push_back(iter->id());
  290. }
  291. }
  292. if (unignored_parent_id != kInvalidAXNodeID)
  293. unignored_parent_id_ = unignored_parent_id;
  294. // Ignored nodes store unignored child information in order to propagate it to
  295. // their parents, but do not expose it directly. The latter is guarded via a
  296. // DCHECK.
  297. unignored_child_count_ = unignored_child_count;
  298. unignored_child_ids_ = unignored_child_ids;
  299. }
  300. AXNode* AXComputedNodeData::SlowGetUnignoredParent() const {
  301. AXNode* unignored_parent = owner_->GetParent();
  302. while (unignored_parent && unignored_parent->IsIgnored())
  303. unignored_parent = unignored_parent->GetParent();
  304. return unignored_parent;
  305. }
  306. void AXComputedNodeData::ComputeIsDescendantOfPlatformLeaf() const {
  307. is_descendant_of_leaf_ = false;
  308. for (const AXNode* ancestor = GetOrComputeUnignoredParent(); ancestor;
  309. ancestor =
  310. ancestor->GetComputedNodeData().GetOrComputeUnignoredParent()) {
  311. if (ancestor->GetComputedNodeData().is_descendant_of_leaf_.value_or(
  312. false) ||
  313. ancestor->IsLeaf()) {
  314. is_descendant_of_leaf_ = true;
  315. return;
  316. }
  317. }
  318. }
  319. void AXComputedNodeData::ComputeLineOffsetsIfNeeded() const {
  320. if (line_starts_ || line_ends_) {
  321. DCHECK_EQ(line_starts_->size(), line_ends_->size());
  322. return; // Already cached.
  323. }
  324. line_starts_ = std::vector<int32_t>();
  325. line_ends_ = std::vector<int32_t>();
  326. const std::u16string& text_content = GetOrComputeTextContentUTF16();
  327. if (text_content.empty())
  328. return;
  329. // TODO(nektar): Using the `base::i18n::BreakIterator` class is not enough. We
  330. // also need to pass information from Blink as to which inline text boxes
  331. // start a new line and deprecate next/previous_on_line.
  332. base::i18n::BreakIterator iter(text_content,
  333. base::i18n::BreakIterator::BREAK_NEWLINE);
  334. if (!iter.Init())
  335. return;
  336. while (iter.Advance()) {
  337. line_starts_->push_back(base::checked_cast<int32_t>(iter.prev()));
  338. line_ends_->push_back(base::checked_cast<int32_t>(iter.pos()));
  339. }
  340. }
  341. void AXComputedNodeData::ComputeSentenceOffsetsIfNeeded() const {
  342. if (sentence_starts_ || sentence_ends_) {
  343. DCHECK_EQ(sentence_starts_->size(), sentence_ends_->size());
  344. return; // Already cached.
  345. }
  346. sentence_starts_ = std::vector<int32_t>();
  347. sentence_ends_ = std::vector<int32_t>();
  348. if (owner_->IsLineBreak())
  349. return;
  350. const std::u16string& text_content = GetOrComputeTextContentUTF16();
  351. if (text_content.empty() ||
  352. base::ContainsOnlyChars(text_content, base::kWhitespaceUTF16)) {
  353. return;
  354. }
  355. // Unlike in ICU, a sentence boundary is not valid in Blink if it falls within
  356. // some whitespace that is used to separate sentences. We therefore need to
  357. // filter the boundaries returned by ICU and return a subset of them. For
  358. // example we should exclude a sentence boundary that is between two space
  359. // characters, "Hello. | there.".
  360. // TODO(nektar): The above is not accomplished simply by using the
  361. // `base::i18n::BreakIterator` class.
  362. base::i18n::BreakIterator iter(text_content,
  363. base::i18n::BreakIterator::BREAK_SENTENCE);
  364. if (!iter.Init())
  365. return;
  366. while (iter.Advance()) {
  367. sentence_starts_->push_back(base::checked_cast<int32_t>(iter.prev()));
  368. sentence_ends_->push_back(base::checked_cast<int32_t>(iter.pos()));
  369. }
  370. }
  371. void AXComputedNodeData::ComputeWordOffsetsIfNeeded() const {
  372. if (word_starts_ || word_ends_) {
  373. DCHECK_EQ(word_starts_->size(), word_ends_->size());
  374. return; // Already cached.
  375. }
  376. word_starts_ = std::vector<int32_t>();
  377. word_ends_ = std::vector<int32_t>();
  378. const std::u16string& text_content = GetOrComputeTextContentUTF16();
  379. if (text_content.empty())
  380. return;
  381. // Unlike in ICU, a word boundary is valid in Blink only if it is before, or
  382. // immediately preceded by, an alphanumeric character, a series of punctuation
  383. // marks, an underscore or a line break. We therefore need to filter the
  384. // boundaries returned by ICU and return a subset of them. For example we
  385. // should exclude a word boundary that is between two space characters, "Hello
  386. // | there".
  387. // TODO(nektar): Fix the fact that the `base::i18n::BreakIterator` class does
  388. // not take into account underscores as word separators.
  389. base::i18n::BreakIterator iter(text_content,
  390. base::i18n::BreakIterator::BREAK_WORD);
  391. if (!iter.Init())
  392. return;
  393. while (iter.Advance()) {
  394. if (iter.IsWord()) {
  395. word_starts_->push_back(base::checked_cast<int>(iter.prev()));
  396. word_ends_->push_back(base::checked_cast<int>(iter.pos()));
  397. }
  398. }
  399. }
  400. std::string AXComputedNodeData::ComputeTextContentUTF8() const {
  401. // If a text field has no descendants, then we compute its text content from
  402. // its value or its placeholder. Otherwise we prefer to look at its descendant
  403. // text nodes because Blink doesn't always add all trailing white space to the
  404. // value attribute.
  405. const bool is_atomic_text_field_without_descendants =
  406. (owner_->data().IsTextField() && !owner_->GetUnignoredChildCount());
  407. if (is_atomic_text_field_without_descendants) {
  408. std::string value =
  409. owner_->data().GetStringAttribute(ax::mojom::StringAttribute::kValue);
  410. // If the value is empty, then there might be some placeholder text in the
  411. // text field, or any other name that is derived from visible contents, even
  412. // if the text field has no children, so we treat this as any other leaf
  413. // node.
  414. if (!value.empty())
  415. return value;
  416. }
  417. // Ordinarily, atomic text fields are leaves, and for all leaves we directly
  418. // retrieve their text content using the information provided by the tree
  419. // source, such as Blink. However, for atomic text fields we need to exclude
  420. // them from the set of leaf nodes when they expose any descendants. This is
  421. // because we want to compute their text content from their descendant text
  422. // nodes as we don't always trust the "value" attribute provided by Blink.
  423. const bool is_atomic_text_field_with_descendants =
  424. (owner_->data().IsTextField() && owner_->GetUnignoredChildCount());
  425. if (owner_->IsLeaf() && !is_atomic_text_field_with_descendants) {
  426. switch (owner_->data().GetNameFrom()) {
  427. case ax::mojom::NameFrom::kNone:
  428. // The accessible name is not displayed on screen, e.g. aria-label, or is
  429. // not displayed directly inside the node, e.g. an associated label
  430. // element.
  431. case ax::mojom::NameFrom::kAttribute:
  432. // The node's accessible name is explicitly empty.
  433. case ax::mojom::NameFrom::kAttributeExplicitlyEmpty:
  434. // The accessible name does not represent the entirety of the node's text
  435. // content, e.g. a table's caption or a figure's figcaption.
  436. case ax::mojom::NameFrom::kCaption:
  437. case ax::mojom::NameFrom::kRelatedElement:
  438. // The accessible name is not displayed directly inside the node but is
  439. // visible via e.g. a tooltip.
  440. case ax::mojom::NameFrom::kTitle:
  441. return std::string();
  442. case ax::mojom::NameFrom::kContents:
  443. // The placeholder text is initially displayed inside the text field and
  444. // takes the place of its value.
  445. case ax::mojom::NameFrom::kPlaceholder:
  446. // The value attribute takes the place of the node's text content, e.g.
  447. // the value of a submit button is displayed inside the button itself.
  448. case ax::mojom::NameFrom::kValue:
  449. return owner_->data().GetStringAttribute(
  450. ax::mojom::StringAttribute::kName);
  451. }
  452. }
  453. std::string text_content;
  454. for (auto it = owner_->UnignoredChildrenCrossingTreeBoundaryBegin();
  455. it != owner_->UnignoredChildrenCrossingTreeBoundaryEnd(); ++it) {
  456. text_content += it->GetTextContentUTF8();
  457. }
  458. return text_content;
  459. }
  460. std::u16string AXComputedNodeData::ComputeTextContentUTF16() const {
  461. return base::UTF8ToUTF16(ComputeTextContentUTF8());
  462. }
  463. } // namespace ui