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 <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_ |
OLD | NEW |