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

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: Removed AXRange class. 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
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 int32_t child_id = AnchorChildID(child_index);
173 DCHECK_NE(child_id, INVALID_ANCHOR_ID);
174 switch (kind_) {
175 case AXPositionKind::NullPosition:
176 NOTREACHED();
177 return CreateNullPosition();
178 case AXPositionKind::TreePosition:
179 return CreateTreePosition(tree_id_, child_id, 0 /* child_index */);
180 case AXPositionKind::TextPosition:
181 return CreateTextPosition(tree_id_, child_id, 0 /* text_offset */,
182 AX_TEXT_AFFINITY_UPSTREAM);
183 }
184
185 return CreateNullPosition();
186 }
187
188 AXPosition<AXPositionType, AXNodeType>* CreateParentPosition() const {
189 if (IsNullPosition())
190 return CreateNullPosition();
191
192 int32_t parent_id = AnchorParentID();
193 if (parent_id == INVALID_ANCHOR_ID)
194 return CreateNullPosition();
195
196 DCHECK_GE(parent_id, 0);
197 switch (kind_) {
198 case AXPositionKind::NullPosition:
199 NOTREACHED();
200 return CreateNullPosition();
201 case AXPositionKind::TreePosition:
202 return CreateTreePosition(tree_id_, parent_id, 0 /* child_index */);
203 case AXPositionKind::TextPosition:
204 return CreateTextPosition(tree_id_, parent_id, 0 /* text_offset */,
205 AX_TEXT_AFFINITY_UPSTREAM);
206 }
207
208 return CreateNullPosition();
209 }
210
211 // The following methods work across anchors.
212
213 // TODO(nektar): Not yet implemented for tree positions.
214 AXPosition<AXPositionType, AXNodeType>* CreateNextCharacterPosition() const {
215 if (IsNullPosition())
216 return CreateNullPosition();
217
218 if (text_offset_ + 1 < MaxTextOffset()) {
219 return CreateTextPosition(tree_id_, anchor_id_, text_offset_ + 1,
220 AX_TEXT_AFFINITY_UPSTREAM);
221 }
222
223 std::unique_ptr<AXPosition<AXPositionType, AXNodeType>> next_leaf(
224 CreateNextAnchorPosition());
225 while (next_leaf && !next_leaf->IsNullPosition() &&
226 next_leaf->AnchorChildCount()) {
227 next_leaf.reset(next_leaf->CreateNextAnchorPosition());
228 }
229
230 DCHECK(next_leaf);
231 return next_leaf.release();
232 }
233
234 // TODO(nektar): Not yet implemented for tree positions.
235 AXPosition<AXPositionType, AXNodeType>* CreatePreviousCharacterPosition()
236 const {
237 if (IsNullPosition())
238 return CreateNullPosition();
239
240 if (text_offset_ > 0) {
241 return CreateTextPosition(tree_id_, anchor_id_, text_offset_ - 1,
242 AX_TEXT_AFFINITY_UPSTREAM);
243 }
244
245 std::unique_ptr<AXPosition<AXPositionType, AXNodeType>> previous_leaf(
246 CreatePreviousAnchorPosition());
247 while (previous_leaf && !previous_leaf->IsNullPosition() &&
248 previous_leaf->AnchorChildCount()) {
249 previous_leaf.reset(previous_leaf->CreatePreviousAnchorPosition());
250 }
251
252 DCHECK(previous_leaf);
253 previous_leaf.reset(previous_leaf->CreatePositionAtEndOfAnchor());
254 if (!previous_leaf->AtStartOfAnchor())
255 --previous_leaf->text_offset_;
256 return previous_leaf.release();
257 }
258
259 // TODO(nektar): Add word, line and paragraph navigation methods.
260
261 protected:
262 virtual void Initialize(AXPositionKind kind,
263 int tree_id,
264 int32_t anchor_id,
265 int child_index,
266 int text_offset,
267 AXTextAffinity affinity) {
268 kind_ = kind;
269 tree_id_ = tree_id;
270 anchor_id_ = anchor_id;
271 child_index_ = child_index;
272 text_offset_ = text_offset;
273 affinity_ = affinity;
274
275 if (!GetAnchor() ||
276 (child_index_ != INVALID_INDEX &&
277 (child_index_ < 0 || child_index_ > AnchorChildCount())) ||
278 (text_offset_ != INVALID_OFFSET &&
279 (text_offset_ < 0 || text_offset_ > MaxTextOffset()))) {
280 // reset to the null position.
281 kind_ = AXPositionKind::NullPosition;
282 tree_id_ = INVALID_TREE_ID;
283 anchor_id_ = INVALID_ANCHOR_ID;
284 child_index_ = INVALID_INDEX;
285 text_offset_ = INVALID_OFFSET;
286 affinity_ = AX_TEXT_AFFINITY_UPSTREAM;
287 }
288 }
289
290 // Uses depth-first pre-order traversal.
291 virtual AXPosition<AXPositionType, AXNodeType>* CreateNextAnchorPosition()
292 const {
293 if (IsNullPosition())
294 return CreateNullPosition();
295
296 if (AnchorChildCount())
297 return CreateChildPositionAt(0);
298
299 std::unique_ptr<AXPosition<AXPositionType, AXNodeType>> current_position(
300 CreateTreePosition(tree_id_, anchor_id_, child_index_));
301 std::unique_ptr<AXPosition<AXPositionType, AXNodeType>> parent_position(
302 CreateParentPosition());
303 while (parent_position && !parent_position->IsNullPosition()) {
304 // Get the next sibling if it exists, otherwise move up to the parent's
305 // next sibling.
306 int index_in_parent = current_position->AnchorIndexInParent();
307 if (index_in_parent < parent_position->AnchorChildCount() - 1) {
308 AXPosition<AXPositionType, AXNodeType>* next_sibling =
309 parent_position->CreateChildPositionAt(index_in_parent + 1);
310 DCHECK(next_sibling && !next_sibling->IsNullPosition());
311 return next_sibling;
312 }
313
314 current_position = std::move(parent_position);
315 parent_position.reset(current_position->CreateParentPosition());
316 }
317
318 return CreateNullPosition();
319 }
320
321 // Uses depth-first pre-order traversal.
322 virtual AXPosition<AXPositionType, AXNodeType>* CreatePreviousAnchorPosition()
323 const {
324 if (IsNullPosition())
325 return CreateNullPosition();
326
327 std::unique_ptr<AXPosition<AXPositionType, AXNodeType>> parent_position(
328 CreateParentPosition());
329 if (!parent_position || parent_position->IsNullPosition())
330 return CreateNullPosition();
331
332 // Get the previous sibling's deepest first child if a previous sibling
333 // exists, otherwise move up to the parent.
334 int index_in_parent = AnchorIndexInParent();
335 if (index_in_parent <= 0)
336 return parent_position.release();
337
338 std::unique_ptr<AXPosition<AXPositionType, AXNodeType>> leaf(
339 parent_position->CreateChildPositionAt(index_in_parent - 1));
340 while (leaf && !leaf->IsNullPosition() && leaf->AnchorChildCount())
341 leaf.reset(leaf->CreateChildPositionAt(0));
342
343 return leaf.release();
344 }
345
346 // Abstract methods.
347 virtual int32_t AnchorChildID(int child_index) const = 0;
348 virtual int AnchorChildCount() const = 0;
349 virtual int AnchorIndexInParent() const = 0;
350 virtual int32_t AnchorParentID() const = 0;
351 virtual AXNodeType* GetNodeInTree(int tree_id, int32_t node_id) const = 0;
352 // Returns the length of the text that is present inside the anchor node,
353 // including any text found on descendant nodes.
354 virtual int MaxTextOffset() const = 0;
355
356 private:
357 AXPositionKind kind_;
358 int tree_id_;
359 int32_t anchor_id_;
360
361 // For text positions, |child_index_| is initially set to |-1| and only
362 // computed on demand. The same with tree positions and |text_offset_|.
363 int child_index_;
364 int text_offset_;
365
366 // TODO(nektar): Get rid of affinity and make Blink handle affinity
367 // internally since inline text objects don't span lines.
368 ui::AXTextAffinity affinity_;
369 };
370
371 template <class AXPositionType, class AXNodeType>
372 int AXPosition<AXPositionType, AXNodeType>::INVALID_TREE_ID = -1;
373 template <class AXPositionType, class AXNodeType>
374 int32_t AXPosition<AXPositionType, AXNodeType>::INVALID_ANCHOR_ID = -1;
375 template <class AXPositionType, class AXNodeType>
376 int AXPosition<AXPositionType, AXNodeType>::INVALID_INDEX = -1;
377 template <class AXPositionType, class AXNodeType>
378 int AXPosition<AXPositionType, AXNodeType>::INVALID_OFFSET = -1;
379
380 } // namespace ui
381
382 #endif // UI_ACCESSIBILITY_AX_POSITION_H_
OLDNEW
« ui/accessibility/BUILD.gn ('K') | « 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