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