Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(60)

Side by Side Diff: ui/accessibility/ax_position.h

Issue 2271893002: Creates AXPosition to uniquely identify a position in the accessibility tree (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Addressed some of the comments by David and Dominic. Created 4 years, 2 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « ui/accessibility/ax_node_position_unittest.cc ('k') | ui/accessibility/ax_range.h » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(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.
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 }
276
277 // Reset to the null position.
278 tree_id_ = -1;
279 anchor_id_ = -1;
280 child_index_ = -1;
281 text_offset_ = -1;
282 kind_ = AXPositionKind::NullPosition;
283 }
284
285 // Uses depth-first pre-order traversal.
286 virtual AXPosition<AXPositionType, AXNodeType>* GetNextAnchorPosition()
287 const {
288 if (IsNullPosition())
289 return CreateNullPosition();
290
291 if (AnchorChildCount())
292 return GetChildPositionAt(0);
293
294 const AXPosition<AXPositionType, AXNodeType>* current_position = this;
295 const AXPosition<AXPositionType, AXNodeType>* parent_position =
296 GetParentPosition();
297 while (parent_position && !parent_position->IsNullPosition()) {
298 // Get the next sibling if it exists, otherwise move up to the parent's
299 // next sibling.
300 int index_in_parent = current_position->AnchorIndexInParent();
301 if (index_in_parent < parent_position->AnchorChildCount() - 1) {
302 AXPosition<AXPositionType, AXNodeType>* next_sibling =
303 parent_position->GetChildPositionAt(index_in_parent + 1);
304 DCHECK(next_sibling && !next_sibling->IsNullPosition());
305 return next_sibling;
306 }
307
308 current_position = parent_position;
309 parent_position = current_position->GetParentPosition();
310 }
311
312 return CreateNullPosition();
313 }
314
315 // Uses depth-first pre-order traversal.
316 virtual AXPosition<AXPositionType, AXNodeType>* GetPreviousAnchorPosition()
317 const {
318 if (IsNullPosition())
319 return CreateNullPosition();
320
321 AXPosition<AXPositionType, AXNodeType>* parent_position =
322 GetParentPosition();
323 if (!parent_position || parent_position->IsNullPosition())
324 return CreateNullPosition();
325
326 // Get the previous sibling's deepest first child if a previous sibling
327 // exists, otherwise move up to the parent.
328 int index_in_parent = AnchorIndexInParent();
329 if (index_in_parent <= 0)
330 return parent_position;
331
332 AXPosition<AXPositionType, AXNodeType>* leaf =
333 parent_position->GetChildPositionAt(index_in_parent - 1);
334 while (leaf && !leaf->IsNullPosition() && leaf->AnchorChildCount())
335 leaf->GetChildPositionAt(0);
336
337 return leaf;
338 }
339
340 // Abstract methods.
341 virtual AXPosition<AXPositionType, AXNodeType>* GetChildPositionAt(
dmazzoni 2016/10/10 20:05:15 Would it be simpler to have this return the AXNode
342 int child_index) const = 0;
343 virtual AXPosition<AXPositionType, AXNodeType>* GetParentPosition() const = 0;
344 virtual int AnchorChildCount() const = 0;
345 virtual int AnchorIndexInParent() const = 0;
346 virtual AXNodeType* GetNodeInTree(int tree_id, int32_t node_id) const = 0;
347 // Returns the length of the text that is present inside the anchor node,
348 // including any text found on descendant nodes.
349 virtual int MaxTextOffset() const = 0;
350
351 private:
352 int tree_id_;
353 int32_t anchor_id_;
354
355 // For text positions, |child_index_| is initially set to |-1| and only
356 // computed on demand. The same with tree positions and |text_offset_|.
357 int child_index_;
358 int text_offset_;
359
360 AXPositionKind kind_;
361
362 // TODO(nektar): Get rid of affinity and make Blink handle affinity
363 // internally since inline text objects don't span lines.
364 ui::AXTextAffinity affinity_;
365 };
366
367 template <class AXPositionType, class AXNodeType>
368 bool operator==(const AXPosition<AXPositionType, AXNodeType>& first,
369 const AXPosition<AXPositionType, AXNodeType>& second) {
370 if (first.IsNullPosition() && second.IsNullPosition())
371 return true;
372 return first == second;
373 }
374
375 template <class AXPositionType, class AXNodeType>
376 bool operator!=(const AXPosition<AXPositionType, AXNodeType>& first,
377 const AXPosition<AXPositionType, AXNodeType>& second) {
378 return !operator==(first, second);
379 }
380
381 } // namespace ui
382
383 #endif // UI_ACCESSIBILITY_AX_POSITION_H_
OLDNEW
« no previous file with comments | « ui/accessibility/ax_node_position_unittest.cc ('k') | ui/accessibility/ax_range.h » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698