Chromium Code Reviews| Index: ui/accessibility/ax_position.h |
| diff --git a/ui/accessibility/ax_position.h b/ui/accessibility/ax_position.h |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..8abcd27e44e509e2d9fc10d64ecf6d4d5fe4587b |
| --- /dev/null |
| +++ b/ui/accessibility/ax_position.h |
| @@ -0,0 +1,384 @@ |
| +// Copyright 2016 The Chromium Authors. All rights reserved. |
| +// Use of this source code is governed by a BSD-style license that can be |
| +// found in the LICENSE file. |
| + |
| +#ifndef UI_ACCESSIBILITY_AX_POSITION_H_ |
| +#define UI_ACCESSIBILITY_AX_POSITION_H_ |
| + |
| +#include <stdint.h> |
| + |
| +#include <queue> |
| + |
| +#include "ui/accessibility/ax_enums.h" |
| +#include "ui/accessibility/ax_export.h" |
| + |
| +namespace ui { |
| + |
| +// Defines the type of position in the accessibility tree. |
| +// A tree position is used when referring to a specific child of a node in the |
| +// accessibility tree. |
| +// A text position is used when referring to a specific character of text inside |
| +// a particular node. |
| +// A null position is used to signify that the provided data is invalid or that |
| +// a boundary has been reached. |
| +enum class AXPositionKind { NullPosition, TreePosition, TextPosition }; |
| + |
| +// A position in the |AXTree|. |
| +// It could either indicate a non-textual node in the accessibility tree, or a |
| +// text node and a character offset. |
| +// A text node has either a role of static_text, inline_text_box or line_break. |
| +// |
| +// This class template uses static polymorphism in order to allow sub-classes to |
| +// be created from the base class without the base class knowing the type of the |
| +// sub-class in advance. |
| +// The template argument |AXPositionType| should always be set to the type of |
| +// any class that inherits from this template, making this a |
| +// "curiously recursive template". |
| +template <class AXPositionType, class AXNodeType> |
| +class AXPosition { |
| + public: |
| + AXPosition() {} |
| + virtual ~AXPosition() {} |
| + |
| + static AXPosition<AXPositionType, AXNodeType>* CreateNullPosition() { |
| + auto new_position = static_cast<AXPosition<AXPositionType, AXNodeType>*>( |
| + new AXPositionType()); |
| + DCHECK(new_position); |
| + new_position->Initialize(-1 /* tree_id */, -1 /* anchor_id */, |
| + -1 /* child_index */, -1 /* text_offset */, |
| + AXPositionKind::NullPosition); |
| + return new_position; |
| + } |
| + |
| + static AXPosition<AXPositionType, AXNodeType>* |
| + CreateTreePosition(int tree_id, int32_t anchor_id, int child_index) { |
| + auto new_position = static_cast<AXPosition<AXPositionType, AXNodeType>*>( |
| + new AXPositionType()); |
| + DCHECK(new_position); |
| + new_position->Initialize(tree_id, anchor_id, child_index, |
| + -1 /* text_offset */, |
| + AXPositionKind::TreePosition); |
| + return new_position; |
| + } |
| + |
| + static AXPosition<AXPositionType, AXNodeType>* |
| + CreateTextPosition(int tree_id, int32_t anchor_id, int text_offset) { |
| + auto new_position = static_cast<AXPosition<AXPositionType, AXNodeType>*>( |
| + new AXPositionType()); |
| + DCHECK(new_position); |
| + new_position->Initialize(tree_id, anchor_id, -1 /* child_index */, |
| + text_offset, AXPositionKind::TextPosition); |
| + return new_position; |
| + } |
| + |
| + int get_tree_id() const { return tree_id_; } |
| + int32_t get_anchor_id() const { return anchor_id_; } |
| + |
| + AXNodeType* GetAnchor() const { |
| + if (tree_id_ == -1 || anchor_id_ == -1) |
| + return nullptr; |
| + DCHECK_GE(tree_id_, 0); |
| + DCHECK_GE(anchor_id_, 0); |
| + return GetNodeInTree(tree_id_, anchor_id_); |
| + } |
| + |
| + int get_child_index() const { return child_index_; } |
| + int get_text_offset() const { return text_offset_; } |
| + AXPositionKind get_kind() const { return kind_; } |
| + ui::AXTextAffinity get_affinity() const { return affinity_; } |
| + void set_affinity(ui::AXTextAffinity affinity) { affinity_ = affinity; } |
| + |
| + bool IsNullPosition() const { |
| + return kind_ == AXPositionKind::NullPosition || !GetAnchor(); |
| + } |
| + bool IsTreePosition() const { |
| + return GetAnchor() && kind_ == AXPositionKind::TreePosition; |
| + } |
| + bool IsTextPosition() const { |
| + return GetAnchor() && kind_ == AXPositionKind::TextPosition; |
| + } |
| + |
| + bool AtStartOfAnchor() const { |
| + if (!GetAnchor()) |
| + return false; |
| + |
| + switch (kind_) { |
| + case AXPositionKind::NullPosition: |
| + return false; |
| + case AXPositionKind::TreePosition: |
| + return child_index_ == 0; |
| + case AXPositionKind::TextPosition: |
| + return text_offset_ == 0; |
| + } |
| + |
| + return false; |
| + } |
| + |
| + bool AtEndOfAnchor() const { |
| + if (!GetAnchor()) |
| + return false; |
| + |
| + switch (kind_) { |
| + case AXPositionKind::NullPosition: |
| + return false; |
| + case AXPositionKind::TreePosition: |
| + return child_index_ == AnchorChildCount(); |
| + case AXPositionKind::TextPosition: |
| + return text_offset_ == MaxTextOffset(); |
| + } |
| + |
| + return false; |
| + } |
| + |
| + AXPosition<AXPositionType, AXNodeType>* CommonAncestor( |
| + const AXPosition<AXPositionType, AXNodeType>& second) const { |
| + std::queue<const AXPosition<AXPositionType, AXNodeType>*> ancestors1; |
| + ancestors1.push(this); |
| + while (!ancestors1.back() || !ancestors1.back()->IsNullPosition()) |
| + ancestors1.push(ancestors1.back()->GetParentPosition()); |
| + ancestors1.pop(); |
| + if (ancestors1.empty()) |
| + return CreateNullPosition(); |
| + |
| + std::queue<const AXPosition<AXPositionType, AXNodeType>*> ancestors2; |
| + ancestors2.push(&second); |
| + while (!ancestors2.back() || !ancestors2.back()->IsNullPosition()) |
| + ancestors2.push(ancestors2.back()->GetParentPosition()); |
| + ancestors2.pop(); |
| + if (ancestors2.empty()) |
| + return CreateNullPosition(); |
| + |
| + const AXPosition<AXPositionType, AXNodeType>* commonAncestor = |
| + CreateNullPosition(); |
| + do { |
| + if (*ancestors1.front() == *ancestors2.front()) { |
| + commonAncestor = ancestors1.front(); |
| + ancestors1.pop(); |
| + ancestors2.pop(); |
| + } else { |
| + break; |
| + } |
| + } while (!ancestors1.empty() && !ancestors2.empty()); |
| + return const_cast<AXPosition<AXPositionType, AXNodeType>*>(commonAncestor); |
| + } |
| + |
| + bool operator<(const AXPosition<AXPositionType, AXNodeType>& position) const { |
| + if (IsNullPosition() || position.IsNullPosition()) |
| + return false; |
| + |
| + const AXPosition<AXPositionType, AXNodeType>* other = &position; |
| + do { |
| + other = other->GetPreviousAnchorPosition(); |
| + if (!other || other->IsNullPosition()) |
| + return false; |
| + } while (*this != *other); |
| + return true; |
| + } |
| + |
| + bool operator<=( |
| + const AXPosition<AXPositionType, AXNodeType>& position) const { |
| + if (IsNullPosition() || position.IsNullPosition()) |
| + return false; |
| + return *this == position || *this < position; |
| + } |
| + |
| + bool operator>(const AXPosition<AXPositionType, AXNodeType>& position) const { |
| + if (IsNullPosition() || position.IsNullPosition()) |
| + return false; |
| + |
| + const AXPosition<AXPositionType, AXNodeType>* other = &position; |
| + do { |
| + other = other->GetNextAnchorPosition(); |
| + if (!other || other->IsNullPosition()) |
| + return false; |
| + } while (*this != *other); |
| + return true; |
| + } |
| + |
| + bool operator>=( |
| + const AXPosition<AXPositionType, AXNodeType>& position) const { |
| + if (IsNullPosition() || position.IsNullPosition()) |
| + return false; |
| + return *this == position || *this > position; |
| + } |
| + |
| + AXPosition<AXPositionType, AXNodeType>* GetPositionAtStartOfAnchor() const { |
| + switch (kind_) { |
| + case AXPositionKind::NullPosition: |
| + return CreateNullPosition(); |
| + case AXPositionKind::TreePosition: |
| + return CreateTreePosition(tree_id_, anchor_id_, 0 /* child_index */); |
| + case AXPositionKind::TextPosition: |
| + return CreateTextPosition(tree_id_, anchor_id_, 0 /* text_offset */); |
| + } |
| + return CreateNullPosition(); |
| + } |
| + |
| + AXPosition<AXPositionType, AXNodeType>* GetPositionAtEndOfAnchor() const { |
| + switch (kind_) { |
| + case AXPositionKind::NullPosition: |
| + return CreateNullPosition(); |
| + case AXPositionKind::TreePosition: |
| + return CreateTreePosition(tree_id_, anchor_id_, AnchorChildCount()); |
| + case AXPositionKind::TextPosition: |
| + return CreateTextPosition(tree_id_, anchor_id_, MaxTextOffset()); |
| + } |
| + return CreateNullPosition(); |
| + } |
| + |
| + // The following methods work across anchors. |
| + |
| + // 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
|
| + AXPosition<AXPositionType, AXNodeType>* GetNextCharacterPosition() const { |
| + if (IsNullPosition()) |
| + return CreateNullPosition(); |
| + |
| + if (text_offset_ + 1 < MaxTextOffset()) |
| + return CreateTextPosition(tree_id_, anchor_id_, text_offset_ + 1); |
| + |
| + AXPosition<AXPositionType, AXNodeType>* next_leaf = GetNextAnchorPosition(); |
| + while (next_leaf && next_leaf->AnchorChildCount()) |
| + next_leaf = next_leaf->GetNextAnchorPosition(); |
| + return next_leaf; |
| + } |
| + |
| + // TODO(nektar): Not yet implemented for tree positions. |
| + AXPosition<AXPositionType, AXNodeType>* GetPreviousCharacterPosition() const { |
| + if (IsNullPosition()) |
| + return CreateNullPosition(); |
| + |
| + if (text_offset_ > 0) |
| + return CreateTextPosition(tree_id_, anchor_id_, text_offset_ - 1); |
| + |
| + AXPosition<AXPositionType, AXNodeType>* previous_leaf = |
| + GetPreviousAnchorPosition(); |
| + while (previous_leaf && previous_leaf->AnchorChildCount()) |
| + previous_leaf = previous_leaf->GetNextAnchorPosition(); |
| + return previous_leaf; |
| + } |
| + |
| + // TODO(nektar): Add word, line and paragraph navigation methods. |
| + |
| + protected: |
| + virtual void Initialize(int tree_id, |
| + int32_t anchor_id, |
| + int child_index, |
| + int text_offset, |
| + AXPositionKind kind) { |
| + if (GetAnchor() && child_index >= 0 && child_index <= AnchorChildCount() && |
| + text_offset >= 0 && text_offset <= MaxTextOffset()) { |
| + tree_id_ = tree_id; |
| + anchor_id_ = anchor_id; |
| + child_index_ = child_index; |
| + text_offset_ = text_offset; |
| + kind_ = kind; |
| + return; |
| + } |
| + |
| + // Reset to the null position. |
| + tree_id_ = -1; |
| + anchor_id_ = -1; |
| + child_index_ = -1; |
| + text_offset_ = -1; |
| + kind_ = AXPositionKind::NullPosition; |
| + } |
| + |
| + // Uses depth-first pre-order traversal. |
| + virtual AXPosition<AXPositionType, AXNodeType>* GetNextAnchorPosition() |
| + const { |
| + if (IsNullPosition()) |
| + return CreateNullPosition(); |
| + |
| + if (AnchorChildCount()) |
| + return GetChildPositionAt(0); |
| + |
| + const AXPosition<AXPositionType, AXNodeType>* current_position = this; |
| + const AXPosition<AXPositionType, AXNodeType>* parent_position = |
| + GetParentPosition(); |
| + while (parent_position && !parent_position->IsNullPosition()) { |
| + // Get the next sibling if it exists, otherwise move up to the parent's |
| + // next sibling. |
| + int index_in_parent = current_position->AnchorIndexInParent(); |
| + if (index_in_parent < parent_position->AnchorChildCount() - 1) { |
| + AXPosition<AXPositionType, AXNodeType>* next_sibling = |
| + parent_position->GetChildPositionAt(index_in_parent + 1); |
| + DCHECK(next_sibling && !next_sibling->IsNullPosition()); |
| + return next_sibling; |
| + } |
| + |
| + current_position = parent_position; |
| + parent_position = current_position->GetParentPosition(); |
| + } |
| + |
| + return CreateNullPosition(); |
| + } |
| + |
| + // Uses depth-first pre-order traversal. |
| + virtual AXPosition<AXPositionType, AXNodeType>* GetPreviousAnchorPosition() |
|
dmazzoni
2016/10/12 20:45:41
Shouldn't this return the end of the previous anch
|
| + const { |
| + if (IsNullPosition()) |
| + return CreateNullPosition(); |
| + |
| + AXPosition<AXPositionType, AXNodeType>* parent_position = |
| + GetParentPosition(); |
| + if (!parent_position || parent_position->IsNullPosition()) |
| + return CreateNullPosition(); |
| + |
| + // Get the previous sibling's deepest first child if a previous sibling |
| + // exists, otherwise move up to the parent. |
| + int index_in_parent = AnchorIndexInParent(); |
| + if (index_in_parent <= 0) |
| + return parent_position; |
| + |
| + AXPosition<AXPositionType, AXNodeType>* leaf = |
| + parent_position->GetChildPositionAt(index_in_parent - 1); |
| + while (leaf && !leaf->IsNullPosition() && leaf->AnchorChildCount()) |
| + leaf->GetChildPositionAt(0); |
|
dmazzoni
2016/10/12 20:45:41
It doesn't look like you're doing anything with th
|
| + |
| + return leaf; |
| + } |
| + |
| + // Abstract methods. |
| + virtual AXPosition<AXPositionType, AXNodeType>* GetChildPositionAt( |
|
dmazzoni
2016/10/12 20:45:41
As I said in a previous comment, I think it would
|
| + int child_index) const = 0; |
| + virtual AXPosition<AXPositionType, AXNodeType>* GetParentPosition() const = 0; |
|
dmazzoni
2016/10/12 20:45:41
Same, this should return an AXNodeType.
|
| + virtual int AnchorChildCount() const = 0; |
| + virtual int AnchorIndexInParent() const = 0; |
| + virtual AXNodeType* GetNodeInTree(int tree_id, int32_t node_id) const = 0; |
| + // Returns the length of the text that is present inside the anchor node, |
| + // including any text found on descendant nodes. |
| + virtual int MaxTextOffset() const = 0; |
| + |
| + private: |
| + int tree_id_; |
| + int32_t anchor_id_; |
| + |
| + // For text positions, |child_index_| is initially set to |-1| and only |
| + // computed on demand. The same with tree positions and |text_offset_|. |
| + int child_index_; |
| + int text_offset_; |
| + |
| + AXPositionKind kind_; |
| + |
| + // TODO(nektar): Get rid of affinity and make Blink handle affinity |
| + // internally since inline text objects don't span lines. |
| + ui::AXTextAffinity affinity_; |
| +}; |
| + |
| +template <class AXPositionType, class AXNodeType> |
| +bool operator==(const AXPosition<AXPositionType, AXNodeType>& first, |
| + const AXPosition<AXPositionType, AXNodeType>& second) { |
| + if (first.IsNullPosition() && second.IsNullPosition()) |
| + return true; |
| + return first == second; |
| +} |
| + |
| +template <class AXPositionType, class AXNodeType> |
| +bool operator!=(const AXPosition<AXPositionType, AXNodeType>& first, |
| + const AXPosition<AXPositionType, AXNodeType>& second) { |
| + return !operator==(first, second); |
| +} |
| + |
| +} // namespace ui |
| + |
| +#endif // UI_ACCESSIBILITY_AX_POSITION_H_ |