Commit 31f461ba authored by Ethan Jimenez's avatar Ethan Jimenez Committed by Commit Bot

Fix AXRange::GetAnchors from stopping at empty anchors

1. In order to collect the anchors between the start and end positions
   of an `AXRange`, an incorrect condition is evaluating if the start of
   the current anchor is equal to its end position, expecting such case
   to only be present when we reach the end of the `AXRange`.

   Such assumption does not hold for anchors of objects invisible to
   the text representation (such as checkboxes, images, etc.), since the
   anchor is empty and its start and end positions are the same.

   Refactoring `AXRange::GetAnchors` to cover such case.

2. Introducing unit tests for `AXRange::GetAnchors`.

3. Adding unit test coverage in `AXPlatformNodeTextRangeProviderTest`
   for the issue in `GetBoundingRectangles` which led to this bug.

Bug: 928948
Change-Id: I73ef429b7fb5043cfb535ce05909ac243b31aef1
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1595545
Commit-Queue: Ethan Jimenez <ethavar@microsoft.com>
Reviewed-by: default avatarNektarios Paisios <nektar@chromium.org>
Cr-Commit-Position: refs/heads/master@{#659701}
parent d15ee17c
......@@ -22,12 +22,13 @@ namespace ui {
template <class AXPositionType>
class AXRange {
public:
using AXPositionInstance = std::unique_ptr<AXPositionType>;
AXRange()
: anchor_(AXPositionType::CreateNullPosition()),
focus_(AXPositionType::CreateNullPosition()) {}
AXRange(std::unique_ptr<AXPositionType> anchor,
std::unique_ptr<AXPositionType> focus) {
AXRange(AXPositionInstance anchor, AXPositionInstance focus) {
if (anchor) {
anchor_ = std::move(anchor);
} else {
......@@ -59,6 +60,15 @@ class AXRange {
return *this;
}
bool operator==(const AXRange& other) const {
if (IsNull())
return other.IsNull();
return !other.IsNull() && *anchor_ == *other.anchor() &&
*focus_ == *other.focus();
}
bool operator!=(const AXRange& other) const { return !(*this == other); }
virtual ~AXRange() {}
bool IsNull() const {
......@@ -76,18 +86,20 @@ class AXRange {
return focus_.get();
}
AXRange GetForwardRange() const {
DCHECK(!IsNull());
if (*focus_ < *anchor_)
return AXRange(focus_->Clone(), anchor_->Clone());
return AXRange(anchor_->Clone(), focus_->Clone());
}
base::string16 GetText() const {
if (IsNull() || *anchor_ == *focus_)
return base::string16();
std::unique_ptr<AXPositionType> start, end;
if (*anchor_ < *focus_) {
start = anchor_->AsLeafTextPosition();
end = focus_->AsLeafTextPosition();
} else {
start = focus_->AsLeafTextPosition();
end = anchor_->AsLeafTextPosition();
}
AXRange forward_range = GetForwardRange();
AXPositionInstance start = forward_range.anchor()->AsLeafTextPosition();
AXPositionInstance end = forward_range.focus()->AsLeafTextPosition();
int start_offset = start->text_offset();
DCHECK_GE(start_offset, 0);
......@@ -114,72 +126,46 @@ class AXRange {
return text.substr(0, text_length);
}
// Returns a vector of AXRanges that span from the start of an anchor
// to the end of an anchor, all of which are in between anchor_ and focus_
// endpoints of this range.
std::vector<AXRange<AXPositionType>> GetAnchors() {
DCHECK(*anchor_ <= *focus_);
// Returns a vector of all ranges (each of them spanning a single anchor)
// between the anchor_ and focus_ endpoints of this instance.
std::vector<AXRange<AXPositionType>> GetAnchors() const {
std::vector<AXRange<AXPositionType>> anchors;
auto range_start = anchor_->Clone();
auto range_end = focus_->Clone();
// Non-null degenerate ranges span no content, but they do have a single
// anchor.
auto current_anchor_start = range_start->AsLeafTextPosition();
if (!current_anchor_start->IsNullPosition() && *range_start == *range_end) {
anchors.emplace_back(AXRange(current_anchor_start->Clone(),
current_anchor_start->Clone()));
if (IsNull())
return anchors;
}
// If start and end are in the same anchor, use end instead of
// CreatePositionAtEndOfAnchor to ensure this doesn't return a range that
// spans past end.
auto current_anchor_end =
current_anchor_start->CreatePositionAtEndOfAnchor();
const auto end = range_end->AsLeafTextPosition();
if (*current_anchor_end > *end &&
end->GetAnchor() == current_anchor_start->GetAnchor())
current_anchor_end = end->Clone();
AXRange forward_range = GetForwardRange();
AXPositionInstance current_anchor_start =
forward_range.anchor()->AsLeafTextPosition();
AXPositionInstance range_end = forward_range.focus()->AsLeafTextPosition();
while (!current_anchor_start->IsNullPosition() &&
!current_anchor_end->IsNullPosition() &&
*current_anchor_start < *current_anchor_end) {
if (current_anchor_start->GetAnchor() ==
current_anchor_end->GetAnchor()) {
anchors.emplace_back(AXRange(current_anchor_start->Clone(),
current_anchor_end->Clone()));
*current_anchor_start <= *range_end) {
// When the current start reaches the same anchor as this AXRange's end,
// simply append this last anchor (trimmed at range_end) and exit.
if (current_anchor_start->GetAnchor() == range_end->GetAnchor()) {
anchors.emplace_back(current_anchor_start->Clone(), range_end->Clone());
return anchors;
}
if (*current_anchor_end >= *end)
break;
AXPositionInstance current_anchor_end =
current_anchor_start->CreatePositionAtEndOfAnchor();
DCHECK_EQ(current_anchor_start->GetAnchor(),
current_anchor_end->GetAnchor());
DCHECK_LE(*current_anchor_start, *current_anchor_end);
DCHECK_LE(*current_anchor_end, *range_end);
if (current_anchor_end->CreateNextTextAnchorPosition()
->IsNullPosition()) {
current_anchor_start = current_anchor_start->CreateNextAnchorPosition()
->AsLeafTextPosition();
} else {
anchors.emplace_back(current_anchor_start->Clone(),
current_anchor_end->Clone());
current_anchor_start =
current_anchor_end->CreateNextTextAnchorPosition();
current_anchor_end->CreateNextAnchorPosition()->AsLeafTextPosition();
}
current_anchor_end = current_anchor_start->CreatePositionAtEndOfAnchor();
// If CreatePositionAtEndOfAnchor goes past the end anchor, use the end
// anchor instead.
if (*current_anchor_end > *end &&
end->GetAnchor() == current_anchor_start->GetAnchor())
current_anchor_end = end->Clone();
}
return anchors;
}
// Appends rects in screen coordinates of all text nodes that span between
// anchor_ and focus_. Rects outside of the viewport are skipped.
std::vector<gfx::Rect> GetScreenRects() const {
DCHECK(*anchor_ <= *focus_);
DCHECK_LE(*anchor_, *focus_);
std::vector<gfx::Rect> rectangles;
auto current_line_start = anchor_->Clone();
auto range_end = focus_->Clone();
......@@ -226,8 +212,8 @@ class AXRange {
}
private:
std::unique_ptr<AXPositionType> anchor_;
std::unique_ptr<AXPositionType> focus_;
AXPositionInstance anchor_;
AXPositionInstance focus_;
};
} // namespace ui
......
......@@ -18,7 +18,18 @@
namespace ui {
using TestPositionType = std::unique_ptr<AXPosition<AXNodePosition, AXNode>>;
using TestPositionInstance =
std::unique_ptr<AXPosition<AXNodePosition, AXNode>>;
using TestPositionRange = AXRange<AXPosition<AXNodePosition, AXNode>>;
#define EXPECT_RANGE_VECTOR_EQ(actual_vector, expected_vector) \
{ \
EXPECT_EQ(expected_vector.size(), actual_vector.size()); \
size_t element_count = \
std::min(expected_vector.size(), actual_vector.size()); \
for (size_t i = 0; i < element_count; ++i) \
EXPECT_EQ(expected_vector[i], actual_vector[i]); \
}
namespace {
......@@ -169,22 +180,60 @@ void AXRangeTest::TearDown() {
} // namespace
TEST_F(AXRangeTest, AXRangeEqualityOperators) {
TestPositionInstance null_position = AXNodePosition::CreateNullPosition();
TestPositionInstance test_position1 = AXNodePosition::CreateTextPosition(
tree_.data().tree_id, button_.id, 0 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
TestPositionInstance test_position2 = AXNodePosition::CreateTextPosition(
tree_.data().tree_id, line_break_.id, 1 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
TestPositionInstance test_position3 = AXNodePosition::CreateTextPosition(
tree_.data().tree_id, inline_box2_.id, 0 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
// Invalid ranges (with at least one null endpoint).
TestPositionRange null_position_and_nullptr(null_position->Clone(), nullptr);
TestPositionRange nullptr_and_test_position(nullptr, test_position1->Clone());
TestPositionRange test_position_and_null_position(test_position2->Clone(),
null_position->Clone());
TestPositionRange test_positions_1_and_2(test_position1->Clone(),
test_position2->Clone());
TestPositionRange test_positions_2_and_1(test_position2->Clone(),
test_position1->Clone());
TestPositionRange test_positions_1_and_3(test_position1->Clone(),
test_position3->Clone());
TestPositionRange test_positions_2_and_3(test_position2->Clone(),
test_position3->Clone());
TestPositionRange test_positions_3_and_2(test_position3->Clone(),
test_position2->Clone());
EXPECT_EQ(null_position_and_nullptr, nullptr_and_test_position);
EXPECT_EQ(nullptr_and_test_position, test_position_and_null_position);
EXPECT_NE(null_position_and_nullptr, test_positions_2_and_1);
EXPECT_NE(test_positions_2_and_1, test_position_and_null_position);
EXPECT_EQ(test_positions_1_and_2, test_positions_1_and_2);
EXPECT_NE(test_positions_2_and_1, test_positions_1_and_2);
EXPECT_EQ(test_positions_3_and_2, test_positions_2_and_3);
EXPECT_NE(test_positions_1_and_2, test_positions_2_and_3);
EXPECT_EQ(test_positions_1_and_2, test_positions_1_and_3);
}
TEST_F(AXRangeTest, AXRangeGetTextWithWholeObjects) {
base::string16 all_text = BUTTON_TEXT.substr(0).append(TEXT_VALUE);
// Create a range starting from the button object and ending at the last
// character of the root, i.e. at the last character of the second line in the
// text field.
TestPositionType start = AXNodePosition::CreateTreePosition(
TestPositionInstance start = AXNodePosition::CreateTreePosition(
tree_.data().tree_id, root_.id, 0 /* child_index */);
TestPositionType end = AXNodePosition::CreateTextPosition(
TestPositionInstance end = AXNodePosition::CreateTextPosition(
tree_.data().tree_id, root_.id, all_text.length() /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_TRUE(end->IsTextPosition());
AXRange<AXPosition<AXNodePosition, AXNode>> forward_range(start->Clone(),
end->Clone());
TestPositionRange forward_range(start->Clone(), end->Clone());
EXPECT_EQ(all_text, forward_range.GetText());
AXRange<AXPosition<AXNodePosition, AXNode>> backward_range(std::move(end),
std::move(start));
TestPositionRange backward_range(std::move(end), std::move(start));
EXPECT_EQ(all_text, backward_range.GetText());
// Button
......@@ -196,11 +245,9 @@ TEST_F(AXRangeTest, AXRangeGetTextWithWholeObjects) {
tree_.data().tree_id, button_.id, BUTTON_TEXT.length() /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_TRUE(end->IsTextPosition());
AXRange<AXPosition<AXNodePosition, AXNode>> button_range(start->Clone(),
end->Clone());
TestPositionRange button_range(start->Clone(), end->Clone());
EXPECT_EQ(BUTTON_TEXT, button_range.GetText());
AXRange<AXPosition<AXNodePosition, AXNode>> button_range_backward(
std::move(end), std::move(start));
TestPositionRange button_range_backward(std::move(end), std::move(start));
EXPECT_EQ(BUTTON_TEXT, button_range_backward.GetText());
// text_field_
......@@ -213,11 +260,9 @@ TEST_F(AXRangeTest, AXRangeGetTextWithWholeObjects) {
ax::mojom::TextAffinity::kDownstream);
ASSERT_TRUE(start->IsTextPosition());
ASSERT_TRUE(end->IsTextPosition());
AXRange<AXPosition<AXNodePosition, AXNode>> text_field_range(start->Clone(),
end->Clone());
TestPositionRange text_field_range(start->Clone(), end->Clone());
EXPECT_EQ(TEXT_VALUE, text_field_range.GetText());
AXRange<AXPosition<AXNodePosition, AXNode>> text_field_range_backward(
std::move(end), std::move(start));
TestPositionRange text_field_range_backward(std::move(end), std::move(start));
EXPECT_EQ(TEXT_VALUE, text_field_range_backward.GetText());
// static_text1_
......@@ -229,11 +274,10 @@ TEST_F(AXRangeTest, AXRangeGetTextWithWholeObjects) {
tree_.data().tree_id, static_text1_.id, LINE_1.length() /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_TRUE(end->IsTextPosition());
AXRange<AXPosition<AXNodePosition, AXNode>> static_text1_range(start->Clone(),
end->Clone());
TestPositionRange static_text1_range(start->Clone(), end->Clone());
EXPECT_EQ(LINE_1, static_text1_range.GetText());
AXRange<AXPosition<AXNodePosition, AXNode>> static_text1_range_backward(
std::move(end), std::move(start));
TestPositionRange static_text1_range_backward(std::move(end),
std::move(start));
EXPECT_EQ(LINE_1, static_text1_range_backward.GetText());
// static_text2_
......@@ -245,11 +289,10 @@ TEST_F(AXRangeTest, AXRangeGetTextWithWholeObjects) {
tree_.data().tree_id, static_text2_.id, LINE_2.length() /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_TRUE(end->IsTextPosition());
AXRange<AXPosition<AXNodePosition, AXNode>> static_text2_range(start->Clone(),
end->Clone());
TestPositionRange static_text2_range(start->Clone(), end->Clone());
EXPECT_EQ(LINE_2, static_text2_range.GetText());
AXRange<AXPosition<AXNodePosition, AXNode>> static_text2_range_backward(
std::move(end), std::move(start));
TestPositionRange static_text2_range_backward(std::move(end),
std::move(start));
EXPECT_EQ(LINE_2, static_text2_range_backward.GetText());
// static_text1_ to static_text2_
......@@ -261,11 +304,10 @@ TEST_F(AXRangeTest, AXRangeGetTextWithWholeObjects) {
tree_.data().tree_id, static_text2_.id, LINE_2.length() /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_TRUE(end->IsTextPosition());
AXRange<AXPosition<AXNodePosition, AXNode>> static_text_range(start->Clone(),
end->Clone());
TestPositionRange static_text_range(start->Clone(), end->Clone());
EXPECT_EQ(TEXT_VALUE, static_text_range.GetText());
AXRange<AXPosition<AXNodePosition, AXNode>> static_text_range_backward(
std::move(end), std::move(start));
TestPositionRange static_text_range_backward(std::move(end),
std::move(start));
EXPECT_EQ(TEXT_VALUE, static_text_range_backward.GetText());
// root_ to static_text2_'s end
......@@ -275,11 +317,10 @@ TEST_F(AXRangeTest, AXRangeGetTextWithWholeObjects) {
tree_.data().tree_id, static_text2_.id, LINE_2.length() /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_TRUE(end->IsTextPosition());
AXRange<AXPosition<AXNodePosition, AXNode>> root_to_static2_text_range(
start->Clone(), end->Clone());
TestPositionRange root_to_static2_text_range(start->Clone(), end->Clone());
EXPECT_EQ(all_text, root_to_static2_text_range.GetText());
AXRange<AXPosition<AXNodePosition, AXNode>>
root_to_static2_text_range_backward(std::move(end), std::move(start));
TestPositionRange root_to_static2_text_range_backward(std::move(end),
std::move(start));
EXPECT_EQ(all_text, root_to_static2_text_range_backward.GetText());
// root_ to static_text2_'s start
......@@ -289,11 +330,10 @@ TEST_F(AXRangeTest, AXRangeGetTextWithWholeObjects) {
0 /* child_index */);
end = AXNodePosition::CreateTreePosition(
tree_.data().tree_id, static_text2_.id, 0 /* child_index */);
AXRange<AXPosition<AXNodePosition, AXNode>> root_to_static2_tree_range(
start->Clone(), end->Clone());
TestPositionRange root_to_static2_tree_range(start->Clone(), end->Clone());
EXPECT_EQ(text_up_to_text2_tree_start, root_to_static2_tree_range.GetText());
AXRange<AXPosition<AXNodePosition, AXNode>>
root_to_static2_tree_range_backward(std::move(end), std::move(start));
TestPositionRange root_to_static2_tree_range_backward(std::move(end),
std::move(start));
EXPECT_EQ(text_up_to_text2_tree_start,
root_to_static2_tree_range_backward.GetText());
}
......@@ -303,19 +343,17 @@ TEST_F(AXRangeTest, AXRangeGetTextWithTextOffsets) {
BUTTON_TEXT.substr(2).append(TEXT_VALUE).substr(0, 15);
// Create a range starting from the button object and ending two characters
// before the end of the root.
TestPositionType start = AXNodePosition::CreateTextPosition(
TestPositionInstance start = AXNodePosition::CreateTextPosition(
tree_.data().tree_id, button_.id, 2 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_TRUE(start->IsTextPosition());
TestPositionType end = AXNodePosition::CreateTextPosition(
TestPositionInstance end = AXNodePosition::CreateTextPosition(
tree_.data().tree_id, static_text2_.id, 4 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_TRUE(end->IsTextPosition());
AXRange<AXPosition<AXNodePosition, AXNode>> forward_range(start->Clone(),
end->Clone());
TestPositionRange forward_range(start->Clone(), end->Clone());
EXPECT_EQ(most_text, forward_range.GetText());
AXRange<AXPosition<AXNodePosition, AXNode>> backward_range(std::move(end),
std::move(start));
TestPositionRange backward_range(std::move(end), std::move(start));
EXPECT_EQ(most_text, backward_range.GetText());
// root_ to static_text2_'s start with offsets
......@@ -327,11 +365,10 @@ TEST_F(AXRangeTest, AXRangeGetTextWithTextOffsets) {
tree_.data().tree_id, static_text2_.id, 3 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
ASSERT_TRUE(end->IsTextPosition());
AXRange<AXPosition<AXNodePosition, AXNode>> root_to_static2_tree_range(
start->Clone(), end->Clone());
TestPositionRange root_to_static2_tree_range(start->Clone(), end->Clone());
EXPECT_EQ(text_up_to_text2_tree_start, root_to_static2_tree_range.GetText());
AXRange<AXPosition<AXNodePosition, AXNode>>
root_to_static2_tree_range_backward(std::move(end), std::move(start));
TestPositionRange root_to_static2_tree_range_backward(std::move(end),
std::move(start));
EXPECT_EQ(text_up_to_text2_tree_start,
root_to_static2_tree_range_backward.GetText());
}
......@@ -339,56 +376,50 @@ TEST_F(AXRangeTest, AXRangeGetTextWithTextOffsets) {
TEST_F(AXRangeTest, AXRangeGetTextWithEmptyRanges) {
// empty string with non-leaf tree position
base::string16 empty_string(base::ASCIIToUTF16(""));
TestPositionType start = AXNodePosition::CreateTreePosition(
TestPositionInstance start = AXNodePosition::CreateTreePosition(
tree_.data().tree_id, root_.id, 0 /* child_index */);
AXRange<AXPosition<AXNodePosition, AXNode>> non_leaf_tree_range(
start->Clone(), start->Clone());
TestPositionRange non_leaf_tree_range(start->Clone(), start->Clone());
EXPECT_EQ(empty_string, non_leaf_tree_range.GetText());
// empty string with leaf tree position
start = AXNodePosition::CreateTreePosition(
tree_.data().tree_id, inline_box1_.id, 0 /* child_index */);
AXRange<AXPosition<AXNodePosition, AXNode>> leaf_empty_range(start->Clone(),
start->Clone());
TestPositionRange leaf_empty_range(start->Clone(), start->Clone());
EXPECT_EQ(empty_string, leaf_empty_range.GetText());
// empty string with leaf text position and no offset
start = AXNodePosition::CreateTextPosition(
tree_.data().tree_id, inline_box1_.id, 0 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
AXRange<AXPosition<AXNodePosition, AXNode>> leaf_text_no_offset(
start->Clone(), start->Clone());
TestPositionRange leaf_text_no_offset(start->Clone(), start->Clone());
EXPECT_EQ(empty_string, leaf_text_no_offset.GetText());
// empty string with leaf text position with offset
start = AXNodePosition::CreateTextPosition(
tree_.data().tree_id, inline_box1_.id, 3 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
AXRange<AXPosition<AXNodePosition, AXNode>> leaf_text_offset(start->Clone(),
start->Clone());
TestPositionRange leaf_text_offset(start->Clone(), start->Clone());
EXPECT_EQ(empty_string, leaf_text_offset.GetText());
// empty string with non-leaf text with no offset
start = AXNodePosition::CreateTextPosition(
tree_.data().tree_id, root_.id, 0 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
AXRange<AXPosition<AXNodePosition, AXNode>> non_leaf_text_no_offset(
start->Clone(), start->Clone());
TestPositionRange non_leaf_text_no_offset(start->Clone(), start->Clone());
EXPECT_EQ(empty_string, non_leaf_text_no_offset.GetText());
// empty string with non-leaf text position with offset
start = AXNodePosition::CreateTextPosition(
tree_.data().tree_id, root_.id, 3 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
AXRange<AXPosition<AXNodePosition, AXNode>> non_leaf_text_offset(
start->Clone(), start->Clone());
TestPositionRange non_leaf_text_offset(start->Clone(), start->Clone());
EXPECT_EQ(empty_string, non_leaf_text_offset.GetText());
// empty string with same position between two anchors, but different offsets
TestPositionType after_end = AXNodePosition::CreateTextPosition(
TestPositionInstance after_end = AXNodePosition::CreateTextPosition(
tree_.data().tree_id, line_break_.id, 1 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
TestPositionType before_start = AXNodePosition::CreateTextPosition(
TestPositionInstance before_start = AXNodePosition::CreateTextPosition(
tree_.data().tree_id, static_text2_.id, 0 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
......@@ -401,4 +432,101 @@ TEST_F(AXRangeTest, AXRangeGetTextWithEmptyRanges) {
after_end->Clone());
EXPECT_EQ(empty_string, same_position_different_anchors_backward.GetText());
}
TEST_F(AXRangeTest, AXRangeGetAnchors) {
TestPositionInstance button_start = AXNodePosition::CreateTextPosition(
tree_.data().tree_id, button_.id, 0 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
TestPositionInstance button_middle = AXNodePosition::CreateTextPosition(
tree_.data().tree_id, button_.id, 3 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
TestPositionInstance button_end = AXNodePosition::CreateTextPosition(
tree_.data().tree_id, button_.id, 6 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
// Since a check box is not visible to the text representation, it spans an
// empty anchor whose start and end positions are the same.
TestPositionInstance check_box = AXNodePosition::CreateTextPosition(
tree_.data().tree_id, check_box_.id, 0 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
TestPositionInstance line1_start = AXNodePosition::CreateTextPosition(
tree_.data().tree_id, inline_box1_.id, 0 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
TestPositionInstance line1_middle = AXNodePosition::CreateTextPosition(
tree_.data().tree_id, inline_box1_.id, 3 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
TestPositionInstance line1_end = AXNodePosition::CreateTextPosition(
tree_.data().tree_id, inline_box1_.id, 6 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
TestPositionInstance line_break_start = AXNodePosition::CreateTextPosition(
tree_.data().tree_id, line_break_.id, 0 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
TestPositionInstance line_break_end = AXNodePosition::CreateTextPosition(
tree_.data().tree_id, line_break_.id, 1 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
TestPositionInstance line2_start = AXNodePosition::CreateTextPosition(
tree_.data().tree_id, inline_box2_.id, 0 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
TestPositionInstance line2_middle = AXNodePosition::CreateTextPosition(
tree_.data().tree_id, inline_box2_.id, 3 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);
TestPositionRange whole_anchor_range(button_start->Clone(),
button_end->Clone());
std::vector<TestPositionRange> range_anchors =
whole_anchor_range.GetAnchors();
std::vector<TestPositionRange> expected_anchors;
expected_anchors.emplace_back(button_start->Clone(), button_end->Clone());
EXPECT_RANGE_VECTOR_EQ(expected_anchors, range_anchors);
TestPositionRange non_null_degenerate_range(check_box->Clone(),
check_box->Clone());
range_anchors = non_null_degenerate_range.GetAnchors();
expected_anchors.clear();
expected_anchors.emplace_back(check_box->Clone(), check_box->Clone());
EXPECT_RANGE_VECTOR_EQ(expected_anchors, range_anchors);
TestPositionRange across_anchors_range(button_middle->Clone(),
line1_middle->Clone());
range_anchors = across_anchors_range.GetAnchors();
expected_anchors.clear();
expected_anchors.emplace_back(button_middle->Clone(), button_end->Clone());
expected_anchors.emplace_back(check_box->Clone(), check_box->Clone());
expected_anchors.emplace_back(line1_start->Clone(), line1_middle->Clone());
EXPECT_RANGE_VECTOR_EQ(expected_anchors, range_anchors);
TestPositionRange across_anchors_backward_range(line1_middle->Clone(),
button_middle->Clone());
range_anchors = across_anchors_backward_range.GetAnchors();
EXPECT_RANGE_VECTOR_EQ(expected_anchors, range_anchors);
TestPositionRange starting_at_end_position_range(line1_end->Clone(),
line2_middle->Clone());
range_anchors = starting_at_end_position_range.GetAnchors();
expected_anchors.clear();
expected_anchors.emplace_back(line1_end->Clone(), line1_end->Clone());
expected_anchors.emplace_back(line_break_start->Clone(),
line_break_end->Clone());
expected_anchors.emplace_back(line2_start->Clone(), line2_middle->Clone());
EXPECT_RANGE_VECTOR_EQ(expected_anchors, range_anchors);
TestPositionRange ending_at_start_position_range(line1_middle->Clone(),
line2_start->Clone());
range_anchors = ending_at_start_position_range.GetAnchors();
expected_anchors.clear();
expected_anchors.emplace_back(line1_middle->Clone(), line1_end->Clone());
expected_anchors.emplace_back(line_break_start->Clone(),
line_break_end->Clone());
expected_anchors.emplace_back(line2_start->Clone(), line2_start->Clone());
EXPECT_RANGE_VECTOR_EQ(expected_anchors, range_anchors);
}
} // namespace ui
......@@ -2143,18 +2143,28 @@ TEST_F(AXPlatformNodeTextRangeProviderTest,
base::win::ScopedSafearray rectangles;
EXPECT_HRESULT_SUCCEEDED(
text_range_provider->GetBoundingRectangles(rectangles.Receive()));
std::vector<double> expected_values = {100, 150, 200, 200};
EXPECT_UIA_DOUBLE_SAFEARRAY_EQ(rectangles.Get(), expected_values);
rectangles.Reset();
ComPtr<ITextRangeProvider> document_textrange;
GetTextRangeProviderFromTextNode(document_textrange, root_node);
base::win::ScopedSafearray body_rectangles;
EXPECT_HRESULT_SUCCEEDED(
document_textrange->GetBoundingRectangles(body_rectangles.Receive()));
document_textrange->GetBoundingRectangles(rectangles.Receive()));
expected_values = {100, 150, 200, 200, 200, 250, 100, 100};
EXPECT_UIA_DOUBLE_SAFEARRAY_EQ(body_rectangles.Get(), expected_values);
EXPECT_UIA_DOUBLE_SAFEARRAY_EQ(rectangles.Get(), expected_values);
rectangles.Reset();
EXPECT_UIA_MOVE(document_textrange, TextUnit_Character,
/*count*/ 9,
/*expected_text*/ L"m",
/*expected_count*/ 9);
EXPECT_HRESULT_SUCCEEDED(
document_textrange->GetBoundingRectangles(rectangles.Receive()));
expected_values = {200, 250, 100, 100};
EXPECT_UIA_DOUBLE_SAFEARRAY_EQ(rectangles.Get(), expected_values);
AXTreeManagerMap::GetInstance().RemoveTreeManager(tree_data.tree_id);
}
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment