1 // Copyright 2016 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 
5 #include "ui/accessibility/ax_node_position.h"
6 
7 #include "base/strings/string_util.h"
8 #include "base/strings/utf_string_conversions.h"
9 #include "build/build_config.h"
10 #include "ui/accessibility/ax_enums.mojom.h"
11 #include "ui/accessibility/ax_node_data.h"
12 #include "ui/accessibility/ax_tree_manager.h"
13 #include "ui/accessibility/ax_tree_manager_map.h"
14 
15 namespace ui {
16 
17 AXEmbeddedObjectBehavior g_ax_embedded_object_behavior =
18 #if defined(OS_WIN)
19     AXEmbeddedObjectBehavior::kExposeCharacter;
20 #else
21     AXEmbeddedObjectBehavior::kSuppressCharacter;
22 #endif  // defined(OS_WIN)
23 
24 // static
CreatePosition(const AXNode & node,int child_index_or_text_offset,ax::mojom::TextAffinity affinity)25 AXNodePosition::AXPositionInstance AXNodePosition::CreatePosition(
26     const AXNode& node,
27     int child_index_or_text_offset,
28     ax::mojom::TextAffinity affinity) {
29   if (!node.tree())
30     return CreateNullPosition();
31 
32   AXTreeID tree_id = node.tree()->GetAXTreeID();
33   if (node.IsText()) {
34     return CreateTextPosition(tree_id, node.id(), child_index_or_text_offset,
35                               affinity);
36   }
37 
38   return CreateTreePosition(tree_id, node.id(), child_index_or_text_offset);
39 }
40 
41 AXNodePosition::AXNodePosition() = default;
42 
43 AXNodePosition::~AXNodePosition() = default;
44 
AXNodePosition(const AXNodePosition & other)45 AXNodePosition::AXNodePosition(const AXNodePosition& other)
46     : AXPosition<AXNodePosition, AXNode>(other) {}
47 
Clone() const48 AXNodePosition::AXPositionInstance AXNodePosition::Clone() const {
49   return AXPositionInstance(new AXNodePosition(*this));
50 }
51 
AnchorChild(int child_index,AXTreeID * tree_id,AXNode::AXID * child_id) const52 void AXNodePosition::AnchorChild(int child_index,
53                                  AXTreeID* tree_id,
54                                  AXNode::AXID* child_id) const {
55   DCHECK(tree_id);
56   DCHECK(child_id);
57 
58   if (!GetAnchor() || child_index < 0 || child_index >= AnchorChildCount()) {
59     *tree_id = AXTreeIDUnknown();
60     *child_id = AXNode::kInvalidAXID;
61     return;
62   }
63 
64   AXNode* child = nullptr;
65   const AXTreeManager* child_tree_manager =
66       AXTreeManagerMap::GetInstance().GetManagerForChildTree(*GetAnchor());
67   if (child_tree_manager) {
68     // The child node exists in a separate tree from its parent.
69     child = child_tree_manager->GetRootAsAXNode();
70     *tree_id = child_tree_manager->GetTreeID();
71   } else {
72     child = GetAnchor()->children()[size_t{child_index}];
73     *tree_id = this->tree_id();
74   }
75 
76   DCHECK(child);
77   *child_id = child->id();
78 }
79 
AnchorChildCount() const80 int AXNodePosition::AnchorChildCount() const {
81   if (!GetAnchor())
82     return 0;
83 
84   const AXTreeManager* child_tree_manager =
85       AXTreeManagerMap::GetInstance().GetManagerForChildTree(*GetAnchor());
86   if (child_tree_manager)
87     return 1;
88 
89   return int{GetAnchor()->children().size()};
90 }
91 
AnchorUnignoredChildCount() const92 int AXNodePosition::AnchorUnignoredChildCount() const {
93   if (!GetAnchor())
94     return 0;
95 
96   return static_cast<int>(GetAnchor()->GetUnignoredChildCount());
97 }
98 
AnchorIndexInParent() const99 int AXNodePosition::AnchorIndexInParent() const {
100   return GetAnchor() ? int{GetAnchor()->index_in_parent()} : INVALID_INDEX;
101 }
102 
AnchorSiblingCount() const103 int AXNodePosition::AnchorSiblingCount() const {
104   AXNode* parent = GetAnchor()->GetUnignoredParent();
105   if (parent)
106     return static_cast<int>(parent->GetUnignoredChildCount());
107 
108   return 0;
109 }
110 
GetAncestorAnchors() const111 base::stack<AXNode*> AXNodePosition::GetAncestorAnchors() const {
112   base::stack<AXNode*> anchors;
113   AXNode* current_anchor = GetAnchor();
114 
115   AXNode::AXID current_anchor_id = GetAnchor()->id();
116   AXTreeID current_tree_id = tree_id();
117 
118   AXNode::AXID parent_anchor_id = AXNode::kInvalidAXID;
119   AXTreeID parent_tree_id = AXTreeIDUnknown();
120 
121   while (current_anchor) {
122     anchors.push(current_anchor);
123     current_anchor = GetParent(
124         current_anchor /*child*/, current_tree_id /*child_tree_id*/,
125         &parent_tree_id /*parent_tree_id*/, &parent_anchor_id /*parent_id*/);
126 
127     current_anchor_id = parent_anchor_id;
128     current_tree_id = parent_tree_id;
129   }
130   return anchors;
131 }
132 
GetLowestUnignoredAncestor() const133 AXNode* AXNodePosition::GetLowestUnignoredAncestor() const {
134   if (!GetAnchor())
135     return nullptr;
136 
137   return GetAnchor()->GetUnignoredParent();
138 }
139 
AnchorParent(AXTreeID * tree_id,AXNode::AXID * parent_id) const140 void AXNodePosition::AnchorParent(AXTreeID* tree_id,
141                                   AXNode::AXID* parent_id) const {
142   DCHECK(tree_id);
143   DCHECK(parent_id);
144 
145   *tree_id = AXTreeIDUnknown();
146   *parent_id = AXNode::kInvalidAXID;
147 
148   if (!GetAnchor())
149     return;
150 
151   AXNode* parent =
152       GetParent(GetAnchor() /*child*/, this->tree_id() /*child_tree_id*/,
153                 tree_id /*parent_tree_id*/, parent_id /*parent_id*/);
154 
155   if (!parent) {
156     *tree_id = AXTreeIDUnknown();
157     *parent_id = AXNode::kInvalidAXID;
158   }
159 }
160 
GetNodeInTree(AXTreeID tree_id,AXNode::AXID node_id) const161 AXNode* AXNodePosition::GetNodeInTree(AXTreeID tree_id,
162                                       AXNode::AXID node_id) const {
163   if (node_id == AXNode::kInvalidAXID)
164     return nullptr;
165 
166   AXTreeManager* manager = AXTreeManagerMap::GetInstance().GetManager(tree_id);
167   if (manager)
168     return manager->GetNodeFromTree(tree_id, node_id);
169 
170   return nullptr;
171 }
172 
GetAnchorID(AXNode * node) const173 AXNode::AXID AXNodePosition::GetAnchorID(AXNode* node) const {
174   return node->id();
175 }
176 
GetTreeID(AXNode * node) const177 AXTreeID AXNodePosition::GetTreeID(AXNode* node) const {
178   return node->tree()->GetAXTreeID();
179 }
180 
GetText() const181 base::string16 AXNodePosition::GetText() const {
182   if (IsNullPosition())
183     return base::string16();
184 
185   // Special case, if a node has only ignored descendants, i.e., it appears to
186   // be empty to assistive software, on some platforms we need to still treat it
187   // as a character and a word boundary. We achieve this by adding an embedded
188   // object character in the text representation used by this class, but we
189   // don't expose that character to assistive software that tries to retrieve
190   // the node's inner text.
191   if (IsEmptyObjectReplacedByCharacter())
192     return base::string16(1, kEmbeddedCharacter);
193 
194   const AXNode* anchor = GetAnchor();
195   DCHECK(anchor);
196   switch (g_ax_embedded_object_behavior) {
197     case AXEmbeddedObjectBehavior::kSuppressCharacter:
198       return base::UTF8ToUTF16(anchor->GetInnerText());
199     case AXEmbeddedObjectBehavior::kExposeCharacter:
200       return base::UTF8ToUTF16(anchor->GetHypertext());
201   }
202 }
203 
IsInLineBreak() const204 bool AXNodePosition::IsInLineBreak() const {
205   if (IsNullPosition())
206     return false;
207   DCHECK(GetAnchor());
208   return GetAnchor()->IsLineBreak();
209 }
210 
IsInTextObject() const211 bool AXNodePosition::IsInTextObject() const {
212   if (IsNullPosition())
213     return false;
214   DCHECK(GetAnchor());
215   return GetAnchor()->IsText();
216 }
217 
IsInWhiteSpace() const218 bool AXNodePosition::IsInWhiteSpace() const {
219   if (IsNullPosition())
220     return false;
221   DCHECK(GetAnchor());
222   return GetAnchor()->IsLineBreak() ||
223          base::ContainsOnlyChars(GetText(), base::kWhitespaceUTF16);
224 }
225 
226 // This override is an optimized version AXPosition::MaxTextOffset. Instead of
227 // concatenating the strings in GetText() to then get their text length, we sum
228 // the lengths of the individual strings. This is faster than concatenating the
229 // strings first and then taking their length, especially when the process
230 // is recursive.
MaxTextOffset() const231 int AXNodePosition::MaxTextOffset() const {
232   if (IsNullPosition())
233     return INVALID_OFFSET;
234 
235   if (IsEmptyObjectReplacedByCharacter())
236     return 1;
237 
238   const AXNode* anchor = GetAnchor();
239   DCHECK(anchor);
240   // TODO(nektar): Replace with PlatformChildCount when AXNodePosition and
241   // BrowserAccessibilityPosition will be merged.
242   if (!AnchorChildCount() || anchor->IsText())
243     return base::UTF8ToUTF16(anchor->GetInnerText()).length();
244 
245   int text_length = 0;
246   // This is an optimization over retrieving the text of the whole subtree and
247   // then finding its length. It saves time by adding lengths instead of
248   // concatenating strings.
249   for (int i = 0; i < AnchorChildCount(); ++i)
250     text_length += CreateChildPositionAt(i)->MaxTextOffset();
251 
252   return text_length;
253 }
254 
IsEmbeddedObjectInParent() const255 bool AXNodePosition::IsEmbeddedObjectInParent() const {
256   switch (g_ax_embedded_object_behavior) {
257     case AXEmbeddedObjectBehavior::kSuppressCharacter:
258       return false;
259     case AXEmbeddedObjectBehavior::kExposeCharacter:
260       // We don't need to expose an "embedded object character" for textual
261       // nodes and nodes that are invisible to platform APIs. Textual nodes are
262       // represented by their actual text.
263       return !IsNullPosition() && !GetAnchor()->IsText() &&
264              !GetAnchor()->IsDescendantOfPlainTextField() &&
265              GetAnchor()->IsChildOfLeaf();
266   }
267 }
268 
IsInLineBreakingObject() const269 bool AXNodePosition::IsInLineBreakingObject() const {
270   if (IsNullPosition())
271     return false;
272   DCHECK(GetAnchor());
273   return GetAnchor()->data().GetBoolAttribute(
274              ax::mojom::BoolAttribute::kIsLineBreakingObject) &&
275          !GetAnchor()->IsInListMarker();
276 }
277 
GetAnchorRole() const278 ax::mojom::Role AXNodePosition::GetAnchorRole() const {
279   if (IsNullPosition())
280     return ax::mojom::Role::kNone;
281   DCHECK(GetAnchor());
282   return GetRole(GetAnchor());
283 }
284 
GetRole(AXNode * node) const285 ax::mojom::Role AXNodePosition::GetRole(AXNode* node) const {
286   return node->data().role;
287 }
288 
GetTextStyles() const289 AXNodeTextStyles AXNodePosition::GetTextStyles() const {
290   // Check either the current anchor or its parent for text styles.
291   AXNodeTextStyles current_anchor_text_styles =
292       !IsNullPosition() ? GetAnchor()->data().GetTextStyles()
293                         : AXNodeTextStyles();
294   if (current_anchor_text_styles.IsUnset()) {
295     AXPositionInstance parent = CreateParentPosition();
296     if (!parent->IsNullPosition())
297       return parent->GetAnchor()->data().GetTextStyles();
298   }
299   return current_anchor_text_styles;
300 }
301 
GetWordStartOffsets() const302 std::vector<int32_t> AXNodePosition::GetWordStartOffsets() const {
303   if (IsNullPosition())
304     return std::vector<int32_t>();
305   DCHECK(GetAnchor());
306 
307   // Embedded object replacement characters are not represented in |kWordStarts|
308   // attribute.
309   if (IsEmptyObjectReplacedByCharacter())
310     return {0};
311 
312   return GetAnchor()->data().GetIntListAttribute(
313       ax::mojom::IntListAttribute::kWordStarts);
314 }
315 
GetWordEndOffsets() const316 std::vector<int32_t> AXNodePosition::GetWordEndOffsets() const {
317   if (IsNullPosition())
318     return std::vector<int32_t>();
319   DCHECK(GetAnchor());
320 
321   // Embedded object replacement characters are not represented in |kWordEnds|
322   // attribute. Since the whole text exposed inside of an embedded object is of
323   // length 1 (the embedded object replacement character), the word end offset
324   // is positioned at 1. Because we want to treat the embedded object
325   // replacement characters as ordinary characters, it wouldn't be consistent to
326   // assume they have no length and return 0 instead of 1.
327   if (IsEmptyObjectReplacedByCharacter())
328     return {1};
329 
330   return GetAnchor()->data().GetIntListAttribute(
331       ax::mojom::IntListAttribute::kWordEnds);
332 }
333 
GetNextOnLineID(AXNode::AXID node_id) const334 AXNode::AXID AXNodePosition::GetNextOnLineID(AXNode::AXID node_id) const {
335   if (IsNullPosition())
336     return AXNode::kInvalidAXID;
337   AXNode* node = GetNodeInTree(tree_id(), node_id);
338   int next_on_line_id;
339   if (!node || !node->data().GetIntAttribute(
340                    ax::mojom::IntAttribute::kNextOnLineId, &next_on_line_id)) {
341     return AXNode::kInvalidAXID;
342   }
343   return static_cast<AXNode::AXID>(next_on_line_id);
344 }
345 
GetPreviousOnLineID(AXNode::AXID node_id) const346 AXNode::AXID AXNodePosition::GetPreviousOnLineID(AXNode::AXID node_id) const {
347   if (IsNullPosition())
348     return AXNode::kInvalidAXID;
349   AXNode* node = GetNodeInTree(tree_id(), node_id);
350   int previous_on_line_id;
351   if (!node ||
352       !node->data().GetIntAttribute(ax::mojom::IntAttribute::kPreviousOnLineId,
353                                     &previous_on_line_id)) {
354     return AXNode::kInvalidAXID;
355   }
356   return static_cast<AXNode::AXID>(previous_on_line_id);
357 }
358 
GetParent(AXNode * child,AXTreeID child_tree_id,AXTreeID * parent_tree_id,AXNode::AXID * parent_id)359 AXNode* AXNodePosition::GetParent(AXNode* child,
360                                   AXTreeID child_tree_id,
361                                   AXTreeID* parent_tree_id,
362                                   AXNode::AXID* parent_id) {
363   DCHECK(parent_tree_id);
364   DCHECK(parent_id);
365 
366   *parent_tree_id = AXTreeIDUnknown();
367   *parent_id = AXNode::kInvalidAXID;
368 
369   if (!child)
370     return nullptr;
371 
372   AXNode* parent = child->parent();
373   *parent_tree_id = child_tree_id;
374 
375   if (!parent) {
376     AXTreeManager* manager =
377         AXTreeManagerMap::GetInstance().GetManager(child_tree_id);
378     if (manager) {
379       parent = manager->GetParentNodeFromParentTreeAsAXNode();
380       *parent_tree_id = manager->GetParentTreeID();
381     }
382   }
383 
384   if (!parent) {
385     *parent_tree_id = AXTreeIDUnknown();
386     return parent;
387   }
388 
389   *parent_id = parent->id();
390   return parent;
391 }
392 
393 }  // namespace ui
394