OLD | NEW |
---|---|
(Empty) | |
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 #ifndef UI_ACCESSIBILITY_AX_POSITION_H_ | |
6 #define UI_ACCESSIBILITY_AX_POSITION_H_ | |
7 | |
8 #include <stdint.h> | |
9 | |
10 #include <queue> | |
11 | |
12 #include "ui/accessibility/ax_enums.h" | |
13 #include "ui/accessibility/ax_export.h" | |
14 | |
15 namespace ui { | |
16 | |
17 // Defines the type of position in the accessibility tree. | |
18 // A tree position is used when referring to a specific child of a node in the | |
19 // accessibility tree. | |
20 // A text position is used when referring to a specific character of text inside | |
21 // a particular node. | |
22 // A null position is used to signify that the provided data is invalid or that | |
23 // a boundary has been reached. | |
24 enum class AXPositionKind { NullPosition, TreePosition, TextPosition }; | |
25 | |
26 // A position in the |AXTree|. | |
27 // It could either indicate a non-textual node in the accessibility tree, or a | |
28 // text node and a character offset. | |
29 // A text node has either a role of static_text, inline_text_box or line_break. | |
30 // | |
31 // This class template uses static polymorphism in order to allow sub-classes to | |
32 // be created from the base class without the base class knowing the type of the | |
33 // sub-class in advance. | |
34 // The template argument |AXPositionType| should always be set to the type of | |
35 // any class that inherits from this template, making this a | |
36 // "curiously recursive template". | |
37 template <class AXPositionType, class AXNodeType> | |
38 class AXPosition { | |
39 public: | |
40 AXPosition() {} | |
41 virtual ~AXPosition() {} | |
42 | |
43 static AXPosition<AXPositionType, AXNodeType>* CreateNullPosition() { | |
44 auto new_position = static_cast<AXPosition<AXPositionType, AXNodeType>*>( | |
45 new AXPositionType()); | |
46 DCHECK(new_position); | |
47 new_position->Initialize(-1 /* tree_id */, -1 /* anchor_id */, | |
48 -1 /* child_index */, -1 /* text_offset */, | |
49 AXPositionKind::NullPosition); | |
50 return new_position; | |
51 } | |
52 | |
53 static AXPosition<AXPositionType, AXNodeType>* | |
54 CreateTreePosition(int tree_id, int32_t anchor_id, int child_index) { | |
55 auto new_position = static_cast<AXPosition<AXPositionType, AXNodeType>*>( | |
56 new AXPositionType()); | |
57 DCHECK(new_position); | |
58 new_position->Initialize(tree_id, anchor_id, child_index, | |
59 -1 /* text_offset */, | |
60 AXPositionKind::TreePosition); | |
61 return new_position; | |
62 } | |
63 | |
64 static AXPosition<AXPositionType, AXNodeType>* | |
65 CreateTextPosition(int tree_id, int32_t anchor_id, int text_offset) { | |
66 auto new_position = static_cast<AXPosition<AXPositionType, AXNodeType>*>( | |
67 new AXPositionType()); | |
68 DCHECK(new_position); | |
69 new_position->Initialize(tree_id, anchor_id, -1 /* child_index */, | |
70 text_offset, AXPositionKind::TextPosition); | |
71 return new_position; | |
72 } | |
73 | |
74 int get_tree_id() const { return tree_id_; } | |
75 int32_t get_anchor_id() const { return anchor_id_; } | |
76 | |
77 AXNodeType* GetAnchor() const { | |
78 if (tree_id_ == -1 || anchor_id_ == -1) | |
79 return nullptr; | |
80 DCHECK_GE(tree_id_, 0); | |
81 DCHECK_GE(anchor_id_, 0); | |
82 return GetNodeInTree(tree_id_, anchor_id_); | |
83 } | |
84 | |
85 int get_child_index() const { return child_index_; } | |
86 int get_text_offset() const { return text_offset_; } | |
87 AXPositionKind get_kind() const { return kind_; } | |
88 ui::AXTextAffinity get_affinity() const { return affinity_; } | |
89 void set_affinity(ui::AXTextAffinity affinity) { affinity_ = affinity; } | |
90 | |
91 bool IsNullPosition() const { | |
92 return kind_ == AXPositionKind::NullPosition || !GetAnchor(); | |
93 } | |
94 bool IsTreePosition() const { | |
95 return GetAnchor() && kind_ == AXPositionKind::TreePosition; | |
96 } | |
97 bool IsTextPosition() const { | |
98 return GetAnchor() && kind_ == AXPositionKind::TextPosition; | |
99 } | |
100 | |
101 bool AtStartOfAnchor() const { | |
102 if (!GetAnchor()) | |
103 return false; | |
104 | |
105 switch (kind_) { | |
106 case AXPositionKind::NullPosition: | |
107 return false; | |
108 case AXPositionKind::TreePosition: | |
109 return child_index_ == 0; | |
110 case AXPositionKind::TextPosition: | |
111 return text_offset_ == 0; | |
112 } | |
113 | |
114 return false; | |
115 } | |
116 | |
117 bool AtEndOfAnchor() const { | |
118 if (!GetAnchor()) | |
119 return false; | |
120 | |
121 switch (kind_) { | |
122 case AXPositionKind::NullPosition: | |
123 return false; | |
124 case AXPositionKind::TreePosition: | |
125 return child_index_ == AnchorChildCount(); | |
126 case AXPositionKind::TextPosition: | |
127 return text_offset_ == MaxTextOffset(); | |
128 } | |
129 | |
130 return false; | |
131 } | |
132 | |
133 AXPosition<AXPositionType, AXNodeType>* CommonAncestor( | |
134 const AXPosition<AXPositionType, AXNodeType>& second) const { | |
135 std::queue<const AXPosition<AXPositionType, AXNodeType>*> ancestors1; | |
136 ancestors1.push(this); | |
137 while (!ancestors1.back() || !ancestors1.back()->IsNullPosition()) | |
138 ancestors1.push(ancestors1.back()->GetParentPosition()); | |
139 ancestors1.pop(); | |
140 if (ancestors1.empty()) | |
141 return CreateNullPosition(); | |
142 | |
143 std::queue<const AXPosition<AXPositionType, AXNodeType>*> ancestors2; | |
144 ancestors2.push(&second); | |
145 while (!ancestors2.back() || !ancestors2.back()->IsNullPosition()) | |
146 ancestors2.push(ancestors2.back()->GetParentPosition()); | |
147 ancestors2.pop(); | |
148 if (ancestors2.empty()) | |
149 return CreateNullPosition(); | |
150 | |
151 const AXPosition<AXPositionType, AXNodeType>* commonAncestor = | |
152 CreateNullPosition(); | |
153 do { | |
154 if (*ancestors1.front() == *ancestors2.front()) { | |
155 commonAncestor = ancestors1.front(); | |
156 ancestors1.pop(); | |
157 ancestors2.pop(); | |
158 } else { | |
159 break; | |
160 } | |
161 } while (!ancestors1.empty() && !ancestors2.empty()); | |
162 return const_cast<AXPosition<AXPositionType, AXNodeType>*>(commonAncestor); | |
163 } | |
164 | |
165 bool operator<(const AXPosition<AXPositionType, AXNodeType>& position) const { | |
166 if (IsNullPosition() || position.IsNullPosition()) | |
167 return false; | |
168 | |
169 const AXPosition<AXPositionType, AXNodeType>* other = &position; | |
170 do { | |
171 other = other->GetPreviousAnchorPosition(); | |
172 if (!other || other->IsNullPosition()) | |
173 return false; | |
174 } while (*this != *other); | |
175 return true; | |
176 } | |
177 | |
178 bool operator<=( | |
179 const AXPosition<AXPositionType, AXNodeType>& position) const { | |
180 if (IsNullPosition() || position.IsNullPosition()) | |
181 return false; | |
182 return *this == position || *this < position; | |
183 } | |
184 | |
185 bool operator>(const AXPosition<AXPositionType, AXNodeType>& position) const { | |
186 if (IsNullPosition() || position.IsNullPosition()) | |
187 return false; | |
188 | |
189 const AXPosition<AXPositionType, AXNodeType>* other = &position; | |
190 do { | |
191 other = other->GetNextAnchorPosition(); | |
192 if (!other || other->IsNullPosition()) | |
193 return false; | |
194 } while (*this != *other); | |
195 return true; | |
196 } | |
197 | |
198 bool operator>=( | |
199 const AXPosition<AXPositionType, AXNodeType>& position) const { | |
200 if (IsNullPosition() || position.IsNullPosition()) | |
201 return false; | |
202 return *this == position || *this > position; | |
203 } | |
204 | |
205 AXPosition<AXPositionType, AXNodeType>* GetPositionAtStartOfAnchor() const { | |
206 switch (kind_) { | |
207 case AXPositionKind::NullPosition: | |
208 return CreateNullPosition(); | |
209 case AXPositionKind::TreePosition: | |
210 return CreateTreePosition(tree_id_, anchor_id_, 0 /* child_index */); | |
211 case AXPositionKind::TextPosition: | |
212 return CreateTextPosition(tree_id_, anchor_id_, 0 /* text_offset */); | |
213 } | |
214 return CreateNullPosition(); | |
215 } | |
216 | |
217 AXPosition<AXPositionType, AXNodeType>* GetPositionAtEndOfAnchor() const { | |
218 switch (kind_) { | |
219 case AXPositionKind::NullPosition: | |
220 return CreateNullPosition(); | |
221 case AXPositionKind::TreePosition: | |
222 return CreateTreePosition(tree_id_, anchor_id_, AnchorChildCount()); | |
223 case AXPositionKind::TextPosition: | |
224 return CreateTextPosition(tree_id_, anchor_id_, MaxTextOffset()); | |
225 } | |
226 return CreateNullPosition(); | |
227 } | |
228 | |
229 // The following methods work across anchors. | |
230 | |
231 // TODO(nektar): Not yet implemented for tree positions. | |
dmazzoni
2016/10/12 20:45:41
As you walk the tree won't you go between tree pos
| |
232 AXPosition<AXPositionType, AXNodeType>* GetNextCharacterPosition() const { | |
233 if (IsNullPosition()) | |
234 return CreateNullPosition(); | |
235 | |
236 if (text_offset_ + 1 < MaxTextOffset()) | |
237 return CreateTextPosition(tree_id_, anchor_id_, text_offset_ + 1); | |
238 | |
239 AXPosition<AXPositionType, AXNodeType>* next_leaf = GetNextAnchorPosition(); | |
240 while (next_leaf && next_leaf->AnchorChildCount()) | |
241 next_leaf = next_leaf->GetNextAnchorPosition(); | |
242 return next_leaf; | |
243 } | |
244 | |
245 // TODO(nektar): Not yet implemented for tree positions. | |
246 AXPosition<AXPositionType, AXNodeType>* GetPreviousCharacterPosition() const { | |
247 if (IsNullPosition()) | |
248 return CreateNullPosition(); | |
249 | |
250 if (text_offset_ > 0) | |
251 return CreateTextPosition(tree_id_, anchor_id_, text_offset_ - 1); | |
252 | |
253 AXPosition<AXPositionType, AXNodeType>* previous_leaf = | |
254 GetPreviousAnchorPosition(); | |
255 while (previous_leaf && previous_leaf->AnchorChildCount()) | |
256 previous_leaf = previous_leaf->GetNextAnchorPosition(); | |
257 return previous_leaf; | |
258 } | |
259 | |
260 // TODO(nektar): Add word, line and paragraph navigation methods. | |
261 | |
262 protected: | |
263 virtual void Initialize(int tree_id, | |
264 int32_t anchor_id, | |
265 int child_index, | |
266 int text_offset, | |
267 AXPositionKind kind) { | |
268 if (GetAnchor() && child_index >= 0 && child_index <= AnchorChildCount() && | |
269 text_offset >= 0 && text_offset <= MaxTextOffset()) { | |
270 tree_id_ = tree_id; | |
271 anchor_id_ = anchor_id; | |
272 child_index_ = child_index; | |
273 text_offset_ = text_offset; | |
274 kind_ = kind; | |
275 return; | |
276 } | |
277 | |
278 // Reset to the null position. | |
279 tree_id_ = -1; | |
280 anchor_id_ = -1; | |
281 child_index_ = -1; | |
282 text_offset_ = -1; | |
283 kind_ = AXPositionKind::NullPosition; | |
284 } | |
285 | |
286 // Uses depth-first pre-order traversal. | |
287 virtual AXPosition<AXPositionType, AXNodeType>* GetNextAnchorPosition() | |
288 const { | |
289 if (IsNullPosition()) | |
290 return CreateNullPosition(); | |
291 | |
292 if (AnchorChildCount()) | |
293 return GetChildPositionAt(0); | |
294 | |
295 const AXPosition<AXPositionType, AXNodeType>* current_position = this; | |
296 const AXPosition<AXPositionType, AXNodeType>* parent_position = | |
297 GetParentPosition(); | |
298 while (parent_position && !parent_position->IsNullPosition()) { | |
299 // Get the next sibling if it exists, otherwise move up to the parent's | |
300 // next sibling. | |
301 int index_in_parent = current_position->AnchorIndexInParent(); | |
302 if (index_in_parent < parent_position->AnchorChildCount() - 1) { | |
303 AXPosition<AXPositionType, AXNodeType>* next_sibling = | |
304 parent_position->GetChildPositionAt(index_in_parent + 1); | |
305 DCHECK(next_sibling && !next_sibling->IsNullPosition()); | |
306 return next_sibling; | |
307 } | |
308 | |
309 current_position = parent_position; | |
310 parent_position = current_position->GetParentPosition(); | |
311 } | |
312 | |
313 return CreateNullPosition(); | |
314 } | |
315 | |
316 // Uses depth-first pre-order traversal. | |
317 virtual AXPosition<AXPositionType, AXNodeType>* GetPreviousAnchorPosition() | |
dmazzoni
2016/10/12 20:45:41
Shouldn't this return the end of the previous anch
| |
318 const { | |
319 if (IsNullPosition()) | |
320 return CreateNullPosition(); | |
321 | |
322 AXPosition<AXPositionType, AXNodeType>* parent_position = | |
323 GetParentPosition(); | |
324 if (!parent_position || parent_position->IsNullPosition()) | |
325 return CreateNullPosition(); | |
326 | |
327 // Get the previous sibling's deepest first child if a previous sibling | |
328 // exists, otherwise move up to the parent. | |
329 int index_in_parent = AnchorIndexInParent(); | |
330 if (index_in_parent <= 0) | |
331 return parent_position; | |
332 | |
333 AXPosition<AXPositionType, AXNodeType>* leaf = | |
334 parent_position->GetChildPositionAt(index_in_parent - 1); | |
335 while (leaf && !leaf->IsNullPosition() && leaf->AnchorChildCount()) | |
336 leaf->GetChildPositionAt(0); | |
dmazzoni
2016/10/12 20:45:41
It doesn't look like you're doing anything with th
| |
337 | |
338 return leaf; | |
339 } | |
340 | |
341 // Abstract methods. | |
342 virtual AXPosition<AXPositionType, AXNodeType>* GetChildPositionAt( | |
dmazzoni
2016/10/12 20:45:41
As I said in a previous comment, I think it would
| |
343 int child_index) const = 0; | |
344 virtual AXPosition<AXPositionType, AXNodeType>* GetParentPosition() const = 0; | |
dmazzoni
2016/10/12 20:45:41
Same, this should return an AXNodeType.
| |
345 virtual int AnchorChildCount() const = 0; | |
346 virtual int AnchorIndexInParent() const = 0; | |
347 virtual AXNodeType* GetNodeInTree(int tree_id, int32_t node_id) const = 0; | |
348 // Returns the length of the text that is present inside the anchor node, | |
349 // including any text found on descendant nodes. | |
350 virtual int MaxTextOffset() const = 0; | |
351 | |
352 private: | |
353 int tree_id_; | |
354 int32_t anchor_id_; | |
355 | |
356 // For text positions, |child_index_| is initially set to |-1| and only | |
357 // computed on demand. The same with tree positions and |text_offset_|. | |
358 int child_index_; | |
359 int text_offset_; | |
360 | |
361 AXPositionKind kind_; | |
362 | |
363 // TODO(nektar): Get rid of affinity and make Blink handle affinity | |
364 // internally since inline text objects don't span lines. | |
365 ui::AXTextAffinity affinity_; | |
366 }; | |
367 | |
368 template <class AXPositionType, class AXNodeType> | |
369 bool operator==(const AXPosition<AXPositionType, AXNodeType>& first, | |
370 const AXPosition<AXPositionType, AXNodeType>& second) { | |
371 if (first.IsNullPosition() && second.IsNullPosition()) | |
372 return true; | |
373 return first == second; | |
374 } | |
375 | |
376 template <class AXPositionType, class AXNodeType> | |
377 bool operator!=(const AXPosition<AXPositionType, AXNodeType>& first, | |
378 const AXPosition<AXPositionType, AXNodeType>& second) { | |
379 return !operator==(first, second); | |
380 } | |
381 | |
382 } // namespace ui | |
383 | |
384 #endif // UI_ACCESSIBILITY_AX_POSITION_H_ | |
OLD | NEW |