Chromium Code Reviews| 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 |