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

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

Powered by Google App Engine
This is Rietveld 408576698