Avi Drissman | 3e1a26c | 2022-09-15 20:26:03 | [diff] [blame] | 1 | // Copyright 2016 The Chromium Authors |
nektar | 9de71d4 | 2016-10-21 21:57:00 | [diff] [blame] | 2 | // Use of this source code is governed by a BSD-style license that can be |
| 3 | // found in the LICENSE file. |
| 4 | |
| 5 | #include "ui/accessibility/ax_node_position.h" |
| 6 | |
Benjamin Beaudry | 0a4cf91d | 2020-01-06 23:57:04 | [diff] [blame] | 7 | #include "build/build_config.h" |
Dominic Mazzoni | dcef1b73 | 2018-01-26 17:57:04 | [diff] [blame] | 8 | #include "ui/accessibility/ax_enums.mojom.h" |
Nektarios Paisios | 88d7a91 | 2021-02-03 16:45:28 | [diff] [blame] | 9 | #include "ui/base/buildflags.h" |
nektar | 9de71d4 | 2016-10-21 21:57:00 | [diff] [blame] | 10 | |
| 11 | namespace ui { |
| 12 | |
Nektarios Paisios | 88d7a91 | 2021-02-03 16:45:28 | [diff] [blame] | 13 | // On some platforms, most objects are represented in the text of their parents |
| 14 | // with a special "embedded object character" and not with their actual text |
| 15 | // contents. Also on the same platforms, if a node has only ignored descendants, |
| 16 | // i.e., it appears to be empty to assistive software, we need to treat it as a |
| 17 | // character and a word boundary. |
Benjamin Beaudry | 0a4cf91d | 2020-01-06 23:57:04 | [diff] [blame] | 18 | AXEmbeddedObjectBehavior g_ax_embedded_object_behavior = |
Xiaohan Wang | aa4b1a3b | 2022-01-20 21:32:19 | [diff] [blame] | 19 | #if BUILDFLAG(IS_WIN) || BUILDFLAG(USE_ATK) |
Benjamin Beaudry | 0a4cf91d | 2020-01-06 23:57:04 | [diff] [blame] | 20 | AXEmbeddedObjectBehavior::kExposeCharacter; |
| 21 | #else |
| 22 | AXEmbeddedObjectBehavior::kSuppressCharacter; |
Xiaohan Wang | aa4b1a3b | 2022-01-20 21:32:19 | [diff] [blame] | 23 | #endif // BUILDFLAG(IS_WIN) || BUILDFLAG(USE_ATK) |
Benjamin Beaudry | 0a4cf91d | 2020-01-06 23:57:04 | [diff] [blame] | 24 | |
David Bienvenu | b10c69e | 2021-05-11 22:52:11 | [diff] [blame] | 25 | ScopedAXEmbeddedObjectBehaviorSetter::ScopedAXEmbeddedObjectBehaviorSetter( |
| 26 | AXEmbeddedObjectBehavior behavior) { |
| 27 | prev_behavior_ = g_ax_embedded_object_behavior; |
| 28 | g_ax_embedded_object_behavior = behavior; |
| 29 | } |
| 30 | |
| 31 | ScopedAXEmbeddedObjectBehaviorSetter::~ScopedAXEmbeddedObjectBehaviorSetter() { |
| 32 | g_ax_embedded_object_behavior = prev_behavior_; |
| 33 | } |
| 34 | |
Nektarios Paisios | aaa4498 | 2022-02-03 08:58:19 | [diff] [blame] | 35 | std::string ToString(const AXPositionKind kind) { |
| 36 | static constexpr auto kKindToString = |
| 37 | base::MakeFixedFlatMap<AXPositionKind, const char*>( |
| 38 | {{AXPositionKind::NULL_POSITION, "NullPosition"}, |
| 39 | {AXPositionKind::TREE_POSITION, "TreePosition"}, |
| 40 | {AXPositionKind::TEXT_POSITION, "TextPosition"}}); |
| 41 | |
| 42 | const auto* iter = kKindToString.find(kind); |
| 43 | if (iter == std::end(kKindToString)) |
| 44 | return std::string(); |
| 45 | return iter->second; |
| 46 | } |
| 47 | |
Nektarios Paisios | d88553dd | 2019-12-10 02:25:00 | [diff] [blame] | 48 | // static |
| 49 | AXNodePosition::AXPositionInstance AXNodePosition::CreatePosition( |
| 50 | const AXNode& node, |
| 51 | int child_index_or_text_offset, |
| 52 | ax::mojom::TextAffinity affinity) { |
| 53 | if (!node.tree()) |
| 54 | return CreateNullPosition(); |
| 55 | |
| 56 | AXTreeID tree_id = node.tree()->GetAXTreeID(); |
Aaron Leventhal | 496a51b | 2022-09-30 16:17:45 | [diff] [blame] | 57 | if (IsTextPositionAnchor(node)) { |
| 58 | // TODO(accessibility) It is a mistake for the to caller try to create a |
| 59 | // text position with BEFORE_TEXT as the text offset. Correct the callers |
| 60 | // that are doing this. |
| 61 | // DCHECK_NE(child_index_or_text_offset, BEFORE_TEXT) |
| 62 | // << "Creating a text position with BEFORE_TEXT as the offset is illegal " |
| 63 | // "and disallowed."; |
| 64 | int text_offset = child_index_or_text_offset == BEFORE_TEXT |
| 65 | ? 0 |
| 66 | : child_index_or_text_offset; |
Alexander Surkov | 2273769 | 2022-10-21 18:16:30 | [diff] [blame] | 67 | return CreateTextPosition(node, text_offset, affinity); |
Nektarios Paisios | 993145d4 | 2020-05-29 13:55:28 | [diff] [blame] | 68 | } |
Nektarios Paisios | d88553dd | 2019-12-10 02:25:00 | [diff] [blame] | 69 | |
Aaron Leventhal | 496a51b | 2022-09-30 16:17:45 | [diff] [blame] | 70 | DCHECK_LE(child_index_or_text_offset, |
| 71 | static_cast<int>(node.GetChildCountCrossingTreeBoundary())) |
| 72 | << "\n* Trying to create a tree position with a child index that is too " |
| 73 | "large. Maybe a text position should have been created instead?\n" |
| 74 | << "\n* Anchor node: " << node << "\n* IsLeaf(): " << node.IsLeaf() |
| 75 | << "\n* Child offset: " << child_index_or_text_offset |
| 76 | << "\n* IsLeafNodeForTreePosition(): " << IsLeafNodeForTreePosition(node) |
| 77 | << "\n* Tree: " << node.tree()->ToString(); |
| 78 | |
Alexander Surkov | 29889c7f | 2022-10-21 11:35:35 | [diff] [blame] | 79 | return CreateTreePosition(node, child_index_or_text_offset); |
Nektarios Paisios | d88553dd | 2019-12-10 02:25:00 | [diff] [blame] | 80 | } |
| 81 | |
Aaron Leventhal | 496a51b | 2022-09-30 16:17:45 | [diff] [blame] | 82 | // static |
| 83 | bool AXNodePosition::IsTextPositionAnchor(const AXNode& node) { |
| 84 | // TODO(accessibility) Simplify. Not actually sure if this is the correct |
| 85 | // thing for the case where IsLeaf() == false but IsLeafNodeForTreePosition() |
| 86 | // is true. |
| 87 | if (node.IsLeaf()) |
| 88 | return true; |
| 89 | |
| 90 | // TODO(accessibility) Try to remove this condition. Text positions for a |
| 91 | // selection operation should only be created inside selectable text. |
| 92 | // A list marker for example is not selectable text: it would either be |
| 93 | // selected as a whole or not selected, and you can't select half of it. |
| 94 | if (IsLeafNodeForTreePosition(node)) |
| 95 | return true; |
| 96 | |
| 97 | if (node.GetRole() == ax::mojom::Role::kSpinButton) { |
| 98 | // TODO(benjamin.beaudry) Please look into whether this code needs to |
| 99 | // remain, or can be simplified. |
| 100 | return true; |
| 101 | } |
| 102 | |
| 103 | // Ignored atomic text fields and spin buttons are not considered leaves by |
| 104 | // AXNode::IsLeaf(), but should always use a text position. |
| 105 | if (node.data().IsAtomicTextField()) { |
| 106 | // Ignored atomic text fields and spin buttons are not considered leaves by |
| 107 | // AXNode::IsLeaf(), but should always use a text position. |
| 108 | // TODO(accessibility) Nobody should be creating a text position on an |
| 109 | // ignored text field. |
| 110 | DCHECK(node.IsIgnored()) << "Returned false from IsLeaf(): " << node; |
| 111 | return true; |
| 112 | } |
| 113 | |
| 114 | return false; |
| 115 | } |
| 116 | |
Nektarios Paisios | 376c84c4 | 2019-11-02 09:56:02 | [diff] [blame] | 117 | AXNodePosition::AXNodePosition() = default; |
nektar | 9de71d4 | 2016-10-21 21:57:00 | [diff] [blame] | 118 | |
Nektarios Paisios | 376c84c4 | 2019-11-02 09:56:02 | [diff] [blame] | 119 | AXNodePosition::~AXNodePosition() = default; |
| 120 | |
| 121 | AXNodePosition::AXNodePosition(const AXNodePosition& other) |
| 122 | : AXPosition<AXNodePosition, AXNode>(other) {} |
nektar | 9de71d4 | 2016-10-21 21:57:00 | [diff] [blame] | 123 | |
nektar | 491c673 | 2016-12-14 00:14:30 | [diff] [blame] | 124 | AXNodePosition::AXPositionInstance AXNodePosition::Clone() const { |
| 125 | return AXPositionInstance(new AXNodePosition(*this)); |
| 126 | } |
| 127 | |
nektar | 9de71d4 | 2016-10-21 21:57:00 | [diff] [blame] | 128 | } // namespace ui |